Full website
This commit is contained in:
1 parent
807c43ece5
commit
ea9f33f60e
40 files changed
+4503
-1202
No files matched your search
@@ -0,0 +1,198 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user