225 Commits

Author SHA1 Message Date
cd18bd3937 Contact form: add emoji to subject, show event date instead of type 2026-06-07 00:27:50 -04:00
d026bc8217 Fix balloon animation: use Web Animations API instead of CSS custom properties in keyframes 2026-06-06 21:38:40 -04:00
75b20e6ca2 Add debug logging to easter egg trigger 2026-06-06 21:29:51 -04:00
181195dbbc Fix easter egg trigger: use touchstart on mobile, 5 taps in 3s
click events are unreliable on mobile due to scroll handling. Use
touchstart (fires immediately) for mobile and click for desktop with
deduplication. Lower threshold to 5 taps, widen window to 3 seconds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:23:05 -04:00
066364d2b7 Fix easter egg trigger: 7 quick taps on non-interactive area
Long press on the logo link was unreliable. Switch to detecting 7 rapid
taps on any non-link/button area within 2 seconds — works on mobile and
desktop without conflicting with navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:15:26 -04:00
2002d7f35a Add balloon easter egg — long press logo to trigger
Hold the logo for 0.6s (works on mobile touch and desktop mouse) to launch
22 balloon silhouettes floating up from the bottom. Balloons drift with a
slight sway, have a shine highlight, and naturally disappear under the navbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:07:06 -04:00
5900ce817e Fix dark mode: add data-theme=light to all main-site HTML pages
Bulma 1.0 follows system dark mode preference if no theme is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:01:38 -04:00
6a2bf1f30b Fix nginx port: bind to 3000 instead of 80 for NPM compatibility
NPM proxies beachpartyballoons.com → host:3000. Binding to 80 conflicts
with NPM which owns that port.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:59:04 -04:00
0ec3766447 Wire SMTP env vars into main-site container, document in .env.example
Contact form needs SMTP_HOST/PORT/SECURE/USER/PASS and CONTACT_TO passed
through docker-compose. Added to main-site environment block and documented
in .env.example.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:37:50 -04:00
aee1f10179 Sync native contact form to main-site, replace iframe
Replaces the third-party iframe form on both the homepage and contact page
with the self-hosted form: drag-and-drop photo upload, honeypot, rate
limiting, inline validation, auto-reply email. Adds multer/sharp/nodemailer
dependencies and the /api/contact endpoint to server.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:32:05 -04:00
fca6e8da0a Add fulfillment state controls to admin orders panel
Replace single Mark Complete button with contextual In progress / Ready /
Complete buttons based on current fulfillment state. Adds a general
/api/admin/orders/[orderId]/status endpoint that handles all transitions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:22:23 -04:00
ca8773d3c3 Fix order complete: update fulfillment state alongside order state
Square requires all fulfillments to be COMPLETED before the order can be
marked COMPLETED — include fulfillment state in the same updateOrder call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:08:54 -04:00
ef38c42e17 Add Orders tab to admin panel for managing online orders
Fetches open orders from Square filtered by source=online-shop metadata.
Each order shows customer, fulfillment time/address, items, and total with
a Mark Complete button that updates the order state in Square directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:03:52 -04:00
02e49ba41b Fix checkout: block false slots when calendar down, add booking request fallback
- Change fulfillment state from RESERVED to PROPOSED (Square rejects RESERVED)
- Return 503 from slots API when CalDAV is unreachable instead of serving empty
  busy blocks that made all time slots appear falsely available
- Add BookingRequestPanel and /api/booking-request endpoint: when the calendar
  server is down, customers can submit their order and preferred time; server
  emails info@beachpartyballoons.com and sends a confirmation to the customer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 19:58:22 -04:00
a49075b167 Fix Apple Pay verification file — serve directly from nginx
With basePath=/shop the Next.js app can't serve /.well-known/ at the
domain root. Mount the file into the nginx container and serve it
directly instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 06:47:06 -04:00
7d5f74a79e Serve Apple Pay verification file via nginx
Move domain association file to estore/public/.well-known/ so Next.js
serves it, and add a /.well-known/ location block in nginx so Apple's
servers can reach it at the domain root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:50:17 -04:00
92ab3a5633 Add Google Pay and Apple Pay support; fix catalog category filter
- PaymentForm now initialises Google Pay and Apple Pay via Square's Web
  Payments SDK alongside the existing card form; wallet buttons appear
  above the card with an "or pay with card" divider when available
