This commit is contained in:
Your Name committed 2026-10-08 08:08:37 +07:00
1 parent d13dedd215
commit dba82b26a8
17 files changed
+427 -71

No files matched your search

+16 -11
View File
@@ -23,6 +23,7 @@ import {
import { formatPrice, type CustomerAddress, type Order } from '@homaroma/types'
import { API_BASE } from '../lib/api'
import { useLang } from '../hooks/useLang'
import { orderStatusBadgeClass, toneClass } from '../lib/theme'
import { useAuthStore, type Customer } from '../store/authStore'
@@ -107,6 +108,7 @@ async function readJson<T>(path: string, init?: RequestInit): Promise<T> {
}
export default function ProfileClient() {
const lang = useLang()
const { token, customer, setAuth, logout } = useAuthStore()
const [activeTab, setActiveTab] = useState<ProfileTab>('orders')
const [dbCustomer, setDbCustomer] = useState<ProfileCustomer | null>(null)
@@ -401,18 +403,21 @@ export default function ProfileClient() {
</div>
<div className="mt-4 space-y-3">
{order.items.map((item) => (
<div key={item.id} className="flex items-center gap-3 text-sm">
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-xl bg-surface-page">
<Image src={item.productImage || '/images/fallback-product.svg'} alt={item.productName} fill unoptimized className="h-full w-full object-cover" />
{order.items.map((item) => {
const displayName = (item.translations as any)?.name?.[lang] || item.productName
return (
<div key={item.id} className="flex items-center gap-3 text-sm">
<div className="relative h-11 w-11 shrink-0 overflow-hidden rounded-xl bg-surface-page">
<Image src={item.productImage || '/images/fallback-product.svg'} alt={displayName} fill unoptimized className="h-full w-full object-cover" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-semibold text-text-main">{displayName}</p>
<p className="text-xs text-muted-text">{item.variantName ? `${item.variantName} · ` : ''}x {item.quantity}</p>
</div>
<p className="font-bold text-brand-primary">{formatPrice(item.priceAtPurchase * item.quantity)}</p>
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-semibold text-text-main">{item.productName}</p>
<p className="text-xs text-muted-text">{item.variantName ? `${item.variantName} · ` : ''}x {item.quantity}</p>
</div>
<p className="font-bold text-brand-primary">{formatPrice(item.priceAtPurchase * item.quantity)}</p>
</div>
))}
)
})}
</div>
<div className="mt-5 flex flex-col gap-3 border-t border-line-primary/20 pt-4 text-sm md:flex-row md:items-center md:justify-between">
@@ -2,7 +2,8 @@
import Link from './LocalizedLink'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ChevronRight, Loader2, Package, Search } from 'lucide-react'
import { API_BASE } from '../lib/api'
@@ -31,9 +32,22 @@ function orderLabel(id: number) {
return `#${id.toString().padStart(4, '0')}`
}
export default function TrackOrderClient() {
interface TrackOrderClientProps {
lang?: string
}
export default function TrackOrderClient({ lang: propLang }: TrackOrderClientProps = {}) {
const router = useRouter()
const lang = useLang()
const contextLang = useLang()
const lang = propLang || contextLang || 'th'
const { t: i18nT } = useTranslation()
const t = useCallback(
(key: string, options?: Record<string, unknown>) => {
return i18nT(key, { lng: lang, ...options })
},
[i18nT, lang],
)
const [orderId, setOrderId] = useState('')
const [phone, setPhone] = useState('')
const [localOrders, setLocalOrders] = useState<number[]>([])
@@ -61,7 +75,7 @@ export default function TrackOrderClient() {
const normalizedOrderId = Number.parseInt(orderId.replace(/[^0-9]/g, ''), 10)
if (!Number.isFinite(normalizedOrderId) || normalizedOrderId <= 0) {
setError('กรุณากรอกหมายเลขคำสั่งซื้อให้ถูกต้อง')
setError(t('track.errors.invalidOrderId'))
setLoading(false)
return
}
@@ -83,9 +97,9 @@ export default function TrackOrderClient() {
return
}
setError('error' in data && data.error ? data.error : 'ไม่สามารถยืนยันข้อมูลคำสั่งซื้อได้')
setError('error' in data && data.error ? data.error : t('track.errors.notFound'))
} catch (err) {
setError(err instanceof Error ? err.message : 'เกิดข้อผิดพลาดในการเชื่อมต่อ กรุณาลองใหม่อีกครั้ง')
setError(err instanceof Error ? err.message : t('track.errors.networkError'))
} finally {
setLoading(false)
}
@@ -99,8 +113,8 @@ export default function TrackOrderClient() {
<Search className="h-5 w-5" />
</div>
<div>
<p className="ui-eyebrow">Manual Search</p>
<h2 className="text-xl font-semibold text-text-main">ค้นหาคำสั่งซื้อ</h2>
<p className="ui-eyebrow">{t('track.search.eyebrow')}</p>
<h2 className="text-xl font-semibold text-text-main">{t('track.search.title')}</h2>
</div>
</div>
@@ -113,7 +127,7 @@ export default function TrackOrderClient() {
<div className="space-y-2">
<label htmlFor="orderId" className="ui-form-label">
Order Number
{t('track.search.orderIdLabel')}
</label>
<input
id="orderId"
@@ -121,14 +135,14 @@ export default function TrackOrderClient() {
value={orderId}
onChange={(event) => setOrderId(event.target.value)}
className="ui-form-shell"
placeholder="เช่น 1024"
placeholder={t('track.search.orderIdPlaceholder')}
type="text"
/>
</div>
<div className="space-y-2">
<label htmlFor="phone" className="ui-form-label">
Phone Number
{t('track.search.phoneLabel')}
</label>
<input
id="phone"
@@ -136,7 +150,7 @@ export default function TrackOrderClient() {
value={phone}
onChange={(event) => setPhone(event.target.value)}
className="ui-form-shell"
placeholder="เบอร์โทรที่ใช้ตอนสั่งซื้อ"
placeholder={t('track.search.phonePlaceholder')}
type="tel"
/>
</div>
@@ -146,7 +160,7 @@ export default function TrackOrderClient() {
disabled={loading}
className="ui-form-inline-button w-full"
>
{loading ? <Loader2 className="h-5 w-5 animate-spin" /> : 'Find Order'}
{loading ? <Loader2 className="h-5 w-5 animate-spin" /> : t('track.search.submit')}
</button>
</form>
</section>
@@ -154,13 +168,13 @@ export default function TrackOrderClient() {
<section className="space-y-6">
<div className="flex items-center gap-3 border-b border-line-primary/20 pb-4">
<Package className="h-5 w-5 text-brand-primary" />
<h2 className="text-xl font-semibold text-text-main">Recent Device Orders</h2>
<h2 className="text-xl font-semibold text-text-main">{t('track.recent.title')}</h2>
</div>
{localOrders.length === 0 ? (
<div className="ui-empty-panel p-10 text-center">
<p className="text-sm font-semibold text-muted-text">ยังไม่มีประวัติคำสั่งซื้อบนอุปกรณ์นี้</p>
<p className="mt-2 text-xs uppercase tracking-[0.18em] text-muted-text/70">ใช้ฟอร์มค้นหาด้านซ้ายเพื่อบันทึกออเดอร์</p>
<p className="text-sm font-semibold text-muted-text">{t('track.recent.emptyTitle')}</p>
<p className="mt-2 text-xs uppercase tracking-[0.18em] text-muted-text/70">{t('track.recent.emptyDesc')}</p>
</div>
) : (
<div className="space-y-3">
@@ -171,7 +185,7 @@ export default function TrackOrderClient() {
className="ui-card ui-card-hover group flex items-center justify-between p-5"
>
<div>
<p className="text-xs font-bold uppercase tracking-[0.22em] text-muted-text/70">Order</p>
<p className="text-xs font-bold uppercase tracking-[0.22em] text-muted-text/70">{t('track.recent.orderLabel')}</p>
<p className="mt-1 font-mono text-2xl font-bold text-text-main">{orderLabel(id)}</p>
</div>
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-page text-text-main transition group-hover:bg-brand-primary group-hover:text-surface-card">
@@ -185,3 +199,4 @@ export default function TrackOrderClient() {
</div>
)
}