balloon-shop/estore/src/lib/overrides.ts
chris 50680a323f Major overhaul: shared nav, admin improvements, email enhancements, routing fixes
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>
2026-04-14 21:14:06 -04:00

54 lines
1.7 KiB
TypeScript

import { readFileSync, existsSync } from 'fs'
import path from 'path'
import { atomicWriteJSON } from './file-utils'
export interface ItemOverride {
hidden?: boolean
categoryOverride?: string
categoryLabelOverride?: string
sortOrder?: number
showColors?: boolean
hiddenModifierIds?: string[]
hiddenVariationIds?: string[]
descriptionOverride?: string
/** Per-modifier minimum selections override. Key = modifier list ID, value = min count. */
modifierMinSelected?: Record<string, number>
/** Minimum latex colors the customer must pick (default 1 when showColors=true). */
colorMin?: number
/** Maximum latex colors the customer can pick (null = unlimited). */
colorMax?: number
/** Extra charge in cents added per chrome color selected (0 = no per-color surcharge). */
chromeSurchargePerColor?: number
/** Unit label for the quantity field, e.g. "ft". When set, the quantity control shows "X ft". */
quantityUnit?: string
}
export type OverridesMap = Record<string, ItemOverride>
const OVERRIDES_PATH = path.join(process.cwd(), 'data', 'item-overrides.json')
export function readOverrides(): OverridesMap {
if (!existsSync(OVERRIDES_PATH)) return {}
try {
return JSON.parse(readFileSync(OVERRIDES_PATH, 'utf-8'))
} catch {
return {}
}
}
export function writeOverrides(overrides: OverridesMap): void {
atomicWriteJSON(OVERRIDES_PATH, overrides)
}
export function setOverride(itemId: string, patch: Partial<ItemOverride>): void {
const all = readOverrides()
all[itemId] = { ...(all[itemId] ?? {}), ...patch }
writeOverrides(all)
}
export function clearOverride(itemId: string): void {
const all = readOverrides()
delete all[itemId]
writeOverrides(all)
}