Files
GearBox/src/client/components/AddToCollectionModal.tsx
2026-04-07 15:28:34 +02:00

167 lines
4.6 KiB
TypeScript

import { useEffect, useState } from "react";
import { toast } from "sonner";
import { useCategories } from "../hooks/useCategories";
import { useCreateItem } from "../hooks/useItems";
import { useUIStore } from "../stores/uiStore";
import { CategoryPicker } from "./CategoryPicker";
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<number | null>(null);
const [notes, setNotes] = useState("");
const [purchasePrice, setPurchasePrice] = useState("");
const [error, setError] = useState<string | null>(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 (
<div
role="dialog"
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={handleClose}
onKeyDown={(e) => {
if (e.key === "Escape") handleClose();
}}
>
<div
role="document"
className="w-full max-w-md bg-white rounded-xl shadow-xl p-6"
onClick={(e) => e.stopPropagation()}
onKeyDown={() => {}}
>
<h2 className="text-lg font-semibold text-gray-900 mb-1">
Add to Collection
</h2>
<p className="text-sm text-gray-500 mb-4">{globalItemName}</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Category
</label>
<CategoryPicker
value={categoryId ?? 0}
onChange={(id) => setCategoryId(id)}
/>
</div>
<div>
<label
htmlFor="collection-notes"
className="block text-sm font-medium text-gray-700 mb-1"
>
Notes
</label>
<textarea
id="collection-notes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Personal notes (optional)"
rows={3}
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent resize-none"
/>
</div>
<div>
<label
htmlFor="collection-price"
className="block text-sm font-medium text-gray-700 mb-1"
>
Purchase Price ($)
</label>
<input
id="collection-price"
type="number"
value={purchasePrice}
onChange={(e) => setPurchasePrice(e.target.value)}
placeholder="Purchase price (optional)"
min="0"
step="0.01"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 focus:border-transparent"
/>
</div>
{error && <p className="text-sm text-red-600">{error}</p>}
<div className="flex justify-end gap-2 pt-2">
<button
type="button"
onClick={handleClose}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
>
Cancel
</button>
<button
type="submit"
disabled={createItem.isPending}
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
>
{createItem.isPending ? "Adding..." : "Add to Collection"}
</button>
</div>
</form>
</div>
</div>
);
}