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 (