Admin panel shows a prominent open/closed toggle above the tabs. When
closed, the shop displays a branded closure message and the checkout API
returns 503. The closure state persists in data/store-status.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nginx: add /photos and /uploads proxy routes to gallery-backend so the
browser can reach the gallery API without needing direct port access
- gallery.js: drop hardcoded port/subdomain fallbacks; use same-origin path
via the new nginx routes
- square.ts: pass buyerEmailAddress to createPayment so Square auto-sends
a payment receipt to the customer on capture
- square.ts: create fulfillments in RESERVED state (was PROPOSED) so staff
can mark orders complete/filled directly from the Square dashboard
- CartDrawer: merge Custom Vinyl into the Shape Balloon line item (one fewer
Square line item per vinyl order); show modifier price deltas in cart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CalDAV: joins were using literal '\n' strings which icalEscape then
double-escaped the backslash, so calendar entries showed raw \n. Now
joins use real newline chars which icalEscape converts correctly.
Added deliveryWindowMinutes to HoursConfig (default 60 min). The
checkout route reads this at request time to set both the Square
deliveryWindowDuration and the customer email arrival window. Admin
hours page now has a number input to configure it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Vinyl line items now include the parent product name in their notes
("Vinyl add-on for: X" and "Add-on for: X | Text: ...") so the Square
dashboard and receipts show which item the vinyl belongs to.
Confirmation emails now show a 1-hour arrival window (was 2.5 hrs
because jobMin for classic tier was used; jobMin is still sent to
Square as deliveryWindowDuration for internal job scheduling).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scheduleType was hardcoded to SCHEDULED even when no deliverAt time was
provided, causing Square to reject with 400. Now uses ASAP when no slot
is present. Also added server-side validation to reject delivery orders
that arrive without a deliverySlotISO before they reach Square.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix vinyl add-on checkout: product line item was dropped when vinyl selected; entryUnitPrice also excluded base product price
- Store vinyl per-letter price on cart entry so CartDrawer charges the config price, not hardcoded 65¢
- Fix two bare modifiers.find() calls (use optional chaining) to prevent checkout crash on bad data
- Validate deliveryCents (must be non-negative integer) and customer name fields (no control chars) in checkout API
- Validate rateOverride values are non-negative numbers in delivery-quote API
- Add RFC 5545 iCalendar escaping to SUMMARY/LOCATION/DESCRIPTION fields to prevent calendar injection
- Add public /api/hours route; pickup and delivery calendars now fetch admin-saved hours and pre-grey closed days
- Reset delivery quote and slot when high-rate item is removed from cart
- Change delivery window copy from 2 hours to 1 hour (DeliveryDatePicker + terms page)
- Fix SVG paths: /color/images/ → /color-picker/images/ (balloon mask, shine, color backgrounds); was causing Safari ? placeholders
- Enlarge padlock icon in PaymentForm from 11px to 14px for better alignment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix Chrome Rose Gold hex (#B76E79 → #C17F87) so it no longer
conflicts with Classic Rose Gold; image still used for display
- ScrollToTop hides when cart drawer is open and uses z-index 98
(below the drawer); uses drawerOpen from CartContext
- Search now switches to All tab automatically so results span every
item, not just the active category
- Add sendAdminErrorAlert() to notify.ts; checkout route emails
admin@beachpartyballoons.com on unexpected server errors and on
critical calendar-write failures; card decline errors are not
forwarded (customers can self-resolve those)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>