Navigation & layout - Replace per-page hardcoded nav/footer with shared nav.js (client-side injection) - Add nginx reverse proxy back to docker-compose for clean localhost routing - Rename /color-picker/ to /color/ across nav, directory, and references eStore admin - Add variation hiding controls (mirrors existing modifier hiding) - Add delivery rate editor (base fee + per-mile per tier, persisted to data/) - Fix all missing BASE prefix on fetch calls (admin PATCH/DELETE, availability, slots, colors) - Mount estore/data/ as a Docker volume so admin config survives rebuilds Booking & calendar - Set pickup calendar events to TRANSPARENT (free) so they don't block delivery slots - Skip CANCELLED events in busy-time calculation - Re-check slot availability at checkout before charging (409 on conflict) Phone & email validation - Auto-format phone as (XXX) XXX-XXXX as user types - Require exactly 10 digits; tighten email regex Confirmation emails (store alert + customer) - Full item detail per line: name, price, add-ons, colors, note - Charges breakdown: subtotal, delivery fee, tax, total - Delivery window: simplified M/D/YY h:mm – h:mm AM/PM format - .ics calendar attachment on customer confirmation Delivery rates - Extract configurable rates to delivery-rates.ts (server-only, no fs in client bundle) - calcDelivery() accepts optional rates param; delivery-quote route passes configured rates Content - Change all "40+ latex colors" references to "70+" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
154 lines
4.9 KiB
TypeScript
154 lines
4.9 KiB
TypeScript
export interface ModifierOption {
|
|
id: string
|
|
name: string
|
|
priceDelta: number | null // cents, positive or negative
|
|
}
|
|
|
|
export interface ModifierList {
|
|
id: string
|
|
name: string
|
|
selectionType: 'SINGLE' | 'MULTIPLE'
|
|
minSelected: number
|
|
maxSelected: number | null
|
|
options: ModifierOption[]
|
|
}
|
|
|
|
export interface CatalogVariation {
|
|
id: string
|
|
name: string
|
|
priceCents: number
|
|
imageUrls: string[]
|
|
/** null = inventory not tracked for this variation */
|
|
inventory: number | null
|
|
}
|
|
|
|
export interface CatalogItem {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
category: string
|
|
categoryLabel: string
|
|
/** Price in cents of the default variation. null = custom quote required. */
|
|
price: number | null
|
|
imageUrl: string | null
|
|
imageUrls: string[]
|
|
featured: boolean
|
|
tags: string[]
|
|
modifiers: ModifierList[] // empty array if none
|
|
showColors: boolean // true only if item is in the Square "Latex" category
|
|
colorMin: number // minimum colors required when showColors=true (default 1)
|
|
colorMax: number | null // maximum colors allowed (null = unlimited)
|
|
chromeSurchargePerColor: number // extra cents per chrome color selected (0 = flat chrome variation instead)
|
|
variations: CatalogVariation[] // all enabled variations; first is the default
|
|
/** Unit label for quantity, e.g. "ft". Omitted for plain count items. */
|
|
quantityUnit?: string
|
|
}
|
|
|
|
export const MOCK_CATALOG: CatalogItem[] = (([
|
|
{
|
|
id: 'cat-001',
|
|
name: 'Classic Birthday Bouquet',
|
|
description:
|
|
'A cheerful mix of latex and foil balloons perfect for any birthday celebration. Walk-in or pre-order — ready while you wait.',
|
|
category: 'classic',
|
|
categoryLabel: 'Classic',
|
|
price: 4500,
|
|
imageUrl: '/images/classic/hero.webp',
|
|
featured: true,
|
|
tags: ['birthday', 'walk-in', 'same-day'],
|
|
},
|
|
{
|
|
id: 'cat-002',
|
|
name: 'Organic Balloon Arrangement',
|
|
description:
|
|
'Free-form organic clusters in your choice of colors. Modern, textural, and completely custom — perfect for weddings, showers, and celebrations.',
|
|
category: 'organic',
|
|
categoryLabel: 'Organic',
|
|
price: 12500,
|
|
imageUrl: '/images/organic/img1.webp',
|
|
featured: true,
|
|
tags: ['organic', 'modern', 'custom', 'wedding'],
|
|
},
|
|
{
|
|
id: 'cat-003',
|
|
name: 'Full Balloon Arch',
|
|
description:
|
|
'A stunning full arch that frames entrances, stages, or backdrops. Custom colors, sizes, and organic or classic styling available.',
|
|
category: 'arch',
|
|
categoryLabel: 'Arches',
|
|
price: null,
|
|
imageUrl: null,
|
|
featured: true,
|
|
tags: ['arch', 'event', 'corporate', 'custom'],
|
|
},
|
|
{
|
|
id: 'cat-004',
|
|
name: 'Table Centerpiece',
|
|
description:
|
|
'Elegant balloon centerpieces that anchor any reception, gala, or corporate table with a touch of luxury.',
|
|
category: 'centerpiece',
|
|
categoryLabel: 'Centerpieces',
|
|
price: 8500,
|
|
imageUrl: '/images/centerpiece/img1.webp',
|
|
featured: true,
|
|
tags: ['centerpiece', 'wedding', 'corporate', 'reception'],
|
|
},
|
|
{
|
|
id: 'cat-005',
|
|
name: 'Helium Bouquet',
|
|
description:
|
|
'Classic floating helium balloon bouquets — great for walk-ins or delivery. Over 70 latex colors and hundreds of foil shapes in stock.',
|
|
category: 'classic',
|
|
categoryLabel: 'Classic',
|
|
price: 2800,
|
|
imageUrl: '/images/helium/img1.webp',
|
|
featured: false,
|
|
tags: ['helium', 'walk-in', 'same-day', 'delivery'],
|
|
},
|
|
{
|
|
id: 'cat-006',
|
|
name: 'Balloon Sculpture',
|
|
description:
|
|
'Custom balloon animals, crowns, and sculptures hand-crafted by our artists. Perfect for kids\u2019 events and entertainment.',
|
|
category: 'sculpture',
|
|
categoryLabel: 'Sculptures',
|
|
price: null,
|
|
imageUrl: '/images/sculptures/img1.webp',
|
|
featured: false,
|
|
tags: ['sculpture', 'kids', 'entertainment', 'custom'],
|
|
},
|
|
{
|
|
id: 'cat-007',
|
|
name: 'Organic Ceiling Installation',
|
|
description:
|
|
'Dramatic ceiling balloon canopies and cloud installations that transform any venue into something extraordinary.',
|
|
category: 'organic',
|
|
categoryLabel: 'Organic',
|
|
price: null,
|
|
imageUrl: '/images/organic/img2.webp',
|
|
featured: true,
|
|
tags: ['ceiling', 'installation', 'venue', 'luxury'],
|
|
},
|
|
{
|
|
id: 'cat-008',
|
|
name: 'Mini Arch Garland',
|
|
description:
|
|
'A compact balloon garland for smaller spaces — beautiful for dessert tables, mantels, backdrops, and doorways.',
|
|
category: 'arch',
|
|
categoryLabel: 'Arches',
|
|
price: 6500,
|
|
imageUrl: null,
|
|
featured: false,
|
|
tags: ['garland', 'small', 'delivery'],
|
|
},
|
|
]) as any[]).map((item) => ({
|
|
modifiers: [],
|
|
showColors: false,
|
|
colorMin: 1,
|
|
colorMax: null,
|
|
chromeSurchargePerColor: 0,
|
|
imageUrls: item.imageUrl ? [item.imageUrl] : [],
|
|
variations: item.price != null ? [{ id: item.id, name: 'Regular', priceCents: item.price, imageUrls: [], inventory: null }] : [],
|
|
...item,
|
|
})) as CatalogItem[]
|