18 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
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
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
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
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
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
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
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