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>
This commit is contained in:
parent
01c908e919
commit
6705293e50
@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -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' }}
|
||||
|
||||
@ -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 (
|
||||
<button
|
||||
@ -24,7 +26,7 @@ export default function ScrollToTop() {
|
||||
position: 'fixed',
|
||||
bottom: '12px',
|
||||
right: '10px',
|
||||
zIndex: 99,
|
||||
zIndex: 98,
|
||||
border: '1px solid #363636',
|
||||
outline: 'none',
|
||||
background: '#94d601',
|
||||
|
||||
@ -368,3 +368,21 @@ export async function sendNewOrderAlert(params: {
|
||||
text: lines.join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
export async function sendAdminErrorAlert(params: {
|
||||
subject: string
|
||||
message: string
|
||||
context?: Record<string, unknown>
|
||||
}): Promise<void> {
|
||||
const to = 'admin@beachpartyballoons.com'
|
||||
const lines = [
|
||||
params.message,
|
||||
'',
|
||||
...(params.context
|
||||
? Object.entries(params.context).map(([k, v]) => `${k}: ${typeof v === 'object' ? JSON.stringify(v) : String(v)}`)
|
||||
: []),
|
||||
'',
|
||||
`Time: ${new Date().toISOString()}`,
|
||||
]
|
||||
await send({ to, subject: `⚠️ ${params.subject}`, text: lines.join('\n') })
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@
|
||||
"colors": [
|
||||
{
|
||||
"name": "Chrome Rose Gold",
|
||||
"hex": "#B76E79",
|
||||
"hex": "#C17F87",
|
||||
"metallic": true,
|
||||
"chromeType": "rosegold",
|
||||
"image": "images/chrome-rosegold.webp"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user