170 lines
7.1 KiB
TypeScript
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>
|
|
)
|
|
}
|