Files
cursa-corbins-2026/src/app/success/page.tsx
T
admin 60f6e9d2e1 Initial commit – Cursa de la Cirera 2026 e-commerce
Next.js 14 + Prisma + Stripe + Google Sheets + Nodemailer
2026-06-17 12:02:28 +02:00

221 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
import { useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
import Image from 'next/image'
import Link from 'next/link'
import { CheckCircle, Package, Truck, CalendarCheck, ArrowLeft } from 'lucide-react'
const PRODUCT_NAMES: Record<string, string> = {
samarreta: 'Samarreta Cursa de la Cirera 2026',
mitjons: 'Mitjons Cursa de la Cirera 2026',
pack: 'Pack Samarreta + Mitjons',
}
interface OrderData {
orderNumber: string
nom: string
cognoms: string
email: string
product: string
sizeTshirt?: string
sizeSocks?: string
shipping: string
totalAmount: number
}
function SuccessContent() {
const params = useSearchParams()
const sessionId = params.get('session_id')
const [order, setOrder] = useState<OrderData | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
if (!sessionId) { setLoading(false); return }
// Poll a bit to give webhook time to process
let attempts = 0
const maxAttempts = 8
async function fetchOrder() {
attempts++
try {
const res = await fetch(`/api/orders/by-session?session_id=${sessionId}`)
if (res.ok) {
const data = await res.json()
if (data.order) {
setOrder(data.order)
setLoading(false)
return
}
}
} catch {
// ignore, will retry
}
if (attempts < maxAttempts) {
setTimeout(fetchOrder, 1500)
} else {
setLoading(false)
}
}
fetchOrder()
}, [sessionId])
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 border-4 border-teal border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<p className="text-slate-400">Confirmant el teu pagament...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen flex flex-col">
{/* Header */}
<header className="border-b border-dark-border bg-dark/90 py-3">
<div className="max-w-6xl mx-auto px-4 sm:px-6 flex items-center gap-3">
<Image
src="/assets/LOGO BLOD BROS SPORT GOTA RODONA ALTA RES.jpg"
alt="Blood Bros Sport"
width={40}
height={40}
className="rounded-full"
/>
<span className="text-white font-bold text-sm">Blood Bros Sport</span>
</div>
</header>
<main className="flex-1 flex items-center justify-center py-12 px-4">
<div className="max-w-lg w-full text-center">
{/* Success icon */}
<div className="relative inline-flex mb-6">
<div
className="absolute inset-0 rounded-full blur-2xl opacity-30 scale-150"
style={{ background: 'radial-gradient(circle, #00C8DC, transparent)' }}
/>
<CheckCircle size={80} className="relative text-teal" />
</div>
<h1 className="text-4xl sm:text-5xl font-black text-white mb-3">
Comanda<br />
<span className="text-teal">confirmada!</span>
</h1>
<p className="text-slate-400 text-base mb-8">
{order
? `Gràcies ${order.nom}! Hem rebut la teva comanda i en breu rebràs la confirmació per email a ${order.email}.`
: 'El teu pagament ha estat processat correctament. Rebràs la confirmació per email en breu.'}
</p>
{order && (
<div className="bg-dark-card border border-dark-border rounded-2xl overflow-hidden mb-8 text-left">
{/* Order number */}
<div className="bg-teal/10 border-b border-dark-border p-5 text-center">
<p className="text-slate-500 text-xs uppercase tracking-widest mb-1">Número de comanda</p>
<p className="text-teal text-3xl font-black tracking-wider">{order.orderNumber}</p>
<p className="text-slate-600 text-xs mt-1">Guarda'l per a consultes futures</p>
</div>
{/* Order details */}
<div className="p-5 space-y-3">
<div className="flex justify-between text-sm">
<span className="text-slate-500">Producte</span>
<span className="text-white font-semibold">{PRODUCT_NAMES[order.product] ?? order.product}</span>
</div>
{(order.sizeTshirt || order.sizeSocks) && (
<div className="flex justify-between text-sm">
<span className="text-slate-500">Talles</span>
<span className="text-white font-semibold">
{[
order.sizeTshirt && `Samarreta ${order.sizeTshirt}`,
order.sizeSocks && `Mitjons ${order.sizeSocks}`,
].filter(Boolean).join(' · ')}
</span>
</div>
)}
<div className="flex justify-between text-sm">
<span className="text-slate-500">Lliurament</span>
<span className="text-white font-semibold">
{order.shipping === 'correos' ? 'Correos Express' : 'Recollida Corbins/Lleida'}
</span>
</div>
<div className="border-t border-dark-border pt-3 flex justify-between">
<span className="text-white font-black">TOTAL</span>
<span className="text-teal font-black text-xl">
{order.totalAmount.toFixed(2).replace('.', ',')}€
</span>
</div>
</div>
</div>
)}
{/* Timeline */}
<div className="flex flex-col gap-3 mb-8 text-left">
<div className="flex items-start gap-4 p-4 bg-dark-card border border-teal/30 rounded-xl">
<CheckCircle size={20} className="text-teal shrink-0 mt-0.5" />
<div>
<p className="text-white font-semibold text-sm">Pagament confirmat</p>
<p className="text-slate-500 text-xs">El teu pagament s'ha processat correctament via Stripe.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 bg-dark-card border border-dark-border rounded-xl">
<Package size={20} className="text-slate-500 shrink-0 mt-0.5" />
<div>
<p className="text-slate-400 font-semibold text-sm">Preparació de la comanda</p>
<p className="text-slate-600 text-xs">A partir del 25 de juny, quan es tanquin les comandes.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 bg-dark-card border border-dark-border rounded-xl">
<Truck size={20} className="text-slate-500 shrink-0 mt-0.5" />
<div>
<p className="text-slate-400 font-semibold text-sm">Enviament / Recollida</p>
<p className="text-slate-600 text-xs">Rebràs una notificació quan la teva comanda surti.</p>
</div>
</div>
<div className="flex items-start gap-4 p-4 bg-dark-card border border-dark-border rounded-xl">
<CalendarCheck size={20} className="text-slate-500 shrink-0 mt-0.5" />
<div>
<p className="text-slate-400 font-semibold text-sm">Lliurament previst: 29 Juny 3 Juliol 2026</p>
<p className="text-slate-600 text-xs">Amb temps per a la Cursa de la Cirera!</p>
</div>
</div>
</div>
<Link
href="/"
className="inline-flex items-center gap-2 text-teal hover:text-white transition-colors text-sm"
>
<ArrowLeft size={16} />
Tornar a la botiga
</Link>
</div>
</main>
<footer className="border-t border-dark-border py-4 text-center">
<p className="text-slate-700 text-xs">
© 2026 Blood Bros Sport · Cursa de la Cirera · Corbins, Lleida
</p>
</footer>
</div>
)
}
export default function SuccessPage() {
return (
<Suspense
fallback={
<div className="min-h-screen flex items-center justify-center">
<div className="w-12 h-12 border-4 border-teal border-t-transparent rounded-full animate-spin" />
</div>
}
>
<SuccessContent />
</Suspense>
)
}