From f309c73304623ea0cc64839adee110dc25836f4d Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 6 Apr 2026 15:55:44 +0200 Subject: [PATCH] feat(22-01): add UIStore modal states, AddToCollectionModal, and sonner toasts - Extend UIStore with addToCollectionModal, addToThreadModal, catalogSessionThreadId - Create AddToCollectionModal with category dropdown, notes, purchase price - Install sonner and add Toaster + AddToCollectionModal to root layout - closeCatalogSearch now resets catalogSessionThreadId Co-Authored-By: Claude Opus 4.6 (1M context) --- bun.lock | 3 + package.json | 1 + .../components/AddToCollectionModal.tsx | 178 ++++++++++++++++++ src/client/routes/__root.tsx | 6 + src/client/stores/uiStore.ts | 34 +++- 5 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 src/client/components/AddToCollectionModal.tsx diff --git a/bun.lock b/bun.lock index ad40123..e8a6191 100644 --- a/bun.lock +++ b/bun.lock @@ -20,6 +20,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "recharts": "^3.8.0", + "sonner": "^2.0.7", "tailwindcss": "^4.2.1", "zod": "^4.3.6", "zustand": "^5.0.11", @@ -964,6 +965,8 @@ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], diff --git a/package.json b/package.json index ba28830..9dba994 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "recharts": "^3.8.0", + "sonner": "^2.0.7", "tailwindcss": "^4.2.1", "zod": "^4.3.6", "zustand": "^5.0.11" diff --git a/src/client/components/AddToCollectionModal.tsx b/src/client/components/AddToCollectionModal.tsx new file mode 100644 index 0000000..98e60ce --- /dev/null +++ b/src/client/components/AddToCollectionModal.tsx @@ -0,0 +1,178 @@ +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { useCategories } from "../hooks/useCategories"; +import { useCreateItem } from "../hooks/useItems"; +import { useUIStore } from "../stores/uiStore"; + +export function AddToCollectionModal() { + const { open, globalItemId, globalItemName } = useUIStore( + (s) => s.addToCollectionModal, + ); + const closeAddToCollection = useUIStore((s) => s.closeAddToCollection); + + const { data: categories } = useCategories(); + const createItem = useCreateItem(); + + const [categoryId, setCategoryId] = useState(null); + const [notes, setNotes] = useState(""); + const [purchasePrice, setPurchasePrice] = useState(""); + const [error, setError] = useState(null); + + // Pre-select first category when categories load + useEffect(() => { + if (categories && categories.length > 0 && categoryId === null) { + setCategoryId(categories[0].id); + } + }, [categories, categoryId]); + + // Reset form state when modal closes + useEffect(() => { + if (!open) { + setCategoryId(categories?.[0]?.id ?? null); + setNotes(""); + setPurchasePrice(""); + setError(null); + } + }, [open, categories]); + + if (!open || !globalItemId) return null; + + function handleClose() { + closeAddToCollection(); + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + if (categoryId === null) { + setError("Please select a category"); + return; + } + setError(null); + + const purchasePriceCents = purchasePrice + ? Math.round(Number.parseFloat(purchasePrice) * 100) + : undefined; + + createItem.mutate( + { + name: globalItemName ?? "Unknown Item", + categoryId, + globalItemId: globalItemId!, + notes: notes || undefined, + purchasePriceCents: purchasePriceCents || undefined, + }, + { + onSuccess: () => { + toast.success("Added to Collection"); + closeAddToCollection(); + }, + onError: (err) => { + setError( + err instanceof Error ? err.message : "Failed to add item", + ); + }, + }, + ); + } + + return ( +
{ + if (e.key === "Escape") handleClose(); + }} + > +
e.stopPropagation()} + onKeyDown={() => {}} + > +

+ Add to Collection +

+

{globalItemName}

+ +
+
+ + +
+ +
+ +