feat(spa): add order history and automatic address book
This commit is contained in:
1 parent
15cb147c81
commit
ca31564ee7
3 files changed
+126
-31
No files matched your search
@@ -3,12 +3,24 @@ 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'
|
||||
import { Search, MapPin, Package, Loader2, ChevronRight, Clock } from 'lucide-react'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
|
||||
interface OrderHistoryItem {
|
||||
id: number
|
||||
status: string
|
||||
totalAmount: number
|
||||
createdAt: string
|
||||
items: any[]
|
||||
}
|
||||
|
||||
export default function TrackOrderPage() {
|
||||
const navigate = useNavigate()
|
||||
const { customer } = useAuthStore()
|
||||
const [categories, setCategories] = useState<Category[]>([])
|
||||
const [localOrders, setLocalOrders] = useState<number[]>([])
|
||||
const [historyOrders, setHistoryOrders] = useState<OrderHistoryItem[]>([])
|
||||
const [historyLoading, setHistoryLoading] = useState(false)
|
||||
|
||||
const [orderId, setOrderId] = useState('')
|
||||
const [phone, setPhone] = useState('')
|
||||
@@ -21,12 +33,23 @@ export default function TrackOrderPage() {
|
||||
.then(setCategories)
|
||||
.catch(console.error)
|
||||
|
||||
// Load local orders
|
||||
const storedStr = localStorage.getItem('homaroma_local_orders')
|
||||
if (storedStr) {
|
||||
setLocalOrders(JSON.parse(storedStr))
|
||||
if (customer) {
|
||||
setHistoryLoading(true)
|
||||
fetch(`${API_BASE}/orders/customer/${customer.id}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (Array.isArray(data)) setHistoryOrders(data)
|
||||
})
|
||||
.catch(console.error)
|
||||
.finally(() => setHistoryLoading(false))
|
||||
} else {
|
||||
// Load local orders if not logged in
|
||||
const storedStr = localStorage.getItem('homaroma_local_orders')
|
||||
if (storedStr) {
|
||||
setLocalOrders(JSON.parse(storedStr))
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [customer])
|
||||
|
||||
const handleTrackForm = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
@@ -127,35 +150,83 @@ export default function TrackOrderPage() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Local Order History */}
|
||||
{/* Local / Account Order History */}
|
||||
<div className="space-y-6">
|
||||
<h2 className="text-lg font-bold uppercase tracking-widest border-b border-spa-stone/10 pb-4 flex items-center gap-2 text-pop-black">
|
||||
<Package className="w-5 h-5 text-spa-stone/60" /> Recent Device Orders
|
||||
{customer ? (
|
||||
<><Clock className="w-5 h-5 text-spa-stone/60" /> My Order History</>
|
||||
) : (
|
||||
<><Package className="w-5 h-5 text-spa-stone/60" /> Recent Device Orders</>
|
||||
)}
|
||||
</h2>
|
||||
|
||||
{localOrders.length === 0 ? (
|
||||
<div className="text-center py-12 bg-spa-light rounded-2xl border border-dashed border-spa-stone/20 space-y-3">
|
||||
<p className="text-spa-stone text-sm font-semibold">No recent orders found on this device.</p>
|
||||
<p className="text-[10px] uppercase text-spa-stone/60 tracking-widest font-bold">Use manual search if you ordered elsewhere</p>
|
||||
</div>
|
||||
{customer ? (
|
||||
historyLoading ? (
|
||||
<div className="flex justify-center py-12"><Loader2 className="w-8 h-8 animate-spin text-spa-forest" /></div>
|
||||
) : historyOrders.length === 0 ? (
|
||||
<div className="text-center py-12 bg-spa-light rounded-2xl border border-dashed border-spa-stone/20 space-y-3">
|
||||
<p className="text-spa-stone text-sm font-semibold">No order history found.</p>
|
||||
<p className="text-[10px] uppercase text-spa-stone/60 tracking-widest font-bold">Start shopping to see your orders here</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{historyOrders.map((order) => (
|
||||
<Link
|
||||
key={order.id}
|
||||
to={`/order/${order.id}`}
|
||||
className="flex justify-between items-center p-5 bg-white rounded-xl border border-spa-stone/10 shadow-sm hover:border-spa-forest transition-all group"
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<p className="text-xs text-spa-stone/80 uppercase tracking-widest font-semibold">Order</p>
|
||||
<span className={`text-[10px] px-2 py-0.5 rounded-full font-bold uppercase tracking-widest ${
|
||||
order.status === 'PENDING_PAYMENT' ? 'bg-orange-100 text-orange-700' :
|
||||
order.status === 'PAID' ? 'bg-green-100 text-green-700' : 'bg-spa-light text-spa-stone'
|
||||
}`}>
|
||||
{order.status.replace('_', ' ')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xl font-bold font-mono text-spa-forest">#{order.id.toString().padStart(4, '0')}</p>
|
||||
<p className="text-xs text-spa-stone mt-1">{new Date(order.createdAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
<div className="text-right flex items-center gap-4">
|
||||
<div className="hidden sm:block">
|
||||
<p className="text-sm font-bold text-pop-black">฿{order.totalAmount.toLocaleString()}</p>
|
||||
<p className="text-[10px] text-spa-stone uppercase">{order.items.length} items</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-full bg-spa-light flex items-center justify-center group-hover:bg-spa-forest transition-colors shrink-0">
|
||||
<ChevronRight className="w-5 h-5 text-spa-stone/80 group-hover:text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</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 rounded-xl border border-spa-stone/10 shadow-sm hover:border-spa-forest transition-all group"
|
||||
>
|
||||
<div>
|
||||
<p className="text-xs text-spa-stone/80 uppercase tracking-widest mb-1 font-semibold">Order</p>
|
||||
<p className="text-xl font-bold font-mono text-spa-forest">#{id.toString().padStart(4, '0')}</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-full bg-spa-light flex items-center justify-center group-hover:bg-spa-forest transition-colors">
|
||||
<ChevronRight className="w-5 h-5 text-spa-stone/80 group-hover:text-white" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
localOrders.length === 0 ? (
|
||||
<div className="text-center py-12 bg-spa-light rounded-2xl border border-dashed border-spa-stone/20 space-y-3">
|
||||
<p className="text-spa-stone text-sm font-semibold">No recent orders found on this device.</p>
|
||||
<p className="text-[10px] uppercase text-spa-stone/60 tracking-widest font-bold">Log in with LINE or use manual search</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 rounded-xl border border-spa-stone/10 shadow-sm hover:border-spa-forest transition-all group"
|
||||
>
|
||||
<div>
|
||||
<p className="text-xs text-spa-stone/80 uppercase tracking-widest mb-1 font-semibold">Order</p>
|
||||
<p className="text-xl font-bold font-mono text-spa-forest">#{id.toString().padStart(4, '0')}</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 rounded-full bg-spa-light flex items-center justify-center group-hover:bg-spa-forest transition-colors">
|
||||
<ChevronRight className="w-5 h-5 text-spa-stone/80 group-hover:text-white" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user