chris 1dc8a087b6 Add vinyl configurator feature and admin sync from balloons-shop
- vinyl-config route + data file for shape/font/pricing config
- CatalogItem: vinylEnabled, vinylPromo fields
- ItemOverride: vinylEnabled, vinylPromo fields
- catalog route: applies vinylEnabled/vinylPromo overrides
- ColorPicker: full vinyl configurator UI (shape picker, text/font, pricing)
- CartContext: vinyl cart fields (vinylText, vinylFontId, vinylShape, etc.)
- CartDrawer: vinyl line items flatMap (shape balloon + custom vinyl service)
- admin/items route: synced more-complete version from balloons-shop
- admin page: vinyl configurator and promo note checkboxes in ItemEditor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 17:01:28 -04:00

78 lines
3.6 KiB
TypeScript

import { NextResponse } from 'next/server'
import { getCatalog } from '@/lib/catalog-cache'
import { readOverrides } from '@/lib/overrides'
export async function GET() {
try {
const [{ items, fetchedAt }, overrides] = await Promise.all([
getCatalog(),
Promise.resolve(readOverrides()),
])
const withOverrides = items.map((item) => {
const ov = overrides[item.id] ?? {}
const toSlug = (n: string) => n.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
// Resolve categories (same logic as catalog route)
const resolvedCats = ov.categoriesOverride?.length
? (() => {
const cats = ov.categoriesOverride!.map(toSlug)
return {
categories: cats,
categoryLabels: ov.categoriesOverride!,
category: cats[0],
categoryLabel: ov.categoriesOverride![0],
}
})()
: {
category: ov.categoryOverride ?? item.category,
categoryLabel: ov.categoryLabelOverride ?? item.categoryLabel,
categories: ov.categoryOverride
? [ov.categoryOverride, ...(item.categories ?? [item.category]).slice(1)]
: (item.categories ?? [item.category]),
categoryLabels: ov.categoryLabelOverride
? [ov.categoryLabelOverride, ...(item.categoryLabels ?? [item.categoryLabel]).slice(1)]
: (item.categoryLabels ?? [item.categoryLabel]),
}
return {
...item,
// Resolved values (what the customer sees)
hidden: ov.hidden ?? false,
featured: ov.featured ?? item.featured ?? false,
...resolvedCats,
sortOrder: ov.sortOrder ?? 0,
showColors: ov.showColors != null ? ov.showColors : item.showColors,
colorMin: ov.colorMin ?? item.colorMin,
colorMax: ov.colorMax !== undefined ? ov.colorMax : item.colorMax,
chromeSurchargePerColor: ov.chromeSurchargePerColor ?? item.chromeSurchargePerColor,
disabledColors: ov.disabledColors?.length ? ov.disabledColors : item.disabledColors,
requiresDelivery: ov.requiresDelivery != null ? ov.requiresDelivery : item.requiresDelivery,
deliveryBaseOverride: ov.deliveryBaseOverride !== undefined ? ov.deliveryBaseOverride : item.deliveryBaseOverride,
deliveryPerMileOverride: ov.deliveryPerMileOverride !== undefined ? ov.deliveryPerMileOverride : item.deliveryPerMileOverride,
description: ov.descriptionOverride ?? item.description,
variations: item.variations.filter((v) => !(ov.hiddenVariationIds ?? []).includes(v.id)),
modifiers: item.modifiers
.filter((m) => !(ov.hiddenModifierIds ?? []).includes(m.id))
.map((m) => {
const minOverride = ov.modifierMinSelected?.[m.id]
return minOverride !== undefined ? { ...m, minSelected: minOverride } : m
}),
// Raw Square values (for admin display)
_rawCategory: item.category,
_rawCategoryLabel: item.categoryLabel,
_rawShowColors: item.showColors,
_rawVariations: item.variations,
_rawModifiers: item.modifiers,
_rawDescription: item.description,
_override: ov,
}
})
return NextResponse.json({ items: withOverrides, overrides, fetchedAt })
} catch (err) {
console.error('[admin/items] error:', err)
return NextResponse.json({ error: 'Failed to load catalog' }, { status: 500 })
}
}