Feat: Add cascading category picker to product forms

Implemented hierarchical category selection in product creation and edit forms.
Now displays parent categories first, then shows child categories when a parent
is selected (e.g., Apparel > Adults, Kids, Babies).

Changes:
- Create CategoryPicker client component for cascading selection
- Update new product page to use CategoryPicker
- Update edit product page to use CategoryPicker
- Query categories with children relationship

User experience:
1. Select parent category (Apparel, Drinkware, Phone Cases, etc.)
2. Select specific type (Adults/Kids/Babies, Ceramic/Steel, etc.)
3. Form automatically validates that both selections are made

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Andymick
2026-07-21 17:54:08 +01:00
co-authored by Claude Haiku 4.5
parent 23631b1bce
commit 64adef53d3
3 changed files with 106 additions and 34 deletions
+6 -17
View File
@@ -7,6 +7,7 @@ import AdminNav from '@/components/AdminNav';
import ColorPicker from '@/components/ColorPicker';
import SizePicker from '@/components/SizePicker';
import PhotoPrintAreaField, { type Box } from '@/components/PhotoPrintAreaField';
import CategoryPicker from '@/components/CategoryPicker';
import { MOCKUP_OPTIONS } from '@/lib/mockupDefaults';
export default async function EditProductPage({ params }: { params: { id: string } }) {
@@ -15,7 +16,10 @@ export default async function EditProductPage({ params }: { params: { id: string
if (!row) notFound();
const product = toProductDTO(row, activePromotions);
const categories = await prisma.category.findMany({ orderBy: { name: 'asc' } });
const categories = await prisma.category.findMany({
orderBy: { name: 'asc' },
include: { children: { orderBy: { name: 'asc' } } },
});
const collections = await prisma.collection.findMany({ orderBy: { name: 'asc' } });
const occasions = collections.filter((c) => c.group === 'OCCASION');
const recipients = collections.filter((c) => c.group === 'RECIPIENT');
@@ -100,22 +104,7 @@ export default async function EditProductPage({ params }: { params: { id: string
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="category" className="tag-label mb-2 block">
Category
</label>
<select
id="category"
name="category"
required
defaultValue={product.category}
className="w-full border border-line bg-paper px-3 py-2 text-sm"
>
{categories.map((c) => (
<option key={c.id} value={c.slug}>
{c.name}
</option>
))}
</select>
<CategoryPicker categories={categories} defaultValue={product.category} />
</div>
<div>
<label htmlFor="mockup" className="tag-label mb-2 block">
+7 -17
View File
@@ -4,10 +4,14 @@ import AdminNav from '@/components/AdminNav';
import ColorPicker from '@/components/ColorPicker';
import SizePicker from '@/components/SizePicker';
import PhotoPrintAreaField from '@/components/PhotoPrintAreaField';
import CategoryPicker from '@/components/CategoryPicker';
import { MOCKUP_OPTIONS } from '@/lib/mockupDefaults';
export default async function NewProductPage() {
const categories = await prisma.category.findMany({ orderBy: { name: 'asc' } });
const categories = await prisma.category.findMany({
orderBy: { name: 'asc' },
include: { children: { orderBy: { name: 'asc' } } },
});
const collections = await prisma.collection.findMany({ orderBy: { name: 'asc' } });
const occasions = collections.filter((c) => c.group === 'OCCASION');
const recipients = collections.filter((c) => c.group === 'RECIPIENT');
@@ -68,22 +72,8 @@ export default async function NewProductPage() {
<div className="grid grid-cols-2 gap-4">
<div>
<label htmlFor="category" className="tag-label mb-2 block">
Category
</label>
<select
id="category"
name="category"
required
className="w-full border border-line bg-paper px-3 py-2 text-sm"
>
{categories.map((c) => (
<option key={c.id} value={c.slug}>
{c.name}
</option>
))}
</select>
<a href="/admin/categories/new" className="mt-1 inline-block text-xs text-clay hover:text-clay-dark">
<CategoryPicker categories={categories} />
<a href="/admin/categories/new" className="mt-3 inline-block text-xs text-clay hover:text-clay-dark">
+ Add a new category
</a>
</div>