From 6705293e500bd0f165ce4c047881ae65ee04d61d Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 17 Apr 2026 14:19:29 -0400 Subject: [PATCH] fix/feat: hex conflict, scroll-to-top, search all, admin error emails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- estore/src/app/api/checkout/route.ts | 24 ++++++++++++++++++++++ estore/src/components/FeaturedProducts.tsx | 2 +- estore/src/components/ScrollToTop.tsx | 10 +++++---- estore/src/lib/notify.ts | 18 ++++++++++++++++ main-site/color/colors.json | 2 +- 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/estore/src/app/api/checkout/route.ts b/estore/src/app/api/checkout/route.ts index 7579e97..d78054f 100644 --- a/estore/src/app/api/checkout/route.ts +++ b/estore/src/app/api/checkout/route.ts @@ -331,6 +331,16 @@ export async function POST(req: NextRequest) { console.error('[checkout] CRITICAL: calendar write failed — voiding pre-auth to avoid charge without booking:', { orderId: order.id, paymentId: payment.id, error: calendarWriteError, }) + void (async () => { + try { + const { sendAdminErrorAlert } = await import('@/lib/notify') + await sendAdminErrorAlert({ + subject: 'Calendar write failed — order not booked', + message: `Calendar write failed for order ${order.id}. Pre-auth ${payment.id} is being voided. Customer: ${customerName} (${customerEmail}).`, + context: { orderId: order.id, paymentId: payment.id, error: String(calendarWriteError) }, + }) + } catch { /* best effort */ } + })() try { await cancelSquarePayment(payment.id!) console.log('[checkout] Pre-auth voided successfully:', payment.id) @@ -447,6 +457,20 @@ export async function POST(req: NextRequest) { const userMessage = CARD_MESSAGES[code] ?? 'Something went wrong with your payment. Please try again or contact us for help.' + // Email admin for unexpected server errors (not card declines the customer can self-resolve) + if (!CARD_MESSAGES[code]) { + void (async () => { + try { + const { sendAdminErrorAlert } = await import('@/lib/notify') + await sendAdminErrorAlert({ + subject: 'Checkout error', + message: err instanceof Error ? err.message : String(err), + context: { code: code || '(none)', customerEmail, customerName }, + }) + } catch { /* best effort */ } + })() + } + return NextResponse.json({ error: userMessage }, { status: 500 }) } } diff --git a/estore/src/components/FeaturedProducts.tsx b/estore/src/components/FeaturedProducts.tsx index 9ea21fc..7c65d29 100644 --- a/estore/src/components/FeaturedProducts.tsx +++ b/estore/src/components/FeaturedProducts.tsx @@ -189,7 +189,7 @@ export default function FeaturedProducts() { placeholder="Search…" value={search} autoFocus - onChange={(e) => setSearch(e.target.value)} + onChange={(e) => { setSearch(e.target.value); if (e.target.value) setCategory('all') }} onBlur={() => { if (!search) setSearchOpen(false) }} onKeyDown={(e) => { if (e.key === 'Escape') { setSearch(''); setSearchOpen(false) } }} style={{ width: '160px' }} diff --git a/estore/src/components/ScrollToTop.tsx b/estore/src/components/ScrollToTop.tsx index 8440376..fba208f 100644 --- a/estore/src/components/ScrollToTop.tsx +++ b/estore/src/components/ScrollToTop.tsx @@ -1,20 +1,22 @@ 'use client' import { useEffect, useState } from 'react' +import { useCart } from '@/context/CartContext' export default function ScrollToTop() { - const [visible, setVisible] = useState(false) + const { drawerOpen } = useCart() + const [scrolled, setScrolled] = useState(false) useEffect(() => { const onScroll = () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0 - setVisible(scrollTop > 130) + setScrolled(scrollTop > 130) } window.addEventListener('scroll', onScroll, { passive: true }) return () => window.removeEventListener('scroll', onScroll) }, []) - if (!visible) return null + if (!scrolled || drawerOpen) return null return (