224 Commits

Author SHA1 Message Date
0d95cf93b3 fix: correct shine.svg path and revert balloon mask to /color/ routes
Balloon mask and color images correctly live at /color/images/ (served by
main site via nginx). Only the shine image path was wrong — it's at
/color/shine.svg, not /color/images/shine.svg. That missing file was the
actual cause of Safari showing ? placeholders. Previous commit incorrectly
moved everything to /color-picker/ which broke the CSS mask-image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 09:36:31 -04:00
9d02417059 fix: pre-launch audit, calendar closed days, delivery rate reset, and swatch paths
- 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>
2026-05-05 09:22:42 -04:00
68a987a921 fix: force-dynamic on admin items route to prevent stale cached responses
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 15:25:50 -04:00
7d7d46af32 Make vinyl an optional add-on with checkbox and additive pricing
- 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>
2026-04-29 23:14:42 -04:00
be7f98a347 Add admin backup and restore for all config files
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>
2026-04-29 23:09:34 -04:00
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
7bc84cea75 fix: shop catalog always reflects latest data after admin changes
- 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>
2026-04-18 10:12:41 -04:00
27093bcd54 fix: multi-category checkboxes in admin + requires-delivery toggle
- 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>
2026-04-18 09:44:00 -04:00
0ea1b98a1f feat: required delivery toggle with custom rates per item
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>
2026-04-18 09:31:29 -04:00
107ef43a0e fix: hide category tab when it's already shown as an occasion tab
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>
2026-04-17 15:45:46 -04:00
623b237826 feat: multi-category items and fix new items not appearing
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>
2026-04-17 15:39:31 -04:00
84ab6bef2d feat: featured items — admin toggle, badge, sorted to top
- 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>
2026-04-17 14:21:33 -04:00
6705293e50 fix/feat: hex conflict, scroll-to-top, search all, admin error emails
- 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>
2026-04-17 14:19:29 -04:00
01c908e919 fix: color picker selection keyed on name instead of hex
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>
2026-04-17 14:08:57 -04:00
6865d2d437 fix: lock body scroll when any modal or drawer is open
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>
2026-04-16 09:12:07 -04:00
e95ec68931 feat: admin color availability filter per item
- 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>
2026-04-16 09:00:32 -04:00
1861e10d6d fix: restore missing next/server imports + add force-dynamic to admin routes
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>
2026-04-16 08:50:34 -04:00
f2fa8e3c17 fix: zoom chrome/metallic preview dots to 220% background-size
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>
2026-04-15 14:51:24 -04:00
c22b668bc5 fix: update /color-picker/ → /color/ in estore ColorPicker and CSS
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>
2026-04-15 14:47:16 -04:00
0576677523 feat: scroll-to-top button in estore; fix JS/CSS cache headers on main site
- 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>
2026-04-15 14:41:42 -04:00
c6d5a0265f fix: tour init on All tab + 11" Latex card; fix modal title truncation
- 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>
2026-04-15 14:39:45 -04:00
6fea1f2be1 fix: hide delivery line in order summary when pickup is selected
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>
2026-04-15 14:34:38 -04:00
c130f9bcdf nginx: redirect /color-picker/* to /color/*
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>
2026-04-15 14:32:25 -04:00
e2d9ae7541 nginx: redirects for legal pages, gzip, security headers
- 301 redirects /privacy|terms|refund → /shop/* (pages live in estore)
- gzip compression for HTML/CSS/JS/JSON/SVG
- X-Frame-Options, X-Content-Type-Options, Referrer-Policy headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:44:56 -04:00
f4b1f7722e Fix data dir permissions and legal doc links
- 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>
2026-04-15 13:28:20 -04:00
215a8f2e3f Add Plausible Analytics to color page and estore
Both were missing tracking. All pages now report to beachpartyballoons.com
in Plausible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 21:25:06 -04:00
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
9f9f326af9 Add root docker-compose and osrm data directory 2026-04-13 19:27:07 -04:00
668ee46ba6 Add root .gitignore 2026-04-13 19:22:46 -04:00
c984c14085 Remove terms page — now lives in estore footer 2026-04-13 19:22:36 -04:00
f58ae2c5f7 Add 'main-site/color-picker/' from commit '248d73a619ea4fbdca711a516f464cd0a505bfae'
git-subtree-dir: main-site/color-picker
git-subtree-mainline: 21ebb9667b34023f8d563bf8fa2abf7f838f51d7
git-subtree-split: 248d73a619ea4fbdca711a516f464cd0a505bfae
2026-04-13 19:22:30 -04:00
21ebb9667b Add 'estore/' from commit 'e34dfc397c94025670baa2b73b482c01f3033a6a'
git-subtree-dir: estore
git-subtree-mainline: 746868d720b9be1003a2f783b7a12d526d8eea60
git-subtree-split: e34dfc397c94025670baa2b73b482c01f3033a6a
2026-04-13 19:22:23 -04:00
746868d720 Add 'main-site/' from commit '5cefb4d1618bc54ae0e86830421a8c911900302c'
git-subtree-dir: main-site
git-subtree-mainline: 4d1daa39101c0a85ca6d916f1c31139faf39632a
git-subtree-split: 5cefb4d1618bc54ae0e86830421a8c911900302c
2026-04-13 19:22:17 -04:00
4d1daa3910 Initial monorepo root 2026-04-13 19:21:56 -04:00
e34dfc397c Allow COOKIE_SECURE=false to disable Secure flag behind HTTP proxy
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>
2026-04-13 18:51:45 -04:00
c8fc15be86 Remove OSRM healthcheck — image has no curl/wget/nc
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>
2026-04-13 18:47:31 -04:00
2f7123af21 Fix OSRM healthcheck: use curl HTTP check and add start_period
/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>
2026-04-13 18:42:55 -04:00
69b28be77c Untrack runtime data files from git
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>
2026-04-13 18:28:59 -04:00
cdaf79ac71 Security hardening, checkout reliability, onboarding tour, and UX fixes
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>
2026-04-13 18:27:33 -04:00
3cb9eae975 Initial commit — Beach Party Balloons shop
Full Next.js storefront with Square catalog integration, balloon color picker,
delivery/pickup slot booking, CalDAV calendar sync, and admin panel.

Admin features: item overrides, category display order/visibility, hours editor,
holiday/occasion windows, quantity units, and modifier deselect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 20:37:10 -04:00
248d73a619 Redesign color picker UI and improve palette modals 2026-02-22 15:51:44 -05:00
5cefb4d161 Fix Contact Us button markup 2025-12-27 11:37:07 -05:00
8b0793c42f Enhance FAQ and gallery UI 2025-12-27 11:30:38 -05:00
b585d851dd Add trusted logos and manual reviews 2025-12-27 10:29:38 -05:00
7c42800245 Prevent caching of store status updates 2025-12-26 12:58:46 -05:00
a3b8593133 Improve gallery sharing and admin tagging 2025-12-26 12:50:25 -05:00
3a679eb03c chore: match uploads by stripping timestamp prefixes 2025-12-08 16:02:51 -05:00
cf575afc3f chore: normalize brace suffix in reprocess base names 2025-12-08 15:59:34 -05:00
a94d938131 chore: allow reprocess to use raw source files 2025-12-08 15:56:12 -05:00
d230e88bd1 chore: log missing sources during reprocess 2025-12-08 15:52:39 -05:00