/
opt
/
canhelp
/
apps
/
web
/
src
/
app
/
payment
/
/opt/canhelp/apps/web/src/app/payment
mkdir
upload
Name
Size
Mode
Actions
page.tsx
5298
0644
edit
dl
rm
Edit:
/opt/canhelp/apps/web/src/app/payment/page.tsx
(5298B)
'use client' import { useEffect, useState } from 'react' import { useSearchParams } from 'next/navigation' import Link from 'next/link' import type { ReactNode } from 'react' import { useLocale } from '@/context/locale' import * as api from '@/lib/api' // Status is determined either by: // 1. ?status=cancelled (from our cancel redirect) // 2. ?ref=ORDER_REF → we poll /payments/history to check the order status export default function PaymentPage() { const searchParams = useSearchParams() const { t } = useLocale() const statusParam = searchParams.get('status') const ref = searchParams.get('ref') const [status, setStatus] = useState<'pending' | 'success' | 'failed' | 'cancelled' | 'loading'>( statusParam === 'cancelled' ? 'cancelled' : ref ? 'loading' : 'pending', ) useEffect(() => { if (statusParam === 'cancelled') { setStatus('cancelled') return } if (!ref) return // Poll payment history to find the order status let attempts = 0 const MAX = 12 // ~24 seconds total const INTERVAL = 2000 const check = async () => { attempts++ try { const history = await api.getPaymentHistory() const payment = history.find((p) => p.orderRef === ref) if (payment && payment.status !== 'pending') { setStatus(payment.status as any) return } } catch {} if (attempts < MAX) { setTimeout(check, INTERVAL) } else { setStatus('pending') // timeout - show pending message } } setTimeout(check, INTERVAL) }, [ref, statusParam]) const config: Record<string, { icon: ReactNode; title: string; desc: string; color: string; bg: string; border: string }> = { success: { icon: ( <svg className="w-16 h-16 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> ), title: t('payment.success.title'), desc: t('payment.success.desc'), color: 'text-green-700', bg: 'bg-green-50', border: 'border-green-200', }, cancelled: { icon: ( <svg className="w-16 h-16 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" /> </svg> ), title: t('payment.cancelled.title'), desc: t('payment.cancelled.desc'), color: 'text-yellow-700', bg: 'bg-yellow-50', border: 'border-yellow-200', }, failed: { icon: ( <svg className="w-16 h-16 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" /> </svg> ), title: t('payment.cancelled.title'), desc: t('payment.cancelled.desc'), color: 'text-red-700', bg: 'bg-red-50', border: 'border-red-200', }, pending: { icon: ( <svg className="w-16 h-16 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}> <path strokeLinecap="round" strokeLinejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> ), title: t('payment.pending.title'), desc: t('payment.pending.desc'), color: 'text-green-700', bg: 'bg-green-50', border: 'border-green-200', }, loading: { icon: ( <svg className="w-16 h-16 text-gray-400 animate-spin" fill="none" viewBox="0 0 24 24"> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z" /> </svg> ), title: t('payment.pending.title'), desc: t('payment.pending.desc'), color: 'text-gray-700', bg: 'bg-gray-50', border: 'border-gray-200', }, } const current = config[status] ?? config.pending return ( <div className="min-h-screen bg-gray-50 flex items-center justify-center p-4"> <div className={`bg-white rounded-2xl border ${current.border} shadow-sm p-10 max-w-sm w-full text-center`}> <div className="flex justify-center mb-5">{current.icon}</div> <h1 className={`text-2xl font-bold mb-2 ${current.color}`}>{current.title}</h1> <p className="text-gray-500 text-sm mb-8">{current.desc}</p> <Link href="/profile" className="inline-flex items-center gap-2 px-6 py-3 bg-green-600 hover:bg-green-700 text-white font-semibold rounded-xl transition-colors text-sm" > <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <path strokeLinecap="round" strokeLinejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> </svg> {t('payment.back_to_profile')} </Link> </div> </div> ) }
Save
cmd:
run