High impact:
- Cart items now show product thumbnail (52px) so customers can visually
confirm what they ordered
- Delivery quote auto-fetches 800ms after the address stops changing,
removing the manual "Check availability" step; also persists across
sessions and restores when the same address is loaded from localStorage
- Calendar error fallback now shows a clear explanation before the
booking request form
Medium impact:
- "Copy shareable cart link" button in cart footer encodes the current
cart as a ?cart= URL param; opening the link re-hydrates the cart from
the catalog so customers can share or continue on another device
- Order status page at /order/[orderId] shows state, fulfillment time,
and items; linked from the post-checkout success screen
- Delivery quote is saved to localStorage and restored automatically
when the same address is loaded in a new session
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Delete Hero, ReviewsSection, TrustedBrands components (never imported)
- Delete /api/admin/orders/[orderId]/complete route (never called; order
state transitions go through /status instead)
- Extract maxColorsFor() from ProductCard and CartDrawer into
src/lib/colors.ts to eliminate the duplicated implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Force explicit light-mode colors with inline styles so Bulma's dark
mode cannot override the closure banner text and background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- Unique title + meta description on every main-site page
- OpenGraph + Twitter card tags sitewide (hero image on homepage, logo elsewhere)
- LocalBusiness JSON-LD on homepage for Google rich results
- Custom 404 page on main-site (branded, links home + contact)
- Custom not-found page on estore
- Fix typo: "Delivery avalable" → "Delivery available"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- First and last name are now separate required fields
- Server combines them into a full name for emails
- Sends a push notification to NTFY_URL on new inquiry (fire-and-forget)
- NTFY_URL env var wired through docker-compose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>