213 lines
8.4 KiB
TypeScript
213 lines
8.4 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import Navbar from '../components/Navbar'
|
|
import Footer from '../components/Footer'
|
|
import EmptyState from '../components/EmptyState'
|
|
import DynamicPageRenderer from '../components/DynamicPageRenderer'
|
|
import { API_BASE, type Category, type PageSection } from '../types'
|
|
import { Ticket, Copy, CheckCircle2, ArrowRight } from 'lucide-react'
|
|
|
|
interface Coupon {
|
|
id: number
|
|
code: string
|
|
type: string
|
|
value: number
|
|
minPurchase: number
|
|
usageLimit: number | null
|
|
usedCount: number
|
|
expiresAt: string | null
|
|
}
|
|
|
|
export default function CouponsPage() {
|
|
const navigate = useNavigate()
|
|
const [categories, setCategories] = useState<Category[]>([])
|
|
const [coupons, setCoupons] = useState<Coupon[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [copiedCode, setCopiedCode] = useState<string | null>(null)
|
|
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
|
|
|
|
useEffect(() => {
|
|
fetch(`${API_BASE}/categories`)
|
|
.then(res => res.json())
|
|
.then(setCategories)
|
|
.catch(console.error)
|
|
|
|
fetch(`${API_BASE}/pages/coupons-layout`)
|
|
.then((r) => {
|
|
if (!r.ok) throw new Error('Page not found');
|
|
return r.json();
|
|
})
|
|
.then((data) => {
|
|
if (data && data.sections) {
|
|
setCmsSections(data.sections);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.log('Coupons dynamic layout sections not loaded:', err.message);
|
|
});
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
fetch(`${API_BASE}/categories`)
|
|
.then(res => res.json())
|
|
.then(setCategories)
|
|
.catch(console.error)
|
|
|
|
fetch(`${API_BASE}/coupons/active`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
// Filter out coupons that have reached their limit
|
|
const availableCoupons = data.filter((c: Coupon) =>
|
|
c.usageLimit === null || c.usedCount < c.usageLimit
|
|
)
|
|
setCoupons(availableCoupons)
|
|
})
|
|
.catch(console.error)
|
|
.finally(() => setLoading(false))
|
|
}, [])
|
|
|
|
const formatDiscount = (type: string, value: number) => {
|
|
if (type === 'PERCENTAGE') return `${value}% OFF`
|
|
if (type === 'FIXED_AMOUNT') return `ลด ${value} ฿`
|
|
if (type === 'FREE_SHIPPING') return `ส่งฟรี`
|
|
return `${value}`
|
|
}
|
|
|
|
const handleCopy = (code: string) => {
|
|
navigator.clipboard.writeText(code)
|
|
setCopiedCode(code)
|
|
setTimeout(() => setCopiedCode(null), 2000)
|
|
}
|
|
|
|
const handleUseNow = (code: string) => {
|
|
localStorage.setItem('homaroma_active_coupon', code)
|
|
navigate('/shop')
|
|
}
|
|
|
|
const announcementSections = cmsSections.filter(s => s.isActive && s.type.toUpperCase() === 'ANNOUNCEMENT_BAR');
|
|
const heroSections = cmsSections.filter(s => s.isActive && (s.type.toUpperCase() === 'HERO' || s.type.toUpperCase() === 'BANNER'));
|
|
const bottomSections = cmsSections.filter(s => s.isActive && s.type.toUpperCase() !== 'ANNOUNCEMENT_BAR' && s.type.toUpperCase() !== 'HERO' && s.type.toUpperCase() !== 'BANNER');
|
|
|
|
return (
|
|
<div className="min-h-screen bg-pop-bg">
|
|
{announcementSections.length > 0 && (
|
|
<DynamicPageRenderer
|
|
sections={announcementSections}
|
|
categories={categories}
|
|
categoriesLoading={false}
|
|
/>
|
|
)}
|
|
|
|
<Navbar categories={categories} />
|
|
|
|
<main className="max-w-6xl mx-auto px-6 pt-32 pb-24 min-h-[70vh]">
|
|
{/* Header */}
|
|
{heroSections.length > 0 ? (
|
|
<DynamicPageRenderer
|
|
sections={heroSections}
|
|
categories={categories}
|
|
categoriesLoading={false}
|
|
/>
|
|
) : (
|
|
<div className="text-center space-y-4 mb-16">
|
|
<h1 className="text-4xl md:text-5xl font-bold text-pop-black">
|
|
Exclusive Offers
|
|
</h1>
|
|
<p className="text-spa-stone max-w-2xl mx-auto">
|
|
คูปองส่วนลดพิเศษสำหรับคุณ เลือกใช้คูปองที่คุณต้องการแล้วไปช้อปกันเลย!
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{loading ? (
|
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{[1, 2, 3].map(i => (
|
|
<div key={i} className="h-64 bg-spa-stone/10 rounded-3xl animate-pulse" />
|
|
))}
|
|
</div>
|
|
) : coupons.length === 0 ? (
|
|
<EmptyState
|
|
title="ยังไม่มีคูปองส่วนลดในขณะนี้"
|
|
message="กรุณากลับมาตรวจสอบโปรโมชั่นใหม่ในภายหลัง"
|
|
actionText="ไปช้อปปิ้งกันก่อน"
|
|
actionLink="/shop"
|
|
/>
|
|
) : (
|
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{coupons.map(coupon => (
|
|
<div
|
|
key={coupon.id}
|
|
className="group relative bg-white border border-spa-stone/10 rounded-3xl overflow-hidden hover:border-spa-forest transition-all duration-500 shadow-sm hover:shadow-md"
|
|
>
|
|
{/* Decoration */}
|
|
<div className="absolute top-0 right-0 w-32 h-32 bg-spa-sage/20 blur-3xl rounded-full -translate-y-1/2 translate-x-1/2" />
|
|
|
|
<div className="p-8 space-y-6 relative z-10">
|
|
<div className="flex items-start justify-between">
|
|
<div className="w-12 h-12 rounded-xl bg-spa-sage/30 text-spa-forest flex items-center justify-center border border-spa-stone/10">
|
|
<Ticket className="w-6 h-6" />
|
|
</div>
|
|
{coupon.expiresAt && (
|
|
<span className="text-[10px] font-bold text-rose-500 bg-rose-500/10 px-2 py-1 rounded-md uppercase tracking-wider">
|
|
หมดเขต {new Date(coupon.expiresAt).toLocaleDateString('th-TH')}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-3xl font-black text-pop-black mb-1">
|
|
{formatDiscount(coupon.type, coupon.value)}
|
|
</h3>
|
|
<p className="text-sm text-spa-stone">
|
|
เมื่อซื้อครบ {coupon.minPurchase} บาท
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 bg-spa-light p-1 pl-4 rounded-xl border border-spa-stone/20">
|
|
<span className="flex-1 font-mono text-spa-forest font-bold tracking-widest uppercase">
|
|
{coupon.code}
|
|
</span>
|
|
<button
|
|
onClick={() => handleCopy(coupon.code)}
|
|
className="p-3 hover:bg-spa-stone/10 rounded-lg text-spa-stone hover:text-spa-forest transition-all group/btn"
|
|
title="Copy Code"
|
|
>
|
|
{copiedCode === coupon.code ? (
|
|
<CheckCircle2 className="w-5 h-5 text-emerald-500" />
|
|
) : (
|
|
<Copy className="w-5 h-5 group-hover/btn:scale-110 transition-transform" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
<button
|
|
onClick={() => handleUseNow(coupon.code)}
|
|
className="w-full py-4 bg-spa-forest text-white hover:bg-spa-stone font-bold rounded-xl flex items-center justify-center gap-2 transition-all duration-300 shadow-sm"
|
|
>
|
|
ใช้คูปองนี้เลย
|
|
<ArrowRight className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Scalloped edge effect */}
|
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1/2 w-6 h-6 bg-pop-bg rounded-full border-r border-spa-stone/10" />
|
|
<div className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1/2 w-6 h-6 bg-pop-bg rounded-full border-l border-spa-stone/10" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</main>
|
|
|
|
{bottomSections.length > 0 && (
|
|
<DynamicPageRenderer
|
|
sections={bottomSections}
|
|
categories={categories}
|
|
categoriesLoading={false}
|
|
/>
|
|
)}
|
|
|
|
<Footer categories={categories} />
|
|
</div>
|
|
)
|
|
}
|