- 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>
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { BASE } from '@/lib/basepath'
|
|
import type { EmailLineItem } from '@/lib/notify'
|
|
|
|
interface Props {
|
|
address: string
|
|
defaultName: string
|
|
defaultPhone: string
|
|
defaultEmail: string
|
|
lineItems: EmailLineItem[]
|
|
}
|
|
|
|
export default function BookingRequestPanel({ address, defaultName, defaultPhone, defaultEmail, lineItems }: Props) {
|
|
const [name, setName] = useState(defaultName)
|
|
const [phone, setPhone] = useState(defaultPhone)
|
|
const [email, setEmail] = useState(defaultEmail)
|
|
const [preferredTime, setPreferredTime] = useState('')
|
|
const [submitting, setSubmitting] = useState(false)
|
|
const [success, setSuccess] = useState(false)
|
|
const [error, setError] = useState('')
|
|
|
|
const handleSubmit = async () => {
|
|
setError('')
|
|
setSubmitting(true)
|
|
try {
|
|
const res = await fetch(`${BASE}/api/booking-request`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ customerName: name, customerPhone: phone, customerEmail: email, preferredTime, address, lineItems }),
|
|
})
|
|
const data = await res.json()
|
|
if (!res.ok) { setError(data.error ?? 'Something went wrong.'); return }
|
|
setSuccess(true)
|
|
} catch {
|
|
setError('Network error — please try again.')
|
|
} finally {
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
|
|
if (success) {
|
|
return (
|
|
<div style={{ marginTop: '0.75rem', padding: '0.75rem', background: '#f0f9fa', border: '1px solid #b2e0e4', borderRadius: '8px', fontSize: '0.82rem' }}>
|
|
<strong style={{ color: '#0d6e75' }}>Request sent!</strong>
|
|
<p style={{ marginTop: '4px', color: '#444' }}>
|
|
We’ll reach out to confirm your delivery time. Check your email for a copy of your request.
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div style={{ marginTop: '0.75rem', padding: '0.75rem', background: '#fff8e1', border: '1px solid #f6c000', borderRadius: '8px' }}>
|
|
<p style={{ fontSize: '0.82rem', fontWeight: 'bold', marginBottom: '0.5rem', color: '#5a4000' }}>
|
|
Send us your order & we’ll confirm a time
|
|
</p>
|
|
|
|
<div style={{ display: 'flex', gap: '6px', marginBottom: '0.4rem' }}>
|
|
<input
|
|
className="input is-small"
|
|
placeholder="Your name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
<input
|
|
className="input is-small"
|
|
placeholder="Phone"
|
|
type="tel"
|
|
value={phone}
|
|
onChange={(e) => setPhone(e.target.value)}
|
|
style={{ flex: 1 }}
|
|
/>
|
|
</div>
|
|
<input
|
|
className="input is-small"
|
|
placeholder="Email address"
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
style={{ marginBottom: '0.4rem', width: '100%' }}
|
|
/>
|
|
<input
|
|
className="input is-small"
|
|
placeholder="Preferred delivery date & time (e.g. Saturday June 14, afternoon)"
|
|
value={preferredTime}
|
|
onChange={(e) => setPreferredTime(e.target.value)}
|
|
style={{ marginBottom: '0.5rem', width: '100%' }}
|
|
/>
|
|
|
|
{error && (
|
|
<p style={{ fontSize: '0.75rem', color: '#cc3333', marginBottom: '0.4rem' }}>{error}</p>
|
|
)}
|
|
|
|
<button
|
|
className={`button is-warning is-small is-fullwidth${submitting ? ' is-loading' : ''}`}
|
|
disabled={submitting || !name.trim() || !phone.trim() || !email.trim() || !preferredTime.trim()}
|
|
onClick={handleSubmit}
|
|
style={{ fontWeight: 'bold' }}
|
|
>
|
|
Send booking request
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|