- Apple Pay domain verification file added to public/.well-known/
- square.ts: fix online-category filter to show all items when the
  category doesn't exist; support multi-category display per item

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:01:29 -04:00
53a2ca03e7 fix: force Bulma light theme on admin page 2026-05-21 12:07:22 -04:00
55055ae9bc fix: light theme for all admin modals 2026-05-21 12:02:56 -04:00
1435964f6f feat: drag-to-select and full-header click in admin gallery
- Click anywhere on the top bar of a photo card to toggle selection
  (replaces the tiny checkbox)
- Drag across the grid to rubber-band select multiple photos at once
- Selected cards show a blue ring + tinted header + solid checkmark icon
- Cards swept during drag show a green ring preview before releasing
- Fixed innerHTML += perf issue (now builds all cards then sets once)
- Thumbnails used in grid so page loads faster

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:49:31 -04:00
7fce1632be feat: editable tag presets, next/prev modal nav, needs-tagging filter
- Restructured presets as single-tag accumulators (click multiple to build up tags)
- Added 6 new tags: bridal-shower, cocktail, signature, indoor, outdoor, mitzvah
- Fixed organic/garland alias conflict
- Presets stored in data/presets.json with full CRUD API (add, edit, delete from admin)
- Edit modal shows photo thumbnail, prev/next navigation, preset buttons
- Keyboard shortcuts: Ctrl+Enter to save, arrow keys to navigate, Esc to close
- "Needs tagging" filter in manage view shows only uncategorized/low-tag photos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:42:32 -04:00
0e4461e957 fix: reseed sets createdAt from filename timestamp or file mtime
Photos reseeded from disk now sort by their original upload time
instead of all getting the same insertion timestamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:29:11 -04:00
633f1e2380 fix: reseed script now skips -sm and -md variant files
Previously all .webp files were indexed including thumbnails and medium
variants, causing each photo to appear three times in the gallery.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:48:06 -04:00
2723a6d954 feat: replace text watermark with BPB logo SVG overlay on gallery uploads
Loads bpb-watermark.svg at startup, converts black fills to semi-transparent
white (35% opacity), and composites it centered at 45% image width over every
uploaded photo. Removes the old diagonal "BEACH PARTY BALLOONS" text overlay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:39:03 -04:00
92cf44e5f5 fix: resolve gallery CORS failure and simplify API routing
- gallery backend: replace origin whitelist with wildcard CORS — NPMplus
  was stripping the Allow-Origin header; wildcard passes through reliably
  and is appropriate for a public photo gallery
- gallery.js: hardcode photobackend.beachpartyballoons.com as the API base
  (NPMplus already routes this subdomain) and remove dead port fallbacks
- nginx.conf: add /photos and /uploads proxy routes to gallery-backend
  (kept for direct-nginx access; NPMplus handles external traffic)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:53:13 -04:00
