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 (
|