Files
homaroma/apps/spa/src/pages/TrackOrderPage.tsx
T

170 lines
7.1 KiB
TypeScript

import { useState, useEffect } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import { API_BASE, type Category } from '../types'
import { Search, MapPin, Package, Loader2, ChevronRight } from 'lucide-react'
export default function TrackOrderPage() {
const navigate = useNavigate()
const [categories, setCategories] = useState<Category[]>([])
const [localOrders, setLocalOrders] = useState<number[]>([])
const [orderId, setOrderId] = useState('')
const [phone, setPhone] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
fetch(`${API_BASE}/categories`)
.then(res => res.json())
.then(setCategories)
.catch(console.error)
// Load local orders
const storedStr = localStorage.getItem('homaroma_local_orders')
if (storedStr) {
setLocalOrders(JSON.parse(storedStr))
}
}, [])
const handleTrackForm = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError(null)
try {
const res = await fetch(`${API_BASE}/orders/track`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
orderId: parseInt(orderId.replace(/[^0-9]/g, ''), 10) || 0,
phone
})
})
const data = await res.json()
if (res.ok && data.success) {
// Automatically save to local history so they don't have to type it again
if (!localOrders.includes(data.orderId)) {
const newOrders = [data.orderId, ...localOrders]
setLocalOrders(newOrders)
localStorage.setItem('homaroma_local_orders', JSON.stringify(newOrders))
}
navigate(`/order/${data.orderId}`)
} else {
setError(data.error || 'Failed to authenticate order details.')
}
} catch (err) {
console.error(err)
setError('An error occurred. Please check your internet connection.')
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex flex-col">
<Navbar categories={categories} />
<main className="flex-1 max-w-4xl w-full mx-auto px-6 flex flex-col justify-center pt-32 pb-24 space-y-12 animate-fade-in">
{/* Header */}
<div className="text-center space-y-4">
<div className="w-16 h-16 bg-cyan-glow/10 rounded-full flex items-center justify-center mx-auto border border-cyan-glow/20">
<MapPin className="w-8 h-8 text-cyan-glow" />
</div>
<h1 className="text-4xl font-bold tracking-tight">Track Your Status</h1>
<p className="text-slate-400">Enter your order details below to check shipping status or upload payment slips.</p>
</div>
<div className="grid md:grid-cols-2 gap-12">
{/* Tracking Form */}
<div className="bg-night-900/50 p-8 rounded-2xl border border-white/5 space-y-6 self-start">
<h2 className="text-lg font-bold uppercase tracking-widest border-b border-white/10 pb-4 flex items-center gap-2 text-cyan-glow">
<Search className="w-5 h-5" /> Manual Search
</h2>
<form onSubmit={handleTrackForm} className="space-y-5">
{error && (
<div className="p-4 bg-rose-500/10 border border-rose-500/20 text-rose-400 rounded-lg text-sm text-center">
{error}
</div>
)}
<div className="space-y-2">
<label className="text-xs uppercase font-bold tracking-widest text-slate-500">Order Number (ID)</label>
<input
type="text"
required
value={orderId}
onChange={e => setOrderId(e.target.value)}
className="w-full bg-night-950 border border-white/10 rounded-lg p-3.5 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all placeholder:text-slate-600"
placeholder="e.g. 1024"
/>
</div>
<div className="space-y-2">
<label className="text-xs uppercase font-bold tracking-widest text-slate-500">Phone Number</label>
<input
type="tel"
required
value={phone}
onChange={e => setPhone(e.target.value)}
className="w-full bg-night-950 border border-white/10 rounded-lg p-3.5 text-white focus:border-cyan-glow focus:ring-1 focus:ring-cyan-glow outline-none transition-all placeholder:text-slate-600"
placeholder="The phone number used during checkout"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-4 bg-white text-night-900 font-bold rounded-lg tracking-widest uppercase hover:bg-cyan-glow hover:shadow-[0_0_20px_rgba(102,252,241,0.3)] transition-all duration-500 disabled:opacity-50 flex items-center justify-center gap-2"
>
{loading ? <Loader2 className="w-5 h-5 animate-spin" /> : 'Find Order'}
</button>
</form>
</div>
{/* Local Order History */}
<div className="space-y-6">
<h2 className="text-lg font-bold uppercase tracking-widest border-b border-white/10 pb-4 flex items-center gap-2 text-white">
<Package className="w-5 h-5 text-slate-400" /> Recent Device Orders
</h2>
{localOrders.length === 0 ? (
<div className="text-center py-12 bg-white/5 rounded-2xl border border-dashed border-white/10 space-y-3">
<p className="text-slate-500 text-sm">No recent orders found on this device.</p>
<p className="text-[10px] uppercase text-slate-600 tracking-widest">Use manual search if you ordered elsewhere</p>
</div>
) : (
<div className="space-y-3">
{localOrders.map((id) => (
<Link
key={id}
to={`/order/${id}`}
className="flex justify-between items-center p-5 bg-white/5 rounded-xl border border-white/5 hover:bg-white/10 hover:border-cyan-glow/50 transition-all group"
>
<div>
<p className="text-xs text-slate-500 uppercase tracking-widest mb-1">Order</p>
<p className="text-xl font-bold font-mono text-cyan-glow">#{id.toString().padStart(4, '0')}</p>
</div>
<div className="w-10 h-10 rounded-full bg-night-950 flex items-center justify-center group-hover:bg-cyan-glow transition-colors">
<ChevronRight className="w-5 h-5 text-slate-500 group-hover:text-night-900" />
</div>
</Link>
))}
</div>
)}
</div>
</div>
</main>
<Footer categories={categories} />
</div>
)
}