diff --git a/bun.lockb b/bun.lockb index b3cf693d..86637d00 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 47e4506e..2a68583e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "eslint-config-next": "13.4.12", "fast-xml-parser": "^4.2.6", "mysql2": "^3.10.1", - "next": "13.4.12", + "next": "^14.2.3", "next-themes": "^0.2.1", "postcss": "8.4.24", "posthog-js": "^1.110.0", diff --git a/scripts/achievements.py b/scripts/achievements.py old mode 100644 new mode 100755 index f0093493..7c68d101 --- a/scripts/achievements.py +++ b/scripts/achievements.py @@ -1,8 +1,10 @@ +#! /usr/bin/env python3 + # Purpose: Parsing all achievement info from wiki (iconURL, name, description) # Result is saved to data/achievements.json # { name: { iconURL, name, description, id } } # -# Content Files used: None +# Content Files used: Achievements.json # Wiki Pages used: https://stardewvalleywiki.com/Achievements """ @@ -16,16 +18,19 @@ from tqdm import tqdm from bs4 import BeautifulSoup -from helpers.utils import save_json +from helpers.utils import save_json, load_content from helpers.models import Achievement def get_achievements() -> dict[str, Achievement]: + # Load the achievements.json file + achievementsData = load_content("Achievements.json") + # Get the page and relevant table body URL = "https://stardewvalleywiki.com/Achievements" page = requests.get(URL) soup = BeautifulSoup(page.text, "html.parser") - tbody = soup.find("table").find("tbody") # Get the table body + tbody = soup.find("table", class_="wikitable").find("tbody") # Get the table body id = 0 # This is not the accurate in game ID achievements = {} @@ -42,6 +47,13 @@ def get_achievements() -> dict[str, Achievement]: # Get the text value of the 3rd and 4th tags name = tr.find_all("td")[2].text.strip() description = tr.find_all("td")[3].text.strip() + + # Attach the game ID to the achievement, if one exists + game_id = None # This is the accurate in game ID + for k, v in achievementsData.items(): + game_name = v.split("^")[0] + if name.lower() in game_name.lower(): + game_id = k # Add the achievement to the dictionary achievements[name] = { @@ -49,6 +61,7 @@ def get_achievements() -> dict[str, Achievement]: "name": name, "description": description.replace(" See (note below)", ""), "id": id, + "gameID": int(game_id) if game_id else None, } id += 1 diff --git a/src/components/cards/achievement-card.tsx b/src/components/cards/achievement-card.tsx index 73da4f13..e3c99f6f 100644 --- a/src/components/cards/achievement-card.tsx +++ b/src/components/cards/achievement-card.tsx @@ -29,7 +29,8 @@ export const AchievementCard = ({ const { activePlayer } = usePlayers(); if ( activePlayer?.general?.achievements && - activePlayer.general.achievements.includes(achievement.id) + achievement.gameID && + activePlayer.general.achievements.includes(achievement.gameID) ) { completed = true; } @@ -37,7 +38,7 @@ export const AchievementCard = ({ return (
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx index 123adc1a..925872de 100644 --- a/src/components/ui/accordion.tsx +++ b/src/components/ui/accordion.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { ChevronDownIcon } from "@radix-ui/react-icons"; +import * as React from "react"; import { cn } from "@/lib/utils"; @@ -19,6 +19,36 @@ const AccordionItem = React.forwardRef< AccordionItem.displayName = "AccordionItem"; const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + pullRight?: React.ReactNode; + } +>(({ className, children, pullRight, ...props }, ref) => ( + + *>svg]:rotate-180 [&[data-state=open]>svg]:rotate-180", + className, + )} + {...props} + > + {children} + {pullRight ? ( +
+ {pullRight} + +
+ ) : ( + + )} +
+
+)); +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; + +const AccordionTriggerNoToggle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( @@ -27,17 +57,16 @@ const AccordionTrigger = React.forwardRef< ref={ref} className={cn( // TODO: remove hover:underline ? - "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180", + "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180", className, )} {...props} > {children} - )); -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; +AccordionTriggerNoToggle.displayName = AccordionPrimitive.Trigger.displayName; const AccordionContent = React.forwardRef< React.ElementRef, @@ -56,4 +85,10 @@ const AccordionContent = React.forwardRef< )); AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; +export { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + AccordionTriggerNoToggle, +}; diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx index c2b867ac..c59358b9 100644 --- a/src/components/ui/progress.tsx +++ b/src/components/ui/progress.tsx @@ -1,26 +1,35 @@ -import * as React from "react" -import * as ProgressPrimitive from "@radix-ui/react-progress" +import * as React from "react"; +import * as ProgressPrimitive from "@radix-ui/react-progress"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Progress = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, value, ...props }, ref) => ( - - - -)) -Progress.displayName = ProgressPrimitive.Root.displayName +>(({ className, value, max, ...props }, ref) => ( +
+ + + + + {typeof value === "number" && typeof max === "number" + ? `${value} / ${max}` + : ``} + +
+)); +Progress.displayName = ProgressPrimitive.Root.displayName; -export { Progress } +export { Progress }; diff --git a/src/contexts/players-context.tsx b/src/contexts/players-context.tsx index 1f3ba076..4b3628c4 100644 --- a/src/contexts/players-context.tsx +++ b/src/contexts/players-context.tsx @@ -159,7 +159,9 @@ export function mergeDeep(target: any, ...sources: any[]): any { if (isObject(target) && isObject(source)) { for (const key in source) { - if (isObject(source[key])) { + if (Array.isArray(source[key])) { + newTarget[key] = source[key]; + } else if (isObject(source[key])) { if (!target[key]) { newTarget[key] = Array.isArray(source[key]) ? [] : {}; } diff --git a/src/data/achievements.json b/src/data/achievements.json index a405b5b1..5fad43f8 100644 --- a/src/data/achievements.json +++ b/src/data/achievements.json @@ -3,240 +3,343 @@ "iconURL": "https://stardewvalleywiki.com/mediawiki/images/1/1d/Achievement_Greenhorn.jpg", "name": "Greenhorn", "description": "Earn 15,000g", - "id": 0 + "id": 0, + "gameID": 0 }, "Cowpoke": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/2/24/Achievement_Cowpoke.jpg", "name": "Cowpoke", "description": "Earn 50,000g", - "id": 1 + "id": 1, + "gameID": 1 }, "Homesteader": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/f/fb/Achievement_Homesteader.jpg", "name": "Homesteader", "description": "Earn 250,000g", - "id": 2 + "id": 2, + "gameID": 2 }, "Millionaire": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/9/91/Achievement_Millionaire.jpg", "name": "Millionaire", "description": "Earn 1,000,000g", - "id": 3 + "id": 3, + "gameID": 3 }, "Legend": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/b/bb/Achievement_Legend.jpg", "name": "Legend", "description": "Earn 10,000,000g (Secret Achievement)", - "id": 4 + "id": 4, + "gameID": 4 }, "A Complete Collection": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/c/c6/Achievement_A_Complete_Collection.jpg", "name": "A Complete Collection", "description": "Complete the museum collection.", - "id": 5 + "id": 5, + "gameID": 5 }, "A New Friend": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/c/cd/Achievement_A_New_Friend.jpg", "name": "A New Friend", "description": "Reach a 5-heart friend level with someone.", - "id": 6 + "id": 6, + "gameID": 6 }, "Best Friends": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/e/e8/Achievement_Best_Friends.jpg", "name": "Best Friends", "description": "Reach a 10-heart friend level with someone.", - "id": 7 + "id": 7, + "gameID": 7 }, "The Beloved Farmer": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/5/5d/Achievement_The_Beloved_Farmer.jpg", "name": "The Beloved Farmer", "description": "Reach a 10-heart friend level with 8 people.", - "id": 8 + "id": 8, + "gameID": 9 }, "Cliques": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/2/26/Achievement_Cliques.jpg", "name": "Cliques", "description": "Reach a 5-heart friend level with 4 people.", - "id": 9 + "id": 9, + "gameID": 11 }, "Networking": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/5/5e/Achievement_Networking.jpg", "name": "Networking", "description": "Reach a 5-heart friend level with 10 people.", - "id": 10 + "id": 10, + "gameID": 12 }, "Popular": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/a/a4/Achievement_Popular.jpg", "name": "Popular", "description": "Reach a 5-heart friend level with 20 people.", - "id": 11 + "id": 11, + "gameID": 13 }, "Cook": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/c/c6/Achievement_Cook.jpg", "name": "Cook", "description": "Cook 10 different recipes.", - "id": 12 + "id": 12, + "gameID": 15 }, "Sous Chef": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/4/44/Achievement_Sous_Chef.jpg", "name": "Sous Chef", "description": "Cook 25 different recipes.", - "id": 13 + "id": 13, + "gameID": 16 }, "Gourmet Chef": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/d/dd/Achievement_Gourmet_Chef.jpg", "name": "Gourmet Chef", "description": "Cook every recipe.", - "id": 14 + "id": 14, + "gameID": 17 }, "Moving Up": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/b/bd/Achievement_Moving_Up.jpg", "name": "Moving Up", "description": "Upgrade your house.", - "id": 15 + "id": 15, + "gameID": 18 }, "Living Large": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/6/65/Achievement_Living_Large.jpg", "name": "Living Large", "description": "Upgrade your house to the maximum size. (2nd upgrade, not cellar)", - "id": 16 + "id": 16, + "gameID": 19 }, "D.I.Y.": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/2/2e/Achievement_DIY.jpg", "name": "D.I.Y.", "description": "Craft 15 different items.", - "id": 17 + "id": 17, + "gameID": 20 }, "Artisan": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/2/20/Achievement_Artisan.jpg", "name": "Artisan", "description": "Craft 30 different items.", - "id": 18 + "id": 18, + "gameID": 21 }, "Craft Master": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/6/60/Achievement_Master_Craft.jpg", "name": "Craft Master", "description": "Craft every item.", - "id": 19 + "id": 19, + "gameID": 22 }, "Fisherman": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/0/00/Achievement_Fisherman.jpg", "name": "Fisherman", "description": "Catch 10 different fish.", - "id": 20 + "id": 20, + "gameID": 24 }, "Ol' Mariner": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/a/af/Achievement_Ol_Mariner.jpg", "name": "Ol' Mariner", "description": "Catch 24 different fish.", - "id": 21 + "id": 21, + "gameID": 25 }, "Master Angler": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/6/65/Achievement_Master_Angler.jpg", "name": "Master Angler", "description": "Catch every fish.", - "id": 22 + "id": 22, + "gameID": 26 }, "Mother Catch": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/0/0f/Achievement_Mother_Catch.jpg", "name": "Mother Catch", "description": "Catch 100 fish.", - "id": 23 + "id": 23, + "gameID": 27 }, "Treasure Trove": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/5/55/Achievement_Treasure_Trove.jpg", "name": "Treasure Trove", "description": "Donate 40 different items to the museum.", - "id": 24 + "id": 24, + "gameID": 28 }, "Gofer": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/2/27/Achievement_Gofer.jpg", "name": "Gofer", "description": "Complete 10 'Help Wanted' requests.", - "id": 25 + "id": 25, + "gameID": 29 }, "A Big Help": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/3/37/Achievement_A_Big_Help.jpg", "name": "A Big Help", "description": "Complete 40 'Help Wanted' requests.", - "id": 26 + "id": 26, + "gameID": 30 }, "Polyculture": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/7/7f/Achievement_Polyculture.jpg", "name": "Polyculture", "description": "Ship 15 of each crop.", - "id": 27 + "id": 27, + "gameID": 31 }, "Monoculture": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/2/2c/Achievement_Monoculture.jpg", "name": "Monoculture", "description": "Ship 300 of one crop.", - "id": 28 + "id": 28, + "gameID": 32 }, "Full Shipment": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/b/b8/Achievement_Full_Shipment.jpg", "name": "Full Shipment", "description": "Ship every item.", - "id": 29 + "id": 29, + "gameID": 34 }, "Prairie King": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/7/79/Achievement_Prarie_King.jpg", "name": "Prairie King", "description": "Beat 'Journey of the Prairie King'.", - "id": 30 + "id": 30, + "gameID": null }, "The Bottom": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/3/3b/Achievement_The_Bottom.jpg", "name": "The Bottom", "description": "Reach the lowest level of the mines.", - "id": 31 + "id": 31, + "gameID": null }, "Local Legend": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/7/72/Achievement_Local_Legend.jpg", "name": "Local Legend", "description": "Restore the Pelican Town Community Center.", - "id": 32 + "id": 32, + "gameID": null }, "Joja Co. Member Of The Year": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/0/02/Achievement_Joja_Co._Member_Of_The_Year.jpg", "name": "Joja Co. Member Of The Year", "description": "Purchase all Joja Community Development projects.", - "id": 33 + "id": 33, + "gameID": null }, "Mystery Of The Stardrops": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/e/e0/Achievement_Mystery_Of_The_Stardrops.jpg", "name": "Mystery Of The Stardrops", "description": "Find every stardrop.", - "id": 34 + "id": 34, + "gameID": null }, "Full House": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/e/e4/Achievement_Full_House.jpg", "name": "Full House", "description": "Get married and have two kids.", - "id": 35 + "id": 35, + "gameID": null }, "Singular Talent": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/6/6f/Achievement_Singular_Talent.jpg", "name": "Singular Talent", "description": "Reach level 10 in a skill.", - "id": 36 + "id": 36, + "gameID": null }, "Master Of The Five Ways": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/4/49/Achievement_Master_Of_The_Five_Ways.jpg", "name": "Master Of The Five Ways", "description": "Reach level 10 in every skill.", - "id": 37 + "id": 37, + "gameID": null }, "Protector Of The Valley": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/6/66/Achievement_Protector_Of_The_Valley.jpg", "name": "Protector Of The Valley", "description": "Complete all of the Adventure Guild Monster Slayer goals.", - "id": 38 + "id": 38, + "gameID": null }, "Fector's Challenge": { "iconURL": "https://stardewvalleywiki.com/mediawiki/images/f/f2/Achievement_Fector%27s_Challenge.jpg", "name": "Fector's Challenge", "description": "Beat 'Journey Of The Prairie King' without dying. (Secret Achievement)", - "id": 39 + "id": 39, + "gameID": null + }, + "A Distant Shore": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/5/5b/Achievement_A_Distant_Shore.jpg/64px-Achievement_A_Distant_Shore.jpg", + "name": "A Distant Shore", + "description": "Reach Ginger Island.", + "id": 40, + "gameID": 40 + }, + "Well-Read": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/1/1b/Achievement_Well-Read.jpg/64px-Achievement_Well-Read.jpg", + "name": "Well-Read", + "description": "Read every power book.", + "id": 41, + "gameID": 35 + }, + "Two Thumbs Up": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/c/ca/Achievement_Two_Thumbs_Up.jpg/64px-Achievement_Two_Thumbs_Up.jpg", + "name": "Two Thumbs Up", + "description": "See a movie.", + "id": 42, + "gameID": 36 + }, + "Blue Ribbon": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/7/7a/Achievement_Blue_Ribbon.jpg/64px-Achievement_Blue_Ribbon.jpg", + "name": "Blue Ribbon", + "description": "Get 1st place in the Stardew Valley Fair competition.", + "id": 43, + "gameID": 37 + }, + "An Unforgettable Soup": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/c/c8/Achievement_An_Unforgettable_Soup.jpg/64px-Achievement_An_Unforgettable_Soup.jpg", + "name": "An Unforgettable Soup", + "description": "Delight the Governor.", + "id": 44, + "gameID": 38 + }, + "Good Neighbors": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/0/00/Achievement_Good_Neighbors.jpg/64px-Achievement_Good_Neighbors.jpg", + "name": "Good Neighbors", + "description": "Help your forest neighbors grow their family.", + "id": 45, + "gameID": 39 + }, + "Danger In The Deep": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/9/99/Achievement_Danger_In_The_Deep.jpg/64px-Achievement_Danger_In_The_Deep.jpg", + "name": "Danger In The Deep", + "description": "Reach the bottom of the 'dangerous' mines.", + "id": 46, + "gameID": 41 + }, + "Infinite Power": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/0/0c/Achievement_Infinite_Power.jpg/64px-Achievement_Infinite_Power.jpg", + "name": "Infinite Power", + "description": "Obtain the most powerful weapon.", + "id": 47, + "gameID": 42 + }, + "Perfection": { + "iconURL": "https://stardewvalleywiki.com/mediawiki/images/thumb/8/8b/Achievement_Perfection.jpg/64px-Achievement_Perfection.jpg", + "name": "Perfection", + "description": "Reach the summit.", + "id": 48, + "gameID": 44 } } \ No newline at end of file diff --git a/src/pages/bundles.tsx b/src/pages/bundles.tsx index ee5d38aa..6af38210 100644 --- a/src/pages/bundles.tsx +++ b/src/pages/bundles.tsx @@ -16,10 +16,28 @@ import { isRandomizer, } from "@/types/bundles"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + import { PlayerType, usePlayers } from "@/contexts/players-context"; import { usePreferences } from "@/contexts/preferences-context"; import { AchievementCard } from "@/components/cards/achievement-card"; +import { BundleItemCard } from "@/components/cards/bundle-item-card"; import { UnblurDialog } from "@/components/dialogs/unblur-dialog"; import BundleSheet from "@/components/sheets/bundle-sheet"; import { @@ -27,16 +45,14 @@ import { AccordionContent, AccordionItem, AccordionTrigger, + AccordionTriggerNoToggle, } from "@/components/ui/accordion"; -import { - ContextMenu, - ContextMenuContent, - ContextMenuRadioGroup, - ContextMenuRadioItem, - ContextMenuTrigger, -} from "@/components/ui/context-menu"; +import { Progress } from "@/components/ui/progress"; +import { useMediaQuery } from "@react-hook/media-query"; +import { IconSettings } from "@tabler/icons-react"; +import clsx from "clsx"; import { useEffect, useState } from "react"; -import { BundleItemCard } from "@/components/cards/bundle-item-card"; +import { isNumber } from "util"; export const ItemQualityToString = { "0": "Normal", @@ -55,6 +71,7 @@ type BundleAccordionProps = { type AccordionSectionProps = { title: string; children: JSX.Element | JSX.Element[]; + completedCount?: number; }; const CommunityCenterRooms: CommunityCenterRoomName[] = [ @@ -68,11 +85,28 @@ const CommunityCenterRooms: CommunityCenterRoomName[] = [ ]; function AccordionSection(props: AccordionSectionProps): JSX.Element { + const { activePlayer } = usePlayers(); + let progressIndicator = + activePlayer && + typeof props.completedCount === "number" && + Array.isArray(props.children) && + props.completedCount < props.children.length ? ( + + ) : ( + `` + ); return (
- + {props.title} @@ -87,94 +121,101 @@ function AccordionSection(props: AccordionSectionProps): JSX.Element { } function BundleAccordion(props: BundleAccordionProps): JSX.Element { - let bundleCompleted = BundleCompleted(props.bundleWithStatus); - let additionalClasses = ""; - let remainingCount = ""; - if (bundleCompleted) { - additionalClasses = - " border-green-900 bg-green-500/20 hover:bg-green-500/30 dark:bg-green-500/10 hover:dark:bg-green-500/20"; - } else { - if ( - // If we don't need all the items, show how many are remaining - !( - props.bundleWithStatus.bundle.itemsRequired === -1 || - props.bundleWithStatus.bundle.itemsRequired >= - props.bundleWithStatus.bundle.items.length - ) - ) { - let completedItems = props.bundleWithStatus.bundleStatus.reduce( - (acc, cur) => { - if (cur) { - return acc + 1; - } - return acc; - }, - 0, - ); - let requiredCount = props.bundleWithStatus.bundle.itemsRequired; - if (props.bundleWithStatus.bundle.itemsRequired === -1) { - requiredCount = props.bundleWithStatus.bundle.items.length; - } - let remaining = requiredCount - completedItems; - remainingCount = ` - ${remaining} item${remaining > 1 ? "s" : ""} remaining`; - } - additionalClasses = " border-neutral-200 dark:border-neutral-800"; - } + const isDesktop = useMediaQuery("only screen and (min-width: 768px)"); + const { bundle, bundleStatus } = props.bundleWithStatus; + + const totalItems = bundle.items.length; + const requiredItems = + bundle.itemsRequired === -1 ? totalItems : bundle.itemsRequired; + const completedItems = bundleStatus.filter(Boolean).length; + const remainingCount = requiredItems - completedItems; + const bundleCompleted = completedItems >= requiredItems; + + const bundleName = props.bundleWithStatus.bundle.localizedName; + + const [selectedBundleName, setSelectedBundleName] = useState( + props.bundleWithStatus.bundle.name, + ); - const completeName = - props.bundleWithStatus.bundle.localizedName + " Bundle" + remainingCount; return (
- {props.alternateOptions && props.alternateOptions.length > 0 ? ( - - - -
{completeName}
-
-
- - - {props.alternateOptions && ( - { - let selectedBundle = props.alternateOptions?.find( - (bundle) => bundle.name === v, - ); - if (props.onChangeBundle && selectedBundle) { - props.onChangeBundle( - selectedBundle, - props.bundleWithStatus, - ); - } - }} - > - {props.alternateOptions.map((option) => { - return ( - +
+
+ {bundleName} Bundle + {props.alternateOptions && + props.alternateOptions.length > 0 && ( + + + + + + + + + +

Change Bundle

+
+
+
+ + + Remix Bundles + + { + setSelectedBundleName(newBundleName); + const selectedBundle = props.alternateOptions?.find( + (bundle) => bundle.name === newBundleName, + ); + if (props.onChangeBundle && selectedBundle) { + props.onChangeBundle( + selectedBundle, + props.bundleWithStatus, + ); + } + }} > - {option.localizedName} Bundle - - ); - })} - - )} - - - ) : ( - -
{completeName}
-
- )} + {props.alternateOptions.map((newBundle) => ( + + {newBundle.localizedName} + + ))} +
+
+
+ )} +
+
+ {!bundleCompleted && ( +
+ +
+ )} + +
{props.children} @@ -433,7 +474,6 @@ export default function Bundles() { // See note in bundlesheet.tsx // @ts-ignore await patchPlayer(patch); - setBundles(GetActiveBundles(activePlayer)); } } @@ -527,6 +567,7 @@ export default function Bundles() { {CommunityCenterRooms.map((roomName: CommunityCenterRoomName) => { let roomBundles: BundleWithStatus[] = []; + let completedCount = 0; if (activePlayer && Array.isArray(activePlayer.bundles)) { roomBundles = activePlayer.bundles.filter((bundleWithStatus) => { if (bundleWithStatus?.bundle) { @@ -535,6 +576,10 @@ export default function Bundles() { return false; } }); + completedCount = roomBundles.reduce((acc, curBundelRet) => { + if (BundleCompleted(curBundelRet)) return acc + 1; + return acc; + }, 0); } else { roomBundles = bundles.filter( (bundleWithStatus) => @@ -542,7 +587,11 @@ export default function Bundles() { ); } return ( - + {roomBundles.map((bundleWithStatus: BundleWithStatus) => { return (