diff --git a/src/client/components/DashboardCard.tsx b/src/client/components/DashboardCard.tsx index cf83ed1..513540e 100644 --- a/src/client/components/DashboardCard.tsx +++ b/src/client/components/DashboardCard.tsx @@ -1,50 +1,50 @@ import { Link } from "@tanstack/react-router"; -import type { ReactNode } from "react"; +import { LucideIcon } from "../lib/iconData"; interface DashboardCardProps { - to: string; - search?: Record; - title: string; - icon: ReactNode; - stats: Array<{ label: string; value: string }>; - emptyText?: string; + to: string; + search?: Record; + title: string; + icon: string; + stats: Array<{ label: string; value: string }>; + emptyText?: string; } export function DashboardCard({ - to, - search, - title, - icon, - stats, - emptyText, + to, + search, + title, + icon, + stats, + emptyText, }: DashboardCardProps) { - const allZero = stats.every( - (s) => s.value === "0" || s.value === "$0.00" || s.value === "0g", - ); + const allZero = stats.every( + (s) => s.value === "0" || s.value === "$0.00" || s.value === "0g", + ); - return ( - -
- {icon} -

{title}

-
-
- {stats.map((stat) => ( -
- {stat.label} - - {stat.value} - -
- ))} -
- {allZero && emptyText && ( -

{emptyText}

- )} - - ); + return ( + +
+ +

{title}

+
+
+ {stats.map((stat) => ( +
+ {stat.label} + + {stat.value} + +
+ ))} +
+ {allZero && emptyText && ( +

{emptyText}

+ )} + + ); } diff --git a/src/client/components/OnboardingWizard.tsx b/src/client/components/OnboardingWizard.tsx index 81f6cf6..a3ebc6a 100644 --- a/src/client/components/OnboardingWizard.tsx +++ b/src/client/components/OnboardingWizard.tsx @@ -2,315 +2,320 @@ import { useState } from "react"; import { useCreateCategory } from "../hooks/useCategories"; import { useCreateItem } from "../hooks/useItems"; import { useUpdateSetting } from "../hooks/useSettings"; +import { LucideIcon } from "../lib/iconData"; import { IconPicker } from "./IconPicker"; interface OnboardingWizardProps { - onComplete: () => void; + onComplete: () => void; } export function OnboardingWizard({ onComplete }: OnboardingWizardProps) { - const [step, setStep] = useState(1); + const [step, setStep] = useState(1); - // Step 2 state - const [categoryName, setCategoryName] = useState(""); - const [categoryIcon, setCategoryIcon] = useState(""); - const [categoryError, setCategoryError] = useState(""); - const [createdCategoryId, setCreatedCategoryId] = useState(null); + // Step 2 state + const [categoryName, setCategoryName] = useState(""); + const [categoryIcon, setCategoryIcon] = useState(""); + const [categoryError, setCategoryError] = useState(""); + const [createdCategoryId, setCreatedCategoryId] = useState( + null, + ); - // Step 3 state - const [itemName, setItemName] = useState(""); - const [itemWeight, setItemWeight] = useState(""); - const [itemPrice, setItemPrice] = useState(""); - const [itemError, setItemError] = useState(""); + // Step 3 state + const [itemName, setItemName] = useState(""); + const [itemWeight, setItemWeight] = useState(""); + const [itemPrice, setItemPrice] = useState(""); + const [itemError, setItemError] = useState(""); - const createCategory = useCreateCategory(); - const createItem = useCreateItem(); - const updateSetting = useUpdateSetting(); + const createCategory = useCreateCategory(); + const createItem = useCreateItem(); + const updateSetting = useUpdateSetting(); - function handleSkip() { - updateSetting.mutate( - { key: "onboardingComplete", value: "true" }, - { onSuccess: onComplete }, - ); - } + function handleSkip() { + updateSetting.mutate( + { key: "onboardingComplete", value: "true" }, + { onSuccess: onComplete }, + ); + } - function handleCreateCategory() { - const name = categoryName.trim(); - if (!name) { - setCategoryError("Please enter a category name"); - return; - } - setCategoryError(""); - createCategory.mutate( - { name, icon: categoryIcon.trim() || undefined }, - { - onSuccess: (created) => { - setCreatedCategoryId(created.id); - setStep(3); - }, - onError: (err) => { - setCategoryError(err.message || "Failed to create category"); - }, - }, - ); - } + function handleCreateCategory() { + const name = categoryName.trim(); + if (!name) { + setCategoryError("Please enter a category name"); + return; + } + setCategoryError(""); + createCategory.mutate( + { name, icon: categoryIcon.trim() || undefined }, + { + onSuccess: (created) => { + setCreatedCategoryId(created.id); + setStep(3); + }, + onError: (err) => { + setCategoryError(err.message || "Failed to create category"); + }, + }, + ); + } - function handleCreateItem() { - const name = itemName.trim(); - if (!name) { - setItemError("Please enter an item name"); - return; - } - if (!createdCategoryId) return; + function handleCreateItem() { + const name = itemName.trim(); + if (!name) { + setItemError("Please enter an item name"); + return; + } + if (!createdCategoryId) return; - setItemError(""); - const payload: any = { - name, - categoryId: createdCategoryId, - }; - if (itemWeight) payload.weightGrams = Number(itemWeight); - if (itemPrice) payload.priceCents = Math.round(Number(itemPrice) * 100); + setItemError(""); + const payload: any = { + name, + categoryId: createdCategoryId, + }; + if (itemWeight) payload.weightGrams = Number(itemWeight); + if (itemPrice) payload.priceCents = Math.round(Number(itemPrice) * 100); - createItem.mutate(payload, { - onSuccess: () => setStep(4), - onError: (err) => { - setItemError(err.message || "Failed to add item"); - }, - }); - } + createItem.mutate(payload, { + onSuccess: () => setStep(4), + onError: (err) => { + setItemError(err.message || "Failed to add item"); + }, + }); + } - function handleDone() { - updateSetting.mutate( - { key: "onboardingComplete", value: "true" }, - { onSuccess: onComplete }, - ); - } + function handleDone() { + updateSetting.mutate( + { key: "onboardingComplete", value: "true" }, + { onSuccess: onComplete }, + ); + } - return ( -
- {/* Backdrop */} -
+ return ( +
+ {/* Backdrop */} +
- {/* Card */} -
- {/* Step indicator */} -
- {[1, 2, 3].map((s) => ( -
- ))} -
+ {/* Card */} +
+ {/* Step indicator */} +
+ {[1, 2, 3].map((s) => ( +
+ ))} +
- {/* Step 1: Welcome */} - {step === 1 && ( -
-

- Welcome to GearBox! -

-

- Track your gear, compare weights, and plan smarter purchases. - Let's set up your first category and item. -

- - -
- )} + {/* Step 1: Welcome */} + {step === 1 && ( +
+

+ Welcome to GearBox! +

+

+ Track your gear, compare weights, and plan smarter purchases. + Let's set up your first category and item. +

+ + +
+ )} - {/* Step 2: Create category */} - {step === 2 && ( -
-

- Create a category -

-

- Categories help you organize your gear (e.g. Shelter, Cooking, - Clothing). -

+ {/* Step 2: Create category */} + {step === 2 && ( +
+

+ Create a category +

+

+ Categories help you organize your gear (e.g. Shelter, Cooking, + Clothing). +

-
-
- - setCategoryName(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - placeholder="e.g. Shelter" - autoFocus - /> -
+
+
+ + setCategoryName(e.target.value)} + className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g. Shelter" + autoFocus + /> +
-
- - -
+
+ + +
- {categoryError && ( -

{categoryError}

- )} -
+ {categoryError && ( +

{categoryError}

+ )} +
- - -
- )} + + +
+ )} - {/* Step 3: Add item */} - {step === 3 && ( -
-

- Add your first item -

-

- Add a piece of gear to your collection. -

+ {/* Step 3: Add item */} + {step === 3 && ( +
+

+ Add your first item +

+

+ Add a piece of gear to your collection. +

-
-
- - setItemName(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - placeholder="e.g. Big Agnes Copper Spur" - autoFocus - /> -
+
+
+ + setItemName(e.target.value)} + className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g. Big Agnes Copper Spur" + autoFocus + /> +
-
-
- - setItemWeight(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - placeholder="e.g. 1200" - /> -
-
- - setItemPrice(e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - placeholder="e.g. 349.99" - /> -
-
+
+
+ + setItemWeight(e.target.value)} + className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g. 1200" + /> +
+
+ + setItemPrice(e.target.value)} + className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g. 349.99" + /> +
+
- {itemError && ( -

{itemError}

- )} -
+ {itemError &&

{itemError}

} +
- - -
- )} + + +
+ )} - {/* Step 4: Done */} - {step === 4 && ( -
-
🎉
-

- You're all set! -

-

- Your first item has been added. You can now browse your collection, - add more gear, and track your setup. -

- -
- )} -
-
- ); + {/* Step 4: Done */} + {step === 4 && ( +
+
+ +
+

+ You're all set! +

+

+ Your first item has been added. You can now browse your + collection, add more gear, and track your setup. +

+ +
+ )} +
+
+ ); } diff --git a/src/client/routes/collection/index.tsx b/src/client/routes/collection/index.tsx index 12b07a7..283ad8c 100644 --- a/src/client/routes/collection/index.tsx +++ b/src/client/routes/collection/index.tsx @@ -10,6 +10,7 @@ import { useCategories } from "../../hooks/useCategories"; import { useItems } from "../../hooks/useItems"; import { useThreads } from "../../hooks/useThreads"; import { useTotals } from "../../hooks/useTotals"; +import { LucideIcon } from "../../lib/iconData"; import { useUIStore } from "../../stores/uiStore"; const searchSchema = z.object({ @@ -61,7 +62,13 @@ function CollectionView() { return (
-
🎒
+
+ +

Your collection is empty

@@ -158,6 +165,7 @@ function CollectionView() { categoryName={categoryName} categoryIcon={categoryIcon} imageFilename={item.imageFilename} + productUrl={item.productUrl} /> ))}
diff --git a/src/client/routes/index.tsx b/src/client/routes/index.tsx index 31cdb79..d94ebd0 100644 --- a/src/client/routes/index.tsx +++ b/src/client/routes/index.tsx @@ -1,55 +1,56 @@ import { createFileRoute } from "@tanstack/react-router"; -import { useTotals } from "../hooks/useTotals"; -import { useThreads } from "../hooks/useThreads"; -import { useSetups } from "../hooks/useSetups"; import { DashboardCard } from "../components/DashboardCard"; -import { formatWeight, formatPrice } from "../lib/formatters"; +import { useSetups } from "../hooks/useSetups"; +import { useThreads } from "../hooks/useThreads"; +import { useTotals } from "../hooks/useTotals"; +import { formatPrice, formatWeight } from "../lib/formatters"; export const Route = createFileRoute("/")({ - component: DashboardPage, + component: DashboardPage, }); function DashboardPage() { - const { data: totals } = useTotals(); - const { data: threads } = useThreads(false); - const { data: setups } = useSetups(); + const { data: totals } = useTotals(); + const { data: threads } = useThreads(false); + const { data: setups } = useSetups(); - const global = totals?.global; - const activeThreadCount = threads?.length ?? 0; - const setupCount = setups?.length ?? 0; + const global = totals?.global; + const activeThreadCount = threads?.length ?? 0; + const setupCount = setups?.length ?? 0; - return ( -
-
- - - -
-
- ); + return ( +
+
+ + + +
+
+ ); } diff --git a/src/client/routes/setups/$setupId.tsx b/src/client/routes/setups/$setupId.tsx index 13af796..01d8c1f 100644 --- a/src/client/routes/setups/$setupId.tsx +++ b/src/client/routes/setups/$setupId.tsx @@ -1,268 +1,276 @@ -import { useState } from "react"; import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { - useSetup, - useDeleteSetup, - useRemoveSetupItem, -} from "../../hooks/useSetups"; +import { useState } from "react"; import { CategoryHeader } from "../../components/CategoryHeader"; import { ItemCard } from "../../components/ItemCard"; import { ItemPicker } from "../../components/ItemPicker"; -import { formatWeight, formatPrice } from "../../lib/formatters"; +import { + useDeleteSetup, + useRemoveSetupItem, + useSetup, +} from "../../hooks/useSetups"; +import { formatPrice, formatWeight } from "../../lib/formatters"; +import { LucideIcon } from "../../lib/iconData"; export const Route = createFileRoute("/setups/$setupId")({ - component: SetupDetailPage, + component: SetupDetailPage, }); function SetupDetailPage() { - const { setupId } = Route.useParams(); - const navigate = useNavigate(); - const numericId = Number(setupId); - const { data: setup, isLoading } = useSetup(numericId); - const deleteSetup = useDeleteSetup(); - const removeItem = useRemoveSetupItem(numericId); + const { setupId } = Route.useParams(); + const navigate = useNavigate(); + const numericId = Number(setupId); + const { data: setup, isLoading } = useSetup(numericId); + const deleteSetup = useDeleteSetup(); + const removeItem = useRemoveSetupItem(numericId); - const [pickerOpen, setPickerOpen] = useState(false); - const [confirmDelete, setConfirmDelete] = useState(false); + const [pickerOpen, setPickerOpen] = useState(false); + const [confirmDelete, setConfirmDelete] = useState(false); - if (isLoading) { - return ( -
-
-
-
- {[1, 2, 3].map((i) => ( -
- ))} -
-
-
- ); - } + if (isLoading) { + return ( +
+
+
+
+ {[1, 2, 3].map((i) => ( +
+ ))} +
+
+
+ ); + } - if (!setup) { - return ( -
-

Setup not found.

-
- ); - } + if (!setup) { + return ( +
+

Setup not found.

+
+ ); + } - // Compute totals from items - const totalWeight = setup.items.reduce( - (sum, item) => sum + (item.weightGrams ?? 0), - 0, - ); - const totalCost = setup.items.reduce( - (sum, item) => sum + (item.priceCents ?? 0), - 0, - ); - const itemCount = setup.items.length; - const currentItemIds = setup.items.map((item) => item.id); + // Compute totals from items + const totalWeight = setup.items.reduce( + (sum, item) => sum + (item.weightGrams ?? 0), + 0, + ); + const totalCost = setup.items.reduce( + (sum, item) => sum + (item.priceCents ?? 0), + 0, + ); + const itemCount = setup.items.length; + const currentItemIds = setup.items.map((item) => item.id); - // Group items by category - const groupedItems = new Map< - number, - { - items: typeof setup.items; - categoryName: string; - categoryIcon: string; - } - >(); + // Group items by category + const groupedItems = new Map< + number, + { + items: typeof setup.items; + categoryName: string; + categoryIcon: string; + } + >(); - for (const item of setup.items) { - const group = groupedItems.get(item.categoryId); - if (group) { - group.items.push(item); - } else { - groupedItems.set(item.categoryId, { - items: [item], - categoryName: item.categoryName, - categoryIcon: item.categoryIcon, - }); - } - } + for (const item of setup.items) { + const group = groupedItems.get(item.categoryId); + if (group) { + group.items.push(item); + } else { + groupedItems.set(item.categoryId, { + items: [item], + categoryName: item.categoryName, + categoryIcon: item.categoryIcon, + }); + } + } - function handleDelete() { - deleteSetup.mutate(numericId, { - onSuccess: () => navigate({ to: "/setups" }), - }); - } + function handleDelete() { + deleteSetup.mutate(numericId, { + onSuccess: () => navigate({ to: "/setups" }), + }); + } - return ( -
- {/* Setup-specific sticky bar */} -
-
-

- {setup.name} -

-
- - {itemCount}{" "} - {itemCount === 1 ? "item" : "items"} - - - - {formatWeight(totalWeight)} - {" "} - total - - - - {formatPrice(totalCost)} - {" "} - cost - -
-
-
+ return ( +
+ {/* Setup-specific sticky bar */} +
+
+

+ {setup.name} +

+
+ + {itemCount}{" "} + {itemCount === 1 ? "item" : "items"} + + + + {formatWeight(totalWeight)} + {" "} + total + + + + {formatPrice(totalCost)} + {" "} + cost + +
+
+
- {/* Actions */} -
- - -
+ {/* Actions */} +
+ + +
- {/* Empty state */} - {itemCount === 0 && ( -
-
-
📦
-

- No items in this setup -

-

- Add items from your collection to build this loadout. -

- -
-
- )} + {/* Empty state */} + {itemCount === 0 && ( +
+
+
+ +
+

+ No items in this setup +

+

+ Add items from your collection to build this loadout. +

+ +
+
+ )} - {/* Items grouped by category */} - {itemCount > 0 && ( -
- {Array.from(groupedItems.entries()).map( - ([ - categoryId, - { items: categoryItems, categoryName, categoryIcon }, - ]) => { - const catWeight = categoryItems.reduce( - (sum, item) => sum + (item.weightGrams ?? 0), - 0, - ); - const catCost = categoryItems.reduce( - (sum, item) => sum + (item.priceCents ?? 0), - 0, - ); - return ( -
- -
- {categoryItems.map((item) => ( - removeItem.mutate(item.id)} - /> - ))} -
-
- ); - }, - )} -
- )} + {/* Items grouped by category */} + {itemCount > 0 && ( +
+ {Array.from(groupedItems.entries()).map( + ([ + categoryId, + { items: categoryItems, categoryName, categoryIcon }, + ]) => { + const catWeight = categoryItems.reduce( + (sum, item) => sum + (item.weightGrams ?? 0), + 0, + ); + const catCost = categoryItems.reduce( + (sum, item) => sum + (item.priceCents ?? 0), + 0, + ); + return ( +
+ +
+ {categoryItems.map((item) => ( + removeItem.mutate(item.id)} + /> + ))} +
+
+ ); + }, + )} +
+ )} - {/* Item Picker */} - setPickerOpen(false)} - /> + {/* Item Picker */} + setPickerOpen(false)} + /> - {/* Delete Confirmation Dialog */} - {confirmDelete && ( -
-
setConfirmDelete(false)} - /> -
-

- Delete Setup -

-

- Are you sure you want to delete{" "} - {setup.name}? This will not - remove items from your collection. -

-
- - -
-
-
- )} -
- ); + {/* Delete Confirmation Dialog */} + {confirmDelete && ( +
+
setConfirmDelete(false)} + /> +
+

+ Delete Setup +

+

+ Are you sure you want to delete{" "} + {setup.name}? This will not + remove items from your collection. +

+
+ + +
+
+
+ )} +
+ ); } diff --git a/src/client/routes/setups/index.tsx b/src/client/routes/setups/index.tsx index 2042b74..c932754 100644 --- a/src/client/routes/setups/index.tsx +++ b/src/client/routes/setups/index.tsx @@ -1,86 +1,93 @@ -import { useState } from "react"; import { createFileRoute } from "@tanstack/react-router"; -import { useSetups, useCreateSetup } from "../../hooks/useSetups"; +import { useState } from "react"; import { SetupCard } from "../../components/SetupCard"; +import { useCreateSetup, useSetups } from "../../hooks/useSetups"; +import { LucideIcon } from "../../lib/iconData"; export const Route = createFileRoute("/setups/")({ - component: SetupsListPage, + component: SetupsListPage, }); function SetupsListPage() { - const [newSetupName, setNewSetupName] = useState(""); - const { data: setups, isLoading } = useSetups(); - const createSetup = useCreateSetup(); + const [newSetupName, setNewSetupName] = useState(""); + const { data: setups, isLoading } = useSetups(); + const createSetup = useCreateSetup(); - function handleCreateSetup(e: React.FormEvent) { - e.preventDefault(); - const name = newSetupName.trim(); - if (!name) return; - createSetup.mutate( - { name }, - { onSuccess: () => setNewSetupName("") }, - ); - } + function handleCreateSetup(e: React.FormEvent) { + e.preventDefault(); + const name = newSetupName.trim(); + if (!name) return; + createSetup.mutate({ name }, { onSuccess: () => setNewSetupName("") }); + } - return ( -
- {/* Create setup form */} -
- setNewSetupName(e.target.value)} - placeholder="New setup name..." - className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> - -
+ return ( +
+ {/* Create setup form */} +
+ setNewSetupName(e.target.value)} + placeholder="New setup name..." + className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> + +
- {/* Loading skeleton */} - {isLoading && ( -
- {[1, 2].map((i) => ( -
- ))} -
- )} + {/* Loading skeleton */} + {isLoading && ( +
+ {[1, 2].map((i) => ( +
+ ))} +
+ )} - {/* Empty state */} - {!isLoading && (!setups || setups.length === 0) && ( -
-
-
🏕️
-

- No setups yet -

-

- Create one to plan your loadout. -

-
-
- )} + {/* Empty state */} + {!isLoading && (!setups || setups.length === 0) && ( +
+
+
+ +
+

+ No setups yet +

+

+ Create one to plan your loadout. +

+
+
+ )} - {/* Setup grid */} - {!isLoading && setups && setups.length > 0 && ( -
- {setups.map((setup) => ( - - ))} -
- )} -
- ); + {/* Setup grid */} + {!isLoading && setups && setups.length > 0 && ( +
+ {setups.map((setup) => ( + + ))} +
+ )} +
+ ); } diff --git a/src/client/routes/threads/$threadId.tsx b/src/client/routes/threads/$threadId.tsx index d285526..3b6c1ad 100644 --- a/src/client/routes/threads/$threadId.tsx +++ b/src/client/routes/threads/$threadId.tsx @@ -1,147 +1,153 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -import { useThread } from "../../hooks/useThreads"; import { CandidateCard } from "../../components/CandidateCard"; +import { useThread } from "../../hooks/useThreads"; +import { LucideIcon } from "../../lib/iconData"; import { useUIStore } from "../../stores/uiStore"; export const Route = createFileRoute("/threads/$threadId")({ - component: ThreadDetailPage, + component: ThreadDetailPage, }); function ThreadDetailPage() { - const { threadId: threadIdParam } = Route.useParams(); - const threadId = Number(threadIdParam); - const { data: thread, isLoading, isError } = useThread(threadId); - const openCandidateAddPanel = useUIStore((s) => s.openCandidateAddPanel); + const { threadId: threadIdParam } = Route.useParams(); + const threadId = Number(threadIdParam); + const { data: thread, isLoading, isError } = useThread(threadId); + const openCandidateAddPanel = useUIStore((s) => s.openCandidateAddPanel); - if (isLoading) { - return ( -
-
-
-
- {[1, 2, 3].map((i) => ( -
- ))} -
-
-
- ); - } + if (isLoading) { + return ( +
+
+
+
+ {[1, 2, 3].map((i) => ( +
+ ))} +
+
+
+ ); + } - if (isError || !thread) { - return ( -
-

- Thread not found -

- - Back to planning - -
- ); - } + if (isError || !thread) { + return ( +
+

+ Thread not found +

+ + Back to planning + +
+ ); + } - const isActive = thread.status === "active"; - const winningCandidate = thread.resolvedCandidateId - ? thread.candidates.find((c) => c.id === thread.resolvedCandidateId) - : null; + const isActive = thread.status === "active"; + const winningCandidate = thread.resolvedCandidateId + ? thread.candidates.find((c) => c.id === thread.resolvedCandidateId) + : null; - return ( -
- {/* Header */} -
- - ← Back to planning - -
-

- {thread.name} -

- - {isActive ? "Active" : "Resolved"} - -
-
+ return ( +
+ {/* Header */} +
+ + ← Back to planning + +
+

{thread.name}

+ + {isActive ? "Active" : "Resolved"} + +
+
- {/* Resolution banner */} - {!isActive && winningCandidate && ( -
-

- {winningCandidate.name} was - picked as the winner and added to your collection. -

-
- )} + {/* Resolution banner */} + {!isActive && winningCandidate && ( +
+

+ {winningCandidate.name} was + picked as the winner and added to your collection. +

+
+ )} - {/* Add candidate button */} - {isActive && ( -
- -
- )} + {/* Add candidate button */} + {isActive && ( +
+ +
+ )} - {/* Candidate grid */} - {thread.candidates.length === 0 ? ( -
-
🏷️
-

- No candidates yet -

-

- Add your first candidate to start comparing. -

-
- ) : ( -
- {thread.candidates.map((candidate) => ( - - ))} -
- )} -
- ); + {/* Candidate grid */} + {thread.candidates.length === 0 ? ( +
+
+ +
+

+ No candidates yet +

+

+ Add your first candidate to start comparing. +

+
+ ) : ( +
+ {thread.candidates.map((candidate) => ( + + ))} +
+ )} +
+ ); }