247 lines
11 KiB
TypeScript
247 lines
11 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback } from 'react'
|
|
import { useAuthStore } from '@/lib/auth'
|
|
|
|
type Step = 'details' | 'share'
|
|
|
|
interface RequestFlowProps {
|
|
onCreated?: (link: string) => void
|
|
}
|
|
|
|
const CURRENCIES = [
|
|
{ code: 'USD', symbol: '$' },
|
|
{ code: 'EUR', symbol: '\u20ac' },
|
|
{ code: 'GBP', symbol: '\u00a3' },
|
|
]
|
|
|
|
export default function RequestFlow({ onCreated }: RequestFlowProps) {
|
|
const { isAuthenticated, username, login } = useAuthStore()
|
|
|
|
const [step, setStep] = useState<Step>('details')
|
|
const [amount, setAmount] = useState('')
|
|
const [currency, setCurrency] = useState('USD')
|
|
const [description, setDescription] = useState('')
|
|
const [recipientEmail, setRecipientEmail] = useState('')
|
|
const [paymentLink, setPaymentLink] = useState('')
|
|
const [copied, setCopied] = useState(false)
|
|
|
|
const numericAmount = parseFloat(amount) || 0
|
|
const selectedCurrency = CURRENCIES.find(c => c.code === currency)
|
|
|
|
const handleCreate = useCallback(() => {
|
|
// Build the payment link with prefilled params
|
|
const params = new URLSearchParams()
|
|
if (numericAmount > 0) params.set('amount', numericAmount.toString())
|
|
if (currency !== 'USD') params.set('currency', currency)
|
|
if (recipientEmail) params.set('to', recipientEmail)
|
|
if (username) params.set('name', username)
|
|
if (description) params.set('note', description)
|
|
|
|
const baseUrl = typeof window !== 'undefined' ? window.location.origin : 'https://rfunds.online'
|
|
const link = `${baseUrl}/pay?${params.toString()}`
|
|
setPaymentLink(link)
|
|
setStep('share')
|
|
onCreated?.(link)
|
|
}, [numericAmount, currency, recipientEmail, username, description, onCreated])
|
|
|
|
const handleCopy = useCallback(() => {
|
|
navigator.clipboard.writeText(paymentLink)
|
|
setCopied(true)
|
|
setTimeout(() => setCopied(false), 2000)
|
|
}, [paymentLink])
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-50 flex items-start justify-center pt-8 pb-16 px-4">
|
|
<div className="w-full max-w-[480px]">
|
|
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-6">
|
|
<h1 className="text-lg font-semibold text-slate-900">
|
|
{step === 'share' ? 'Share payment link' : 'Request money'}
|
|
</h1>
|
|
{isAuthenticated ? (
|
|
<div className="flex items-center gap-2 px-3 py-1.5 bg-white rounded-full border border-slate-200 text-sm text-slate-600">
|
|
<span className="w-2 h-2 rounded-full bg-emerald-500" />
|
|
{username}
|
|
</div>
|
|
) : (
|
|
<button onClick={login} className="px-3 py-1.5 bg-emerald-600 text-white rounded-full text-sm font-medium hover:bg-emerald-500 transition-colors">
|
|
Sign in
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* ── Step 1: Details ── */}
|
|
{step === 'details' && (
|
|
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
|
|
<div className="p-6">
|
|
<label className="text-sm font-medium text-slate-500 block mb-4">How much do you want to receive?</label>
|
|
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<input
|
|
type="text"
|
|
inputMode="decimal"
|
|
value={amount}
|
|
onChange={e => {
|
|
const v = e.target.value.replace(/[^0-9.]/g, '')
|
|
if (v.split('.').length <= 2) setAmount(v)
|
|
}}
|
|
placeholder="0.00"
|
|
className="flex-1 text-4xl font-light text-slate-900 outline-none bg-transparent placeholder:text-slate-300"
|
|
autoFocus
|
|
/>
|
|
<select
|
|
value={currency}
|
|
onChange={e => setCurrency(e.target.value)}
|
|
className="text-lg font-medium text-slate-700 bg-slate-100 px-3 py-2 rounded-xl border-0 outline-none cursor-pointer hover:bg-slate-200 transition-colors appearance-none"
|
|
>
|
|
{CURRENCIES.map(c => (
|
|
<option key={c.code} value={c.code}>{c.code}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 block mb-1.5">Your email (to receive notification)</label>
|
|
<input
|
|
type="email"
|
|
value={recipientEmail}
|
|
onChange={e => setRecipientEmail(e.target.value)}
|
|
placeholder="you@example.com"
|
|
className="w-full px-4 py-3 border border-slate-200 rounded-xl text-sm text-slate-900 outline-none focus:border-emerald-400 focus:ring-2 focus:ring-emerald-100 transition-all"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-medium text-slate-600 block mb-1.5">What's it for? (shown to payer)</label>
|
|
<input
|
|
type="text"
|
|
value={description}
|
|
onChange={e => setDescription(e.target.value)}
|
|
placeholder="e.g. Dinner split, Invoice #123"
|
|
className="w-full px-4 py-3 border border-slate-200 rounded-xl text-sm text-slate-900 outline-none focus:border-emerald-400 focus:ring-2 focus:ring-emerald-100 transition-all"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t border-slate-100 p-4">
|
|
<button
|
|
onClick={handleCreate}
|
|
disabled={numericAmount < 1}
|
|
className="w-full py-3.5 bg-emerald-600 text-white rounded-xl text-sm font-semibold hover:bg-emerald-500 disabled:bg-slate-200 disabled:text-slate-400 transition-colors"
|
|
>
|
|
Create payment link
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── Step 2: Share ── */}
|
|
{step === 'share' && (
|
|
<div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
|
|
<div className="p-6 text-center">
|
|
<div className="w-16 h-16 rounded-full bg-violet-100 flex items-center justify-center mx-auto mb-4">
|
|
<svg className="w-8 h-8 text-violet-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.86-4.318a4.5 4.5 0 00-7.093-1.657l-4.5 4.5a4.5 4.5 0 006.364 6.364l1.757-1.757" />
|
|
</svg>
|
|
</div>
|
|
|
|
<h2 className="text-lg font-semibold text-slate-900 mb-1">Payment link ready</h2>
|
|
<p className="text-sm text-slate-500 mb-6">
|
|
Share this link to receive {selectedCurrency?.symbol}{numericAmount.toFixed(2)} {currency}
|
|
</p>
|
|
|
|
{/* Link display */}
|
|
<div className="bg-slate-50 rounded-xl p-4 mb-4">
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="text"
|
|
readOnly
|
|
value={paymentLink}
|
|
className="flex-1 text-xs text-slate-600 bg-transparent outline-none font-mono truncate"
|
|
/>
|
|
<button
|
|
onClick={handleCopy}
|
|
className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors flex-shrink-0 ${
|
|
copied
|
|
? 'bg-emerald-100 text-emerald-700'
|
|
: 'bg-emerald-600 text-white hover:bg-emerald-500'
|
|
}`}
|
|
>
|
|
{copied ? 'Copied!' : 'Copy'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Summary */}
|
|
<div className="bg-slate-50 rounded-xl p-4 text-left space-y-2 mb-4">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Amount</span>
|
|
<span className="font-medium text-slate-900">{selectedCurrency?.symbol}{numericAmount.toFixed(2)} {currency}</span>
|
|
</div>
|
|
{description && (
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Note</span>
|
|
<span className="text-slate-700">{description}</span>
|
|
</div>
|
|
)}
|
|
{recipientEmail && (
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-slate-500">Notify</span>
|
|
<span className="text-slate-700">{recipientEmail}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Share options */}
|
|
<div className="flex gap-2">
|
|
<a
|
|
href={`mailto:?subject=${encodeURIComponent(`Payment request: ${selectedCurrency?.symbol}${numericAmount.toFixed(2)}`)}&body=${encodeURIComponent(`Hi,\n\nI'm requesting a payment of ${selectedCurrency?.symbol}${numericAmount.toFixed(2)} ${currency}${description ? ` for: ${description}` : ''}.\n\nYou can pay here: ${paymentLink}\n\nThanks!`)}`}
|
|
className="flex-1 py-2.5 bg-slate-100 text-slate-700 rounded-xl text-xs font-medium hover:bg-slate-200 transition-colors text-center"
|
|
>
|
|
Email link
|
|
</a>
|
|
<button
|
|
onClick={() => {
|
|
if (navigator.share) {
|
|
navigator.share({
|
|
title: `Payment request: ${selectedCurrency?.symbol}${numericAmount.toFixed(2)}`,
|
|
text: description || `Payment of ${selectedCurrency?.symbol}${numericAmount.toFixed(2)} ${currency}`,
|
|
url: paymentLink,
|
|
}).catch(() => {})
|
|
} else {
|
|
handleCopy()
|
|
}
|
|
}}
|
|
className="flex-1 py-2.5 bg-slate-100 text-slate-700 rounded-xl text-xs font-medium hover:bg-slate-200 transition-colors"
|
|
>
|
|
Share
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t border-slate-100 p-4">
|
|
<button
|
|
onClick={() => { setStep('details'); setAmount(''); setDescription(''); setPaymentLink('') }}
|
|
className="w-full py-3 bg-white border border-slate-200 text-slate-700 rounded-xl text-sm font-medium hover:bg-slate-50 transition-colors"
|
|
>
|
|
Create another request
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Footer */}
|
|
<div className="mt-6 text-center">
|
|
<p className="text-xs text-slate-400">
|
|
Powered by <span className="font-medium">rSpace</span> Payment Infrastructure
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|