4a135a7919 fix: route gallery API through nginx, send Square receipts, unblock order completion
- 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>
2026-05-20 14:32:00 -04:00
bb878c2a8a fix: apply 6.35% CT Sales Tax to all order line items
Tax was $0 on first production order — catalog items don't have tax
configured in Square Dashboard. Apply it programmatically via an
ad-hoc LINE_ITEM scoped tax on every line item. Delivery remains
untaxed (service charge taxable: false).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:46:36 -04:00
5777788127 fix: point all Plausible scripts to self-hosted metrics instance
Replace plausible.io with metrics.beachpartyballoons.com across all
main-site pages and estore layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:15:41 -04:00
8799892341 fix: add Plausible window initializer to estore layout
Matches the pattern used on all main-site pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:40:54 -04:00
973808088e revert: restore product image background to white
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:34:24 -04:00
f45a1f807f fix: make product image rounded corners visible
border-radius was already set but invisible (white image on white background).
Changing the image background to a warm off-white makes the 12px rounded
corners show against the surrounding area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:07:12 -04:00
e8240e383a fix: increase Square card form height to show postal code field
89px only fit two rows; postal code (third row) was clipped by the container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 13:24:23 -04:00
bbf08e4267 fix: hide subtotal line when it equals the total (no delivery/tax)
Subtotal was always shown alongside Total even for pickup orders with
no additional charges, making both lines identical. Now the breakdown
(Subtotal / Delivery / Tax) only appears when there are fees beyond
the item total. Applies to both customer and store alert emails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 13:16:33 -04:00
8e283a4dff fix: update Green Tea hex to #b2ddc3, remove Pastel Magenta
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 10:50:16 -04:00
5643153a05 feat: add Magenta (#a01357) to Pinks & Reds color family
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 10:41:50 -04:00
07ae012aa3 feat: add Green Tea (#8a9f7f) to Greens color family
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 10:39:34 -04:00
ed5db69a90 fix: replace placeholder phone number in privacy policy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:44:17 -04:00
e2af78ff55 fix: nginx waits for estore healthcheck before starting
Nginx resolves upstream hostnames at boot time; if estore isn't
registered in Docker DNS yet it crashes in a restart loop.
Using service_healthy lets nginx wait until the Next.js app
passes its healthcheck before nginx attempts to start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:36:04 -04:00
57cc5840b9 feat: obfuscate email with click-to-reveal across all pages
Replace all bare info@ occurrences with a click-to-reveal pattern:
- New EmailLink React component (base64 decode on click, never in DOM pre-click)
- privacy, terms, refund pages use EmailLink
- contact/index.html uses a vanilla JS button with the same pattern
- PaymentForm mailto builder uses atob() to keep email out of source literals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:23:50 -04:00
3330c47af2 fix: secure admin API endpoints with Bearer token auth
- main-site/server.js: add requireAuth middleware to POST /api/update-status
- gallery-backend/routes/photos.js: add requireAuth to upload, delete, and update routes
- admin/admin.js: send Authorization: Bearer header on all mutating requests (fetch + XHR upload); handle 401 on update-status and photo save
- docker-compose.yml: pass ADMIN_PASSWORD to gallery-backend; remove MongoDB public port mapping (27017:27017)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 08:30:58 -04:00
c40db43c04 fix: replace literal \u2014 escape with em dash in notes placeholder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 07:57:27 -04:00
f969e5d242 feat: configurable booking lead time in admin (default 48h)
Adds leadTimeHours to HoursConfig. Slot generation, calendar minDate,
and pickup disabled-date precomputation all read from the config.
Admin hours page has a new input to adjust it without a redeploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 07:48:13 -04:00
134705792c fix: pass OSRM_URL from root .env into estore container
env_file only reads estore/.env; the root .env value wasn't reaching
the container. Wiring it through compose environment: fixes this.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 16:40:53 -04:00
c240ec4ce6 fix: disable Next.js fetch cache for OSRM requests
next:{revalidate:86400} was caching the OSRM URL in Next.js's on-disk
data cache, so old localhost:5002 requests replayed even after the env
var was updated to osrm:5000. Drive-time lookups need live responses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 16:19:50 -04:00
5bebd51ac4 fix: log OSRM failure reason and URL instead of silent fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 15:15:40 -04:00
175305a28f fix: calendar event UTC times and slot query range
CalDAV DTSTART/DTEND now use UTC (Z-suffix) instead of TZID local
time. Without a VTIMEZONE component, some CalDAV servers strip the
TZID on return, causing ical.js to read the times as UTC — shifting
every event 4 hours early and letting taken slots appear free.

Slot query range changed from ±6h around UTC midnight (36-hour window)
to 3AM–6AM UTC the next day (~27h) which covers the full ET business
day without pulling in afternoon events from the previous day.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 14:52:06 -04:00
bb6c8a03a7 fix: calendar newlines, admin delivery window setting
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>
2026-05-05 10:57:37 -04:00
ffd07e35bd fix: vinyl order attribution and 1-hour customer delivery window
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>
2026-05-05 10:51:18 -04:00
2be379a029 fix: delivery slot required; redirect user back if missing; email fallback on error
- Revert ASAP fallback — SCHEDULED is always correct; server validates before Square
- validateAndContinue now catches a cleared delivery/pickup slot and redirects
  back to the delivery step with an inline error message rather than letting
  the order reach Square with a missing deliver_at
- PaymentForm onError prop: 400-level checkout errors call onError so CartDrawer
  can navigate the user to the right step to fix the problem
- On any payment error, show an "email us your order details" mailto link that
  pre-fills name, phone, items, fulfillment, and total so we can issue an invoice

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 10:47:44 -04:00
bc0540d36a fix: delivery order failing with MISSING_REQUIRED_PARAMETER from Square
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>
2026-05-05 10:42:25 -04:00