- Vinyl is now opt-in via a checkbox (unchecked by default)
- Item's base price is preserved; vinyl cost is added on top
- Checkbox label explains it's lettering on a separate 18" foil balloon
- Price breakdown shows vinyl as an add-on, not a replacement
- Validation only requires text/font when the checkbox is checked
- Editing a vinyl cart entry pre-checks the checkbox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Download button exports item-overrides, delivery-rates, categories-display,
occasions, hours, and vinyl-config as a single JSON file. Restore button
applies a previously downloaded backup (skips vinyl-config to avoid
overwriting it). Both accessible from the admin header.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add force-dynamic to /api/catalog so Next.js never serves a
stale cached route response to the shop
- Add invalidateCatalogCache() to catalog-cache lib to drop the
30s in-process memory cache on demand
- Call invalidateCatalogCache() after every admin PATCH/DELETE on
an item so override saves are reflected on the very next shop
request (no 30s delay)
Refresh from Square already updated the shared disk + memory cache;
force-dynamic ensures the shop route handler actually runs each time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Category selector replaced with checkboxes — items can now be
assigned to multiple categories directly in admin (not just Square).
Each category shows a "Square" label if it came from the Square
assignment. Saves as categoriesOverride[] (array of category names).
- categoriesOverride takes precedence over old categoryOverride in the
catalog route; old overrides still work as fallback.
- Requires-delivery toggle and custom rate fields were already in the
code but needed container rebuild to appear — no logic change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Items can now be marked as "requires delivery" in admin — these items
cannot be picked up and must be delivered (and struck).
- Admin item editor: "Requires delivery" checkbox + custom base/per-mile
rate fields that appear when the toggle is on
- ProductCard: "Delivery & setup required" note on the card
- CartDrawer: pickup toggle is hidden and replaced with an explanation
when any cart item requires delivery; the quote call passes the
item's custom rate override (highest base + highest per-mile wins
when multiple requires-delivery items are in the cart)
- delivery-quote API: accepts optional rateOverride to apply per-item
pricing on top of the inferred tier
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If a "Mothers Day" or "Graduation" occasion is active and its
squareCategorySlug matches a product category, suppress the duplicate
regular category tab so it doesn't appear twice in the bar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Items can now belong to multiple Square categories and appear in all
matching tabs (e.g. a Mother's Day balloon also shows under Easter).
Also fixes new items not appearing when the Square account has no
"online" category — previously this caused zero items to load; now
it falls back to showing all items.
Changes:
- CatalogItem gains categories[] + categoryLabels[] (multi-category)
- square.ts collects all non-skip categories per item; "online" filter
is now optional (show all if category doesn't exist in Square)
- catalog/route.ts propagates categoryOverride into categories[0]
- FeaturedProducts: tabs and filter use the full categories array
- Admin CategoryDisplayEditor sees all categories from multi-cat items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add featured to ItemOverride so it can be set per-item in admin
- Catalog API applies the override and sorts featured items before
non-featured (within each group, sortOrder still applies)
- ProductCard shows a teal Featured badge on the image when featured
and not sold out
- Admin item editor has a ⭐ Featured checkbox beside Hidden
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>
Classic Rose Gold and Chrome Rose Gold share the same hex (#B76E79),
so clicking one would deselect the other. Switched all selection
checks (toggle, remove, highlight) to use color.name which is unique.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add useLockBodyScroll hook (sets overflow:hidden on body, restores on
unmount) and apply it to ColorPicker, AdminColorFilter, WelcomeModal,
and GuidedTour. CartDrawer uses an inline effect keyed on drawerOpen
since it is always mounted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add disabledColors field to ItemOverride and CatalogItem
- Propagate through catalog API applyOverrides
- ColorPicker filters disabled colors out before showing to customers
- New AdminColorFilter modal: same collapsible family layout and balloon
swatches as the customer view; click to hide/show individual colors;
Enable all / Disable all shortcuts; badge shows count of hidden colors
- Button appears in the color limits section for color-enabled items
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A botched sed command stripped the first import line from every admin
route file, breaking NextRequest/NextResponse references. Restored all
imports and added export const dynamic = 'force-dynamic' to all admin
GET handlers so Next.js 14 never serves a stale cached response after
a save — this was the root cause of changes appearing not to save.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Image-based colors (chrome/metallic) have a balloon silhouette against
a transparent bg, so cover was fitting the whole image including
whitespace. 220% zooms into the center where the finish actually is.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ColorPicker.tsx was constructing image URLs with the old /color-picker/
prefix. globals.css had the same for the balloon-mask.svg SVG mask.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ScrollToTop component matching main site's green Top button
(appears after 130px scroll, same styling and font)
- Fix main-site server.js: JS/CSS now use max-age=3600 + must-revalidate
instead of 30d immutable — changes reach users within 1 hour instead
of being stuck in browser cache for a month
- Images/fonts keep 30d immutable (safe, as they are content-addressed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tour now switches to the All tab and clears search on start, ensuring
the 11" Latex product is always visible and the exit overlay works
- data-tour="first-card" now targets the 11" Latex item by name instead
of whichever card happens to be first in the filtered list
- Modal header title now truncates with ellipsis so the X close button
is never pushed off screen by a long product name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
quote was non-null after entering a delivery address, so the delivery
fee row showed even after switching back to pickup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Browsers with cached pages from the old /color-picker/ path resolve
relative image URLs against that base, causing 404s after the rename.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: create /app/data owned by nextjs before USER switch so fresh
deployments work without manual chown. Existing servers need:
sudo chown -R 1001:1001 estore/data
- nav.js: fix footer legal links to point to /shop/privacy|terms|refund
(pages live in estore, not main site)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NODE_ENV=production sets Secure:true but the container may sit behind
an HTTP-only reverse proxy, causing browsers to reject the cookie.
COOKIE_SECURE=false in .env overrides the flag without changing NODE_ENV.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The osrm-backend image is too minimal to run any health probe.
Drop the healthcheck entirely and use a plain depends_on so the
shop starts after OSRM, without blocking on a health condition
that can never pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/dev/tcp is bash-only and fails in the container's default sh.
Switch to a real HTTP check against the OSRM API root, and add a
30s start_period so Docker doesn't fail the check before the road
data finishes loading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
catalog-cache.json and item-overrides.json are written at runtime by the
admin panel — they should not be in version control.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Security:
- Replace raw password cookie with HMAC-derived session token + constant-time compare
- Add rate limiting (5 attempts / 15 min) to admin login
- Atomic JSON writes via file-utils to prevent corruption on crash
- Tighten CSP headers; add Square CDN to style-src and font-src
- WebP conversion + 20 MB limit on admin image uploads
Checkout reliability:
- Delayed capture flow: pre-auth → calendar write → capture (never charge without booking)
- Derive payment idempotency key from SHA-256(nonce) to prevent nonce/key mismatch on retry
- Idempotency key persisted in localStorage; auto-retry on network failure
- Idempotent CalDAV writes using orderId-based UIDs; treat 412 as success
- User-friendly Square error messages instead of raw API detail strings
UX:
- Welcome modal + 5-step guided tour with spotlight and scroll-into-view
- Balloon release agreement checkbox required before payment
- 24-hour lead time enforced server-side in both delivery and pickup slot generators
- Fix Square card form race condition with double-rAF before attach()
- Tour hides Bulma modal-background for bright, unobscured modal steps
Notifications:
- Improved SMTP error logging; re-throw on failure so callers see it
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>