feat: implement user profile, order history, and address book with checkout integration

This commit is contained in:
Your Name committed 2026-06-07 18:48:56 +07:00
1 parent bae945b9d4
commit 0c3c450e1f
9 files changed
+1109 -32

No files matched your search

+800
View File
@@ -0,0 +1,800 @@
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import Navbar from '../components/Navbar'
import Footer from '../components/Footer'
import { API_BASE, formatPrice, type Category, type Order, type CustomerAddress } from '../types'
import { useAuthStore } from '../store/authStore'
import {
User,
MapPin,
Package,
Settings,
Plus,
Edit3,
Trash2,
Check,
Loader2,
ExternalLink,
Phone,
Mail,
Award
} from 'lucide-react'
export default function ProfilePage() {
const { token, customer, setAuth } = useAuthStore()
const [categories, setCategories] = useState<Category[]>([])
// Tab State
const [activeTab, setActiveTab] = useState<'orders' | 'addresses' | 'settings'>('orders')
// Data States
const [dbCustomer, setDbCustomer] = useState<any>(null)
const [orders, setOrders] = useState<Order[]>([])
const [addresses, setAddresses] = useState<CustomerAddress[]>([])
// Loading States
const [loadingOrders, setLoadingOrders] = useState(false)
const [loadingAddresses, setLoadingAddresses] = useState(false)
// Address Form States
const [showAddressForm, setShowAddressForm] = useState(false)
const [editingAddress, setEditingAddress] = useState<CustomerAddress | null>(null)
const [recipientName, setRecipientName] = useState('')
const [phone, setPhone] = useState('')
const [addressLine, setAddressLine] = useState('')
const [subdistrict, setSubdistrict] = useState('')
const [district, setDistrict] = useState('')
const [province, setProvince] = useState('')
const [zipcode, setZipcode] = useState('')
const [isDefault, setIsDefault] = useState(false)
const [submittingAddress, setSubmittingAddress] = useState(false)
// Settings Form States
const [emailInput, setEmailInput] = useState('')
const [phoneInput, setPhoneInput] = useState('')
const [submittingSettings, setSubmittingSettings] = useState(false)
const [settingsSuccess, setSettingsSuccess] = useState(false)
// Fetch Categories
useEffect(() => {
fetch(`${API_BASE}/categories`)
.then(res => res.json())
.then(setCategories)
.catch(console.error)
}, [])
// Fetch Customer Profile details & Points dynamically
const fetchProfile = () => {
if (!customer) return
fetch(`${API_BASE}/profile/${customer.id}`)
.then(res => res.json())
.then(data => {
if (data && !data.error) {
setDbCustomer(data)
setEmailInput(data.email || '')
setPhoneInput(data.phone || '')
}
})
.catch(console.error)
}
// Fetch Orders
const fetchOrders = () => {
if (!customer) return
setLoadingOrders(true)
fetch(`${API_BASE}/orders/customer/${customer.id}`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) {
setOrders(data)
}
})
.catch(console.error)
.finally(() => setLoadingOrders(false))
}
// Fetch Addresses
const fetchAddresses = () => {
if (!customer) return
setLoadingAddresses(true)
fetch(`${API_BASE}/profile/${customer.id}/addresses`)
.then(res => res.json())
.then(data => {
if (Array.isArray(data)) {
setAddresses(data)
}
})
.catch(console.error)
.finally(() => setLoadingAddresses(false))
}
useEffect(() => {
if (customer) {
fetchProfile()
fetchOrders()
fetchAddresses()
}
}, [customer])
// Reset Address Form
const resetAddressForm = () => {
setRecipientName('')
setPhone('')
setAddressLine('')
setSubdistrict('')
setDistrict('')
setProvince('')
setZipcode('')
setIsDefault(false)
setEditingAddress(null)
setShowAddressForm(false)
}
// Handle Edit Address Click
const startEditAddress = (addr: CustomerAddress) => {
setEditingAddress(addr)
setRecipientName(addr.recipientName)
setPhone(addr.phone)
setAddressLine(addr.addressLine)
setSubdistrict(addr.subdistrict)
setDistrict(addr.district)
setProvince(addr.province)
setZipcode(addr.zipcode)
setIsDefault(addr.isDefault)
setShowAddressForm(true)
}
// Handle Add/Edit Address Submission
const handleAddressSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!customer) return
setSubmittingAddress(true)
const payload = {
recipientName,
phone,
addressLine,
subdistrict,
district,
province,
zipcode,
isDefault
}
try {
const url = editingAddress
? `${API_BASE}/profile/${customer.id}/addresses/${editingAddress.id}`
: `${API_BASE}/profile/${customer.id}/addresses`
const method = editingAddress ? 'PUT' : 'POST'
const res = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
if (res.ok) {
resetAddressForm()
fetchAddresses()
} else {
const err = await res.json()
alert(err.error || 'Failed to save address')
}
} catch (err) {
console.error(err)
alert('Failed to connect to server')
} finally {
setSubmittingAddress(false)
}
}
// Handle Delete Address
const handleDeleteAddress = async (addrId: number) => {
if (!customer) return
if (!confirm('ยืนยันที่จะลบที่อยู่จัดส่งนี้?')) return
try {
const res = await fetch(`${API_BASE}/profile/${customer.id}/addresses/${addrId}`, {
method: 'DELETE'
})
if (res.ok) {
fetchAddresses()
} else {
alert('Failed to delete address')
}
} catch (err) {
console.error(err)
}
}
// Handle Set Default Address
const handleSetDefault = async (addrId: number) => {
if (!customer) return
try {
const res = await fetch(`${API_BASE}/profile/${customer.id}/addresses/${addrId}/default`, {
method: 'PATCH'
})
if (res.ok) {
fetchAddresses()
}
} catch (err) {
console.error(err)
}
}
// Handle Settings Submit (Update email & phone)
const handleSettingsSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!customer) return
setSubmittingSettings(true)
setSettingsSuccess(false)
try {
const res = await fetch(`${API_BASE}/profile/${customer.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: emailInput || null,
phone: phoneInput || null
})
})
if (res.ok) {
const updatedCustomer = await res.json()
setDbCustomer(updatedCustomer)
// Update store
if (token) {
setAuth(token, updatedCustomer)
}
setSettingsSuccess(true)
setTimeout(() => setSettingsSuccess(false), 3000)
} else {
const err = await res.json()
alert(err.error || 'Failed to update profile')
}
} catch (err) {
console.error(err)
alert('Failed to connect to server')
} finally {
setSubmittingSettings(false)
}
}
const getOrderStatusColor = (status: string) => {
switch (status.toUpperCase()) {
case 'PAID':
return 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20'
case 'PENDING_PAYMENT':
return 'bg-amber-500/10 text-amber-600 border-amber-500/20'
case 'SHIPPED':
return 'bg-blue-500/10 text-blue-600 border-blue-500/20'
case 'CANCELLED':
return 'bg-rose-500/10 text-rose-600 border-rose-500/20'
default:
return 'bg-spa-stone/10 text-spa-stone border-spa-stone/20'
}
}
const translateStatus = (status: string) => {
switch (status.toUpperCase()) {
case 'PAID':
return 'ชำระเงินสำเร็จ'
case 'PENDING_PAYMENT':
return 'รอการชำระเงิน'
case 'SHIPPED':
return 'จัดส่งแล้ว'
case 'CANCELLED':
return 'ยกเลิกแล้ว'
default:
return status
}
}
return (
<div className="min-h-screen bg-pop-bg">
<Navbar categories={categories} />
<main className="max-w-5xl mx-auto px-6 pt-32 pb-24 text-pop-black">
{!customer ? (
/* Login CTA screen */
<div className="max-w-md mx-auto text-center space-y-8 py-20 animate-fade-in bg-white p-10 rounded-3xl border border-spa-stone/10 shadow-sm mt-10">
<div className="w-20 h-20 bg-spa-light rounded-full flex items-center justify-center mx-auto border border-spa-stone/10 shadow-inner">
<User className="w-10 h-10 text-spa-stone" />
</div>
<div className="space-y-3">
<h1 className="text-3xl font-bold text-pop-black">เข้าสู่ระบบสมาชิก</h1>
<p className="text-spa-stone text-sm">ล็อกอินเพื่อตรวจสอบประวัติคำสั่งซื้อ สะสมคะแนนสมาชิก และบันทึกสมุดที่อยู่จัดส่งสินค้า</p>
</div>
<button
onClick={() => {
const liff = (window as any).liff
if (liff) {
liff.login({ redirectUri: window.location.href })
} else {
alert('LINE LIFF System is initializing. Please refresh.')
}
}}
className="w-full py-4 bg-spa-forest hover:bg-spa-stone text-white font-bold rounded-xl tracking-widest uppercase transition-all duration-300 shadow-sm flex items-center justify-center gap-3"
>
<Award className="w-5 h-5" />
<span>เข้าสู่ระบบผ่าน LINE</span>
</button>
<Link to="/shop" className="block text-xs text-spa-stone hover:underline font-bold">กลับไปเลือกซื้อสินค้า</Link>
</div>
) : (
/* Logged In Dashboard Layout */
<div className="space-y-10 animate-fade-in">
{/* Header Profile Banner */}
<div className="bg-white rounded-3xl border border-spa-stone/10 p-8 flex flex-col md:flex-row justify-between items-center gap-6 shadow-sm relative overflow-hidden">
<div className="absolute top-0 inset-x-0 h-1.5 bg-gradient-to-r from-spa-sage via-spa-forest to-spa-stone opacity-50" />
{/* User Avatar & Info */}
<div className="flex flex-col md:flex-row items-center gap-5 text-center md:text-left">
<div className="w-24 h-24 rounded-full overflow-hidden border-2 border-spa-forest/20 shadow-md">
<img
src={customer.pictureUrl || `https://ui-avatars.com/api/?name=${customer.displayName}`}
alt="LINE Profile"
className="w-full h-full object-cover"
/>
</div>
<div className="space-y-1.5">
<h1 className="text-3xl font-bold text-pop-black">{customer.displayName}</h1>
<div className="flex flex-col md:flex-row md:items-center gap-2 md:gap-4 text-xs font-semibold text-spa-stone">
{dbCustomer?.phone && (
<span className="flex items-center gap-1.5 justify-center md:justify-start">
<Phone className="w-3.5 h-3.5" />
<span>{dbCustomer.phone}</span>
</span>
)}
{dbCustomer?.email && (
<span className="flex items-center gap-1.5 justify-center md:justify-start">
<Mail className="w-3.5 h-3.5" />
<span>{dbCustomer.email}</span>
</span>
)}
</div>
</div>
</div>
{/* Point Cards */}
<div className="bg-gradient-to-br from-spa-forest to-spa-stone text-white p-6 rounded-2xl w-full md:w-auto min-w-[220px] text-center space-y-1.5 shadow-md border border-white/5 relative overflow-hidden">
<div className="absolute -right-8 -bottom-8 w-24 h-24 bg-white/5 rounded-full" />
<p className="text-[10px] uppercase font-bold tracking-widest text-spa-sage">คะแนนสะสมของคุณ</p>
<div className="text-4xl font-extrabold flex items-center justify-center gap-1">
<span>{dbCustomer?.points ?? customer.points ?? 0}</span>
</div>
<p className="text-[10px] text-spa-sage/95 font-semibold">1 คะแนนมีค่าเท่ากับ ฿1 สำหรับใช้เป็นส่วนลด</p>
</div>
</div>
{/* Dashboard Tabs & Content */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8 items-start">
{/* Vertical Menu Tabs */}
<div className="bg-white rounded-2xl border border-spa-stone/10 p-3 shadow-sm space-y-1">
<button
onClick={() => setActiveTab('orders')}
className={`w-full py-3 px-4 rounded-xl font-bold text-left text-sm flex items-center gap-3 transition-all ${
activeTab === 'orders'
? 'bg-spa-forest text-white shadow-sm'
: 'text-spa-stone hover:bg-spa-light hover:text-pop-black'
}`}
>
<Package className="w-4.5 h-4.5" />
<span>ประวัติการสั่งซื้อ</span>
<span className="ml-auto bg-spa-sage/20 text-[10px] px-2 py-0.5 rounded-full font-extrabold text-spa-forest">
{orders.length}
</span>
</button>
<button
onClick={() => setActiveTab('addresses')}
className={`w-full py-3 px-4 rounded-xl font-bold text-left text-sm flex items-center gap-3 transition-all ${
activeTab === 'addresses'
? 'bg-spa-forest text-white shadow-sm'
: 'text-spa-stone hover:bg-spa-light hover:text-pop-black'
}`}
>
<MapPin className="w-4.5 h-4.5" />
<span>สมุดที่อยู่จัดส่ง</span>
<span className="ml-auto bg-spa-sage/20 text-[10px] px-2 py-0.5 rounded-full font-extrabold text-spa-forest">
{addresses.length}
</span>
</button>
<button
onClick={() => setActiveTab('settings')}
className={`w-full py-3 px-4 rounded-xl font-bold text-left text-sm flex items-center gap-3 transition-all ${
activeTab === 'settings'
? 'bg-spa-forest text-white shadow-sm'
: 'text-spa-stone hover:bg-spa-light hover:text-pop-black'
}`}
>
<Settings className="w-4.5 h-4.5" />
<span>ข้อมูลติดต่อของฉัน</span>
</button>
</div>
{/* Tab Display Area */}
<div className="lg:col-span-3 space-y-8">
{/* 1. ORDERS TAB */}
{activeTab === 'orders' && (
<div className="space-y-6">
<h2 className="text-xl font-bold border-b border-spa-stone/10 pb-4 flex items-center gap-2">
<Package className="w-5 h-5 text-spa-forest" />
<span>ประวัติคำสั่งซื้อทั้งหมด</span>
</h2>
{loadingOrders ? (
<div className="flex items-center justify-center py-20">
<Loader2 className="w-8 h-8 text-spa-forest animate-spin" />
</div>
) : orders.length === 0 ? (
<div className="text-center py-16 bg-white rounded-2xl border border-spa-stone/10 space-y-4 shadow-sm">
<Package className="w-12 h-12 text-spa-stone/40 mx-auto" />
<div>
<p className="font-bold text-pop-black">ไม่มีประวัติการสั่งซื้อ</p>
<p className="text-xs text-spa-stone font-medium">คุณยังไม่ได้ทำการสั่งซื้อสินค้าใดๆ กับแบรนด์ของเรา</p>
</div>
<Link
to="/shop"
className="inline-block px-6 py-2.5 bg-spa-forest hover:bg-spa-stone text-white font-bold rounded-lg text-xs uppercase tracking-wider transition-colors shadow-sm"
>
ไปหน้าช้อปปิ้ง
</Link>
</div>
) : (
<div className="space-y-4">
{orders.map(order => (
<div
key={order.id}
className="bg-white border border-spa-stone/10 rounded-2xl p-6 shadow-sm hover:border-spa-stone transition-all duration-300 space-y-4"
>
{/* Card Header */}
<div className="flex flex-col md:flex-row justify-between md:items-center gap-3 border-b border-spa-stone/10 pb-4">
<div>
<p className="text-xs text-spa-stone font-semibold">เลขออเดอร์ #{order.id.toString().padStart(4, '0')}</p>
<p className="text-[10px] text-spa-stone/85 mt-0.5">สั่งเมื่อ {new Date(order.createdAt).toLocaleDateString('th-TH', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' })}</p>
</div>
<div className="flex items-center gap-3">
<span className={`px-3 py-1 rounded-full text-xs font-bold border ${getOrderStatusColor(order.status)}`}>
{translateStatus(order.status)}
</span>
<Link
to={`/order/${order.id}`}
className="p-1.5 hover:bg-spa-light rounded-lg border border-spa-stone/10 transition-colors"
title="ดูหน้าออเดอร์สำเร็จ"
>
<ExternalLink className="w-4 h-4 text-spa-stone" />
</Link>
</div>
</div>
{/* Card Items */}
<div className="space-y-3">
{order.items.map(item => (
<div key={item.id} className="flex items-center gap-3 text-xs">
<div className="w-10 h-10 rounded-md bg-spa-stone/10 overflow-hidden border border-spa-stone/15 flex-shrink-0">
<img src={item.productImage || '/images/oil_ozone.png'} alt="Product" className="w-full h-full object-cover" />
</div>
<div className="flex-1 min-w-0">
<p className="font-bold text-pop-black truncate">{item.productName}</p>
<p className="text-[10px] text-spa-stone/80 mt-0.5">{item.variantSize} x {item.quantity}</p>
</div>
<p className="font-bold text-rose-500 shrink-0">{formatPrice(item.priceAtPurchase * item.quantity)}</p>
</div>
))}
</div>
{/* Card Footer */}
<div className="border-t border-spa-stone/10 pt-4 flex flex-col md:flex-row justify-between items-start md:items-center gap-3 text-xs">
<div>
{order.trackingNo && (
<p className="font-semibold text-spa-stone">
เลขแทรกพัสดุ: <span className="font-mono text-spa-forest bg-spa-sage/15 px-2 py-0.5 rounded font-extrabold">{order.trackingNo}</span>
</p>
)}
</div>
<div className="flex items-center justify-between w-full md:w-auto gap-4 self-stretch md:self-auto border-t md:border-0 border-spa-stone/10 pt-3 md:pt-0">
<span className="text-spa-stone font-semibold">ยอดรวมสุทธิ:</span>
<span className="text-base font-extrabold text-rose-500">{formatPrice(order.totalAmount)}</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* 2. ADDRESSES TAB */}
{activeTab === 'addresses' && (
<div className="space-y-6">
<div className="flex justify-between items-center border-b border-spa-stone/10 pb-4 gap-4">
<h2 className="text-xl font-bold flex items-center gap-2">
<MapPin className="w-5 h-5 text-spa-forest" />
<span>สมุดที่อยู่จัดส่งของฉัน</span>
</h2>
{!showAddressForm && (
<button
onClick={() => {
resetAddressForm()
setShowAddressForm(true)
}}
className="px-4 py-2 bg-spa-forest hover:bg-spa-stone text-white font-bold rounded-lg text-xs uppercase tracking-wider transition-colors shadow-sm flex items-center gap-2"
>
<Plus className="w-3.5 h-3.5" />
<span>เพิ่มที่อยู่ใหม่</span>
</button>
)}
</div>
{/* Address Creation/Editing Form */}
{showAddressForm && (
<form onSubmit={handleAddressSubmit} className="bg-white border border-spa-stone/15 p-6 rounded-2xl shadow-sm space-y-5 animate-slide-down">
<h3 className="text-sm font-bold uppercase tracking-widest text-spa-forest border-b border-spa-stone/10 pb-2">
{editingAddress ? 'แก้ไขที่อยู่จัดส่ง' : 'เพิ่มที่อยู่จัดส่งใหม่'}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">ชื่อผู้รับ</label>
<input
type="text"
required
value={recipientName}
onChange={e => setRecipientName(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
placeholder="เช่น สมชาย ใจดี"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">เบอร์โทรศัพท์ผู้รับ</label>
<input
type="tel"
required
value={phone}
onChange={e => setPhone(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
placeholder="เช่น 0812345678"
/>
</div>
</div>
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">ที่อยู่ (บ้านเลขที่, หมู่บ้าน, ถนน, ซอย)</label>
<input
type="text"
required
value={addressLine}
onChange={e => setAddressLine(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
placeholder="เช่น 99/9 หมู่ 9 ซอยสุขุมวิท 23"
/>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">แขวง / ตำบล</label>
<input
type="text"
required
value={subdistrict}
onChange={e => setSubdistrict(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">เขต / อำเภอ</label>
<input
type="text"
required
value={district}
onChange={e => setDistrict(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">จังหวัด</label>
<input
type="text"
required
value={province}
onChange={e => setProvince(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">รหัสไปรษณีย์</label>
<input
type="text"
required
maxLength={5}
value={zipcode}
onChange={e => setZipcode(e.target.value.replace(/\D/g, ''))}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-2.5 text-xs text-pop-black focus:border-spa-forest outline-none transition-all"
/>
</div>
</div>
<div className="flex items-center gap-2.5 py-1">
<input
type="checkbox"
id="isDefault"
checked={isDefault}
onChange={e => setIsDefault(e.target.checked)}
className="w-4 h-4 text-spa-forest focus:ring-spa-forest border-spa-stone/30 rounded cursor-pointer"
/>
<label htmlFor="isDefault" className="text-xs font-semibold text-spa-stone cursor-pointer select-none">
ตั้งเป็นที่อยู่จัดส่งเริ่มต้น (Default Address)
</label>
</div>
<div className="flex items-center justify-end gap-3 pt-3 border-t border-spa-stone/10">
<button
type="button"
onClick={resetAddressForm}
className="px-5 py-2.5 border border-spa-stone/25 hover:bg-spa-light text-spa-stone font-bold rounded-lg text-xs uppercase tracking-wider transition-colors"
>
ยกเลิก
</button>
<button
type="submit"
disabled={submittingAddress}
className="px-5 py-2.5 bg-spa-forest hover:bg-spa-stone text-white font-bold rounded-lg text-xs uppercase tracking-wider transition-colors disabled:opacity-50 flex items-center gap-2"
>
{submittingAddress && <Loader2 className="w-3.5 h-3.5 animate-spin" />}
<span>บันทึกที่อยู่</span>
</button>
</div>
</form>
)}
{loadingAddresses ? (
<div className="flex items-center justify-center py-20">
<Loader2 className="w-8 h-8 text-spa-forest animate-spin" />
</div>
) : addresses.length === 0 ? (
<div className="text-center py-16 bg-white rounded-2xl border border-spa-stone/10 space-y-4 shadow-sm">
<MapPin className="w-12 h-12 text-spa-stone/40 mx-auto" />
<div>
<p className="font-bold text-pop-black">ไม่มีข้อมูลที่อยู่จัดส่ง</p>
<p className="text-xs text-spa-stone font-medium">กรุณากดปุ่มเพิ่มที่อยู่ด้านบนเพื่อบันทึกสมุดที่อยู่จัดส่งสินค้าของคุณ</p>
</div>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{addresses.map(addr => (
<div
key={addr.id}
className={`bg-white border rounded-2xl p-5 shadow-sm space-y-4 transition-all duration-300 relative ${
addr.isDefault
? 'border-spa-forest ring-1 ring-spa-forest/20'
: 'border-spa-stone/10 hover:border-spa-stone'
}`}
>
<div className="flex justify-between items-start gap-4">
<div className="space-y-1">
<div className="flex items-center gap-2 flex-wrap">
<p className="font-bold text-sm text-pop-black">{addr.recipientName}</p>
{addr.isDefault && (
<span className="bg-spa-forest/15 text-spa-forest text-[9px] font-extrabold px-2 py-0.5 rounded-full border border-spa-forest/20">
เริ่มต้น
</span>
)}
</div>
<p className="text-[10px] text-spa-stone font-semibold flex items-center gap-1">
<Phone className="w-3 h-3 text-spa-stone/60" />
<span>{addr.phone}</span>
</p>
</div>
<div className="flex items-center gap-1 shrink-0">
<button
onClick={() => startEditAddress(addr)}
className="p-1.5 hover:bg-spa-light rounded-lg border border-spa-stone/10 transition-colors"
title="แก้ไขที่อยู่"
>
<Edit3 className="w-3.5 h-3.5 text-spa-stone" />
</button>
<button
onClick={() => handleDeleteAddress(addr.id)}
className="p-1.5 hover:bg-rose-500/10 rounded-lg border border-rose-500/10 transition-colors"
title="ลบที่อยู่"
>
<Trash2 className="w-3.5 h-3.5 text-rose-500" />
</button>
</div>
</div>
<p className="text-xs text-spa-stone/90 leading-relaxed font-medium">
{addr.addressLine} ต.{addr.subdistrict} อ.{addr.district} จ.{addr.province} {addr.zipcode}
</p>
{!addr.isDefault && (
<button
onClick={() => handleSetDefault(addr.id)}
className="w-full py-1.5 border border-spa-stone/20 hover:bg-spa-light text-[10px] font-bold uppercase tracking-wider text-spa-stone rounded-lg transition-colors"
>
ตั้งเป็นที่อยู่เริ่มต้น
</button>
)}
</div>
))}
</div>
)}
</div>
)}
{/* 3. SETTINGS TAB */}
{activeTab === 'settings' && (
<div className="space-y-6">
<h2 className="text-xl font-bold border-b border-spa-stone/10 pb-4 flex items-center gap-2">
<Settings className="w-5 h-5 text-spa-forest" />
<span>ข้อมูลติดต่อสมาชิก</span>
</h2>
<form onSubmit={handleSettingsSubmit} className="bg-white border border-spa-stone/10 rounded-2xl p-6 shadow-sm max-w-lg space-y-5">
{settingsSuccess && (
<div className="p-4 bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 font-bold rounded-xl text-xs text-center flex items-center justify-center gap-2">
<Check className="w-4 h-4" />
<span>บันทึกข้อมูลติดต่อของคุณสำเร็จแล้ว</span>
</div>
)}
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">ที่อยู่อีเมล (Email Address)</label>
<input
type="email"
value={emailInput}
onChange={e => setEmailInput(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-xs text-pop-black focus:border-spa-forest outline-none transition-all placeholder:text-spa-stone/30"
placeholder="เช่น example@email.com"
/>
</div>
<div className="space-y-1.5">
<label className="text-[10px] uppercase font-bold tracking-widest text-spa-stone/80">เบอร์โทรศัพท์ (Phone Number)</label>
<input
type="tel"
value={phoneInput}
onChange={e => setPhoneInput(e.target.value)}
className="w-full bg-spa-light border border-spa-stone/20 rounded-lg p-3 text-xs text-pop-black focus:border-spa-forest outline-none transition-all placeholder:text-spa-stone/30"
placeholder="เช่น 0812345678"
/>
</div>
<div className="pt-2">
<button
type="submit"
disabled={submittingSettings}
className="w-full py-3 bg-spa-forest hover:bg-spa-stone text-white font-bold rounded-lg text-xs uppercase tracking-widest transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
>
{submittingSettings ? <Loader2 className="w-4 h-4 animate-spin" /> : <Check className="w-4 h-4" />}
<span>บันทึกข้อมูลติดต่อ</span>
</button>
</div>
</form>
</div>
)}
</div>
</div>
</div>
)}
</main>
<Footer categories={categories} />
</div>
)
}