// 432 Bleu — Box Office · ticket drawer (tier select, contact info, real checkout) const { useState, useEffect } = React; function TicketDrawer({ show, accent, onClose }) { const [selectedTierId, setSelectedTierId] = useState(null); const [pwycAmount, setPwycAmount] = useState(''); const [name, setName] = useState(''); const [email, setEmail] = useState(''); const [submitting, setSubmitting] = useState(false); const [cryptoSubmitting, setCryptoSubmitting] = useState(false); const [error, setError] = useState(''); const [done, setDone] = useState(false); const [attempted, setAttempted] = useState(false); useEffect(() => { setSelectedTierId(null); setPwycAmount(''); setName(''); setEmail(''); setSubmitting(false); setCryptoSubmitting(false); setError(''); setDone(false); setAttempted(false); }, [show && show.id]); // lock scroll while open useEffect(() => { if (show) { document.body.style.overflow = 'hidden'; } return () => { document.body.style.overflow = ''; }; }, [show]); const open = !!show; const selectedTier = show && show.tiers.find(t => t.id === selectedTierId); const isPwyc = !!(selectedTier && selectedTier.name === 'PWYC'); const amountCents = !selectedTier ? 0 : isPwyc ? Math.round((parseFloat(pwycAmount) || 0) * 100) : selectedTier.priceCents; const canSubmit = !!selectedTier && name.trim() !== '' && email.includes('@') && !submitting && !(isPwyc && amountCents < 100); const canSubmitCrypto = canSubmit && selectedTier && selectedTier.name !== 'GA' && !cryptoSubmitting; const allSoldOut = !!show && show.tiers.length > 0 && show.tiers.every(t => !t.available); const nameInvalid = attempted && name.trim() === ''; const emailInvalid = attempted && !email.includes('@'); async function handleConfirm() { if (!canSubmit) { setAttempted(true); return; } setSubmitting(true); setError(''); try { if (amountCents === 0 && !isPwyc) { const res = await fetch('/api/tickets/free', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ event_id: show.id, tier_id: selectedTier.id, email: email.trim(), name: name.trim(), }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Something went wrong. Please try again.'); } setDone(true); setSubmitting(false); } else { const res = await fetch('/api/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ event_id: show.id, tier_id: selectedTier.id, email: email.trim(), name: name.trim(), amount_cents: amountCents, }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Something went wrong. Please try again.'); } const { checkout_url } = await res.json(); window.location.href = checkout_url; // leaving the SPA for Stripe } } catch (e) { setError(e.message || 'Something went wrong. Please try again.'); setSubmitting(false); } } async function handleCryptoConfirm() { if (!canSubmitCrypto) { setAttempted(true); return; } setCryptoSubmitting(true); setError(''); try { const res = await fetch('/api/checkout/crypto', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ event_id: show.id, tier_id: selectedTier.id, email: email.trim(), name: name.trim(), amount_cents: amountCents, }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Crypto checkout is unavailable right now.'); } const { checkout_url } = await res.json(); window.location.href = checkout_url; // leaving the SPA for NOWPayments } catch (e) { setError(e.message || 'Crypto checkout is unavailable right now.'); setCryptoSubmitting(false); } } return ( <> {/* scrim */}
{/* panel */} > ); } function Header({ show, accent, onClose }) { return (Tuned to {show.hz} Hz. All entries 21+.