Files
homaroma/apps/website/src/app/order/[id]/page.tsx
T
2026-06-17 07:42:59 -04:00

199 lines
9.6 KiB
TypeScript

import Link from 'next/link'
import { notFound } from 'next/navigation'
import type { Metadata } from 'next'
import { CheckCircle2, Clock, PackageCheck, Truck } from 'lucide-react'
import { formatPrice } from '@homaroma/types'
import DynamicPageRenderer from '../../../components/dynamic/DynamicPageRenderer'
import { getCategoriesResult, getCmsPageResult, getOrderResult } from '../../../lib/cms'
import { splitCmsLayoutSections } from '../../../lib/cms-sections'
const lang = 'th'
function orderLabel(id: number) {
return `#${id.toString().padStart(4, '0')}`
}
function statusLabel(status: string) {
const labels: Record<string, string> = {
PENDING_PAYMENT: 'รอชำระเงิน',
PAID: 'ชำระเงินแล้ว',
PROCESSING: 'กำลังจัดเตรียมสินค้า',
SHIPPED: 'จัดส่งแล้ว',
DELIVERED: 'จัดส่งสำเร็จ',
CANCELLED: 'ยกเลิกแล้ว',
}
return labels[status] ?? status.replaceAll('_', ' ')
}
function statusTone(status: string) {
if (status === 'PENDING_PAYMENT') return 'bg-amber-50 text-amber-700 border-amber-100'
if (status === 'PAID' || status === 'PROCESSING') return 'bg-emerald-50 text-emerald-700 border-emerald-100'
if (status === 'SHIPPED' || status === 'DELIVERED') return 'bg-sky-50 text-sky-700 border-sky-100'
if (status === 'CANCELLED') return 'bg-rose-50 text-rose-700 border-rose-100'
return 'bg-gray-50 text-gray-700 border-gray-100'
}
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
const { id } = await params
const result = await getOrderResult(id)
if (!result.ok) return { title: 'Order Not Found | HomAroma' }
return {
title: `Order ${orderLabel(result.data.id)} | HomAroma`,
description: `ตรวจสอบสถานะคำสั่งซื้อ ${orderLabel(result.data.id)} จาก HomAroma`,
}
}
export default async function OrderStatusPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const [orderResult, categoriesResult, cmsLayoutResult] = await Promise.all([
getOrderResult(id),
getCategoriesResult(lang),
getCmsPageResult('order-success-layout', lang),
])
const categories = categoriesResult.ok ? categoriesResult.data : []
const cmsSections = cmsLayoutResult.ok ? cmsLayoutResult.data.sections ?? [] : []
const { announcementSections, bottomSections } = splitCmsLayoutSections(cmsSections)
if (!cmsLayoutResult.ok && cmsLayoutResult.status !== 404) {
console.error(`[website-cms] order-success-layout unavailable: ${cmsLayoutResult.error}`)
}
if (!orderResult.ok) {
if (orderResult.status === 404) notFound()
return (
<main className="min-h-[70vh] bg-pop-bg">
{announcementSections.length > 0 && (
<DynamicPageRenderer sections={announcementSections} categories={categories} lang={lang} />
)}
<section className="mx-auto max-w-3xl px-6 py-24 text-center">
<div className="rounded-3xl border border-rose-100 bg-white p-10 shadow-brutal">
<h1 className="text-2xl font-semibold text-pop-black">ไม่สามารถโหลดคำสั่งซื้อนี้ได้</h1>
<p className="mt-3 text-sm text-rose-500">{orderResult.error}</p>
<Link href="/track" className="mt-6 inline-flex font-semibold text-pop-pink">กลับไปค้นหาคำสั่งซื้อ</Link>
</div>
</section>
</main>
)
}
const order = orderResult.data
const subtotal = order.subtotal ?? Math.max(0, order.totalAmount - order.shippingFee)
return (
<main className="min-h-[70vh] bg-pop-bg">
{announcementSections.length > 0 && (
<DynamicPageRenderer sections={announcementSections} categories={categories} lang={lang} />
)}
<section className="mx-auto max-w-4xl space-y-8 px-6 py-16 md:py-20">
<div className="text-center">
<div className="mx-auto mb-5 flex h-20 w-20 items-center justify-center rounded-full bg-white text-pop-pink shadow-brutal">
<CheckCircle2 className="h-10 w-10" />
</div>
<p className="text-xs font-bold uppercase tracking-[0.28em] text-pop-pink">Order Status</p>
<h1 className="mt-4 text-4xl font-semibold text-pop-black md:text-5xl">
{orderLabel(order.id)}
</h1>
<p className="mt-3 text-gray-600">คำสั่งซื้อของคุณถูกบันทึกแล้ว สามารถตรวจสอบสถานะล่าสุดได้จากหน้านี้</p>
</div>
<section className="grid gap-5 md:grid-cols-3">
<article className="rounded-3xl bg-white p-6 shadow-brutal">
<Clock className="mb-4 h-6 w-6 text-pop-pink" />
<p className="text-xs font-bold uppercase tracking-[0.22em] text-gray-400">Created</p>
<p className="mt-2 font-semibold text-pop-black">
{new Date(order.createdAt).toLocaleDateString('th-TH')}
</p>
</article>
<article className="rounded-3xl bg-white p-6 shadow-brutal">
<PackageCheck className="mb-4 h-6 w-6 text-pop-pink" />
<p className="text-xs font-bold uppercase tracking-[0.22em] text-gray-400">Status</p>
<span className={`mt-3 inline-flex rounded-full border px-3 py-1 text-xs font-bold uppercase tracking-[0.14em] ${statusTone(order.status)}`}>
{statusLabel(order.status)}
</span>
</article>
<article className="rounded-3xl bg-white p-6 shadow-brutal">
<Truck className="mb-4 h-6 w-6 text-pop-pink" />
<p className="text-xs font-bold uppercase tracking-[0.22em] text-gray-400">Tracking No.</p>
<p className="mt-2 font-semibold text-pop-black">{order.trackingNo || 'อยู่ระหว่างจัดเตรียมพัสดุ'}</p>
</article>
</section>
<section className="rounded-3xl bg-white p-8 shadow-brutal">
<h2 className="border-b border-gray-100 pb-4 text-sm font-bold uppercase tracking-[0.22em] text-pop-pink">
Order Items
</h2>
<div className="mt-6 space-y-4">
{order.items.map((item) => (
<div key={item.id} className="flex items-center gap-4 rounded-2xl bg-pop-bg p-4">
<div className="h-14 w-14 shrink-0 overflow-hidden rounded-xl bg-white">
<img
src={item.productImage || '/images/oil_ozone.png'}
alt={item.productName}
className="h-full w-full object-cover"
/>
</div>
<div className="min-w-0 flex-1">
<p className="font-semibold text-pop-black">{item.productName}</p>
<p className="text-sm text-gray-500">
{item.variantSize ? `${item.variantSize} · ` : ''}x {item.quantity}
</p>
</div>
<p className="font-bold text-pop-pink">{formatPrice(item.priceAtPurchase * item.quantity)}</p>
</div>
))}
</div>
<div className="mt-6 space-y-3 border-t border-gray-100 pt-5 text-sm">
<div className="flex justify-between text-gray-500">
<span>Subtotal</span>
<span>{formatPrice(subtotal)}</span>
</div>
<div className="flex justify-between text-gray-500">
<span>Shipping Fee</span>
<span>{formatPrice(order.shippingFee)}</span>
</div>
{(order.discountAmount ?? 0) > 0 && (
<div className="flex justify-between text-emerald-600">
<span>Discount{order.couponCode ? ` (${order.couponCode})` : ''}</span>
<span>-{formatPrice(order.discountAmount)}</span>
</div>
)}
<div className="flex justify-between border-t border-gray-100 pt-4 text-lg font-bold text-pop-black">
<span>Total</span>
<span className="text-pop-pink">{formatPrice(order.totalAmount)}</span>
</div>
</div>
</section>
{order.status === 'PENDING_PAYMENT' && (
<section className="rounded-3xl border border-amber-100 bg-amber-50 p-6 text-amber-800">
<h2 className="font-semibold">คำสั่งซื้อนี้ยังรอการชำระเงิน</h2>
<p className="mt-2 text-sm leading-relaxed">
หน้านี้รองรับการตรวจสอบสถานะคำสั่งซื้อแล้ว ส่วนการชำระเงินและอัปโหลดสลิปบน website จะย้ายมาในรอบถัดไป เพื่อไม่ทำ workaround ที่เสี่ยงกับ payment flow
</p>
</section>
)}
<div className="flex flex-wrap justify-center gap-3">
<Link href="/track" className="rounded-full bg-white px-6 py-3 font-semibold text-pop-black shadow-brutal transition hover:text-pop-pink">
ค้นหาออเดอร์อื่น
</Link>
<Link href="/shop" className="rounded-full bg-pop-pink px-6 py-3 font-semibold text-white transition hover:bg-pop-red">
กลับไปช้อปต่อ
</Link>
</div>
</section>
{bottomSections.length > 0 && (
<DynamicPageRenderer sections={bottomSections} categories={categories} lang={lang} />
)}
</main>
)
}