Rename ssr to website, website to spa, add spa nginx config
This commit is contained in:
1 parent
fc6bf0218e
commit
8514342c2f
103 files changed
+801
-768
No files matched your search
@@ -1,10 +1,9 @@
|
||||
import React from 'react'
|
||||
import { X, ShoppingBag, Plus, Minus, Trash2, Tag, ArrowRight } from 'lucide-react'
|
||||
import { Link } from 'react-router-dom'
|
||||
"use client";
|
||||
import { X, ShoppingBag, Plus, Minus, Trash2 } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useCartStore } from '../store/cartStore'
|
||||
import { formatPrice } from '@homaroma/types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { API_BASE } from '../types'
|
||||
|
||||
export default function CartDrawer() {
|
||||
const { t } = useTranslation()
|
||||
@@ -40,7 +39,7 @@ export default function CartDrawer() {
|
||||
</div>
|
||||
|
||||
{/* Cart Items */}
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-4">
|
||||
<div className="flex-1 overflow-y-auto p-6 space-y-6">
|
||||
{items.length === 0 ? (
|
||||
<div className="h-full flex flex-col items-center justify-center text-slate-500 space-y-4">
|
||||
<ShoppingBag className="w-16 h-16 opacity-20" />
|
||||
@@ -55,7 +54,7 @@ export default function CartDrawer() {
|
||||
) : (
|
||||
items.map((item) => (
|
||||
<div key={item.cartItemId} className="flex gap-4 p-4 rounded-2xl bg-white/5 border border-white/5 group relative">
|
||||
<div className="w-18 h-22 rounded-lg bg-night-950 overflow-hidden flex-shrink-0" style={{ width: '72px', height: '88px' }}>
|
||||
<div className="w-20 h-24 rounded-lg bg-night-950 overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={item.productImage || '/images/oil_ozone.png'}
|
||||
alt={item.productName}
|
||||
@@ -63,46 +62,34 @@ export default function CartDrawer() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col pt-1 space-y-1 relative min-w-0">
|
||||
<h3 className="font-bold text-sm leading-tight pr-6 truncate">{item.productName}</h3>
|
||||
<div className="flex-1 flex flex-col pt-1 space-y-1 relative">
|
||||
<h3 className="font-bold text-sm leading-tight pr-6">{item.productName}</h3>
|
||||
{item.variantSize && (
|
||||
<p className="text-xs text-slate-500">{item.variantSize}</p>
|
||||
<p className="text-xs text-slate-400">{item.variantSize}</p>
|
||||
)}
|
||||
|
||||
{/* Price row */}
|
||||
<div className="flex items-baseline gap-1.5 mt-0.5">
|
||||
<span className="text-xs text-slate-500">{formatPrice(item.price)}</span>
|
||||
<span className="text-xs text-slate-600">×</span>
|
||||
<span className="text-xs text-slate-500">{item.quantity}</span>
|
||||
<span className="text-xs text-slate-600">=</span>
|
||||
<span className="text-sm font-bold text-cyan-glow">{formatPrice(item.price * item.quantity)}</span>
|
||||
</div>
|
||||
<p className="text-cyan-glow text-sm font-bold mt-auto">{formatPrice(item.price)}</p>
|
||||
|
||||
{/* Quantity controls */}
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<div className="flex items-center gap-2 bg-night-950/70 rounded-lg border border-white/5">
|
||||
<button
|
||||
onClick={() => updateQuantity(item.cartItemId, item.quantity - 1)}
|
||||
className="p-1.5 hover:text-cyan-glow transition-colors"
|
||||
>
|
||||
<Minus className="w-3 h-3" />
|
||||
</button>
|
||||
<span className="w-5 text-center text-xs font-bold">{item.quantity}</span>
|
||||
<button
|
||||
onClick={() => updateQuantity(item.cartItemId, item.quantity + 1)}
|
||||
className="p-1.5 hover:text-cyan-glow transition-colors"
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mt-2 bg-night-950/50 rounded-lg w-max border border-white/5">
|
||||
<button
|
||||
onClick={() => updateQuantity(item.cartItemId, item.quantity - 1)}
|
||||
className="p-1 hover:text-cyan-glow transition-colors"
|
||||
>
|
||||
<Minus className="w-4 h-4" />
|
||||
</button>
|
||||
<span className="w-4 text-center text-xs font-bold">{item.quantity}</span>
|
||||
<button
|
||||
onClick={() => updateQuantity(item.cartItemId, item.quantity + 1)}
|
||||
className="p-1 hover:text-cyan-glow transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => removeFromCart(item.cartItemId)}
|
||||
className="absolute top-0 right-0 p-1.5 text-slate-600 hover:text-rose-500 transition-colors"
|
||||
title="Remove item"
|
||||
className="absolute top-1 right-0 p-1 text-slate-500 hover:text-rose-500 transition-colors opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -112,103 +99,22 @@ export default function CartDrawer() {
|
||||
|
||||
{/* Footer */}
|
||||
{items.length > 0 && (
|
||||
<CartDrawerFooter subtotal={getCartTotal()} onClose={() => toggleCart(false)} t={t} />
|
||||
<div className="p-6 border-t border-white/10 bg-night-950/50 space-y-6">
|
||||
<div className="flex items-center justify-between text-lg font-bold">
|
||||
<span>{t('cart.subtotal')}</span>
|
||||
<span className="text-cyan-glow">{formatPrice(getCartTotal())}</span>
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 text-center">{t('cart.shippingNote')}</p>
|
||||
<Link
|
||||
href="/checkout"
|
||||
onClick={() => toggleCart(false)}
|
||||
className="btn-primary w-full py-4 text-center block uppercase tracking-widest text-sm"
|
||||
>
|
||||
{t('cart.proceed')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CartDrawerFooter({ subtotal, onClose }: { subtotal: number; onClose: () => void; t?: any }) {
|
||||
const [savedCoupon, setSavedCoupon] = React.useState<string | null>(null)
|
||||
const [discountAmount, setDiscountAmount] = React.useState<number>(0)
|
||||
const [isFreeShipping, setIsFreeShipping] = React.useState<boolean>(false)
|
||||
const [couponValid, setCouponValid] = React.useState<boolean>(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const code = localStorage.getItem('irelax_active_coupon')
|
||||
setSavedCoupon(code)
|
||||
if (code && subtotal > 0) {
|
||||
fetch(`${API_BASE}/coupons/validate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ code, cartTotal: subtotal })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.valid) {
|
||||
setCouponValid(true)
|
||||
setDiscountAmount(data.discountAmount || 0)
|
||||
setIsFreeShipping(data.isFreeShipping || false)
|
||||
} else {
|
||||
setCouponValid(false)
|
||||
}
|
||||
})
|
||||
.catch(() => setCouponValid(false))
|
||||
}
|
||||
}, [subtotal])
|
||||
|
||||
const removeCoupon = () => {
|
||||
localStorage.removeItem('irelax_active_coupon')
|
||||
setSavedCoupon(null)
|
||||
setDiscountAmount(0)
|
||||
setIsFreeShipping(false)
|
||||
setCouponValid(false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="p-5 border-t border-white/10 bg-night-950/60 space-y-4">
|
||||
|
||||
{/* Totals */}
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between text-slate-400">
|
||||
<span>ราคาสินค้า</span>
|
||||
<span>{formatPrice(subtotal)}</span>
|
||||
</div>
|
||||
{(discountAmount > 0 || isFreeShipping) && savedCoupon && (
|
||||
<div className="flex justify-between text-cyan-glow">
|
||||
<span className="flex items-center gap-1.5">
|
||||
ส่วนลดคูปอง
|
||||
<span className="text-[10px] font-bold bg-cyan-glow/15 px-1.5 py-0.5 rounded tracking-widest">
|
||||
{savedCoupon}
|
||||
</span>
|
||||
<button onClick={removeCoupon} className="text-slate-500 hover:text-rose-400 transition-colors" title="ยกเลิก">
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</span>
|
||||
<span>{isFreeShipping ? 'ส่งฟรี' : `- ${formatPrice(discountAmount)}`}</span>
|
||||
</div>
|
||||
)}
|
||||
{savedCoupon && !couponValid && (
|
||||
<div className="flex justify-between text-amber-400 text-xs">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Tag className="w-3 h-3" />
|
||||
คูปอง <span className="font-bold tracking-widest">{savedCoupon}</span> (ยังไม่ถึงยอดขั้นต่ำ)
|
||||
<button onClick={removeCoupon} className="text-slate-500 hover:text-rose-400 transition-colors">
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between font-bold text-base pt-2 border-t border-white/10">
|
||||
<span>รวมสุทธิ</span>
|
||||
<span className="text-cyan-glow">{formatPrice(Math.max(0, subtotal - discountAmount))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Checkout button */}
|
||||
<Link
|
||||
to="/checkout"
|
||||
onClick={onClose}
|
||||
className="flex items-center justify-center gap-2 w-full py-4 bg-white text-night-900 font-bold rounded-xl uppercase tracking-widest text-sm hover:bg-cyan-glow hover:shadow-[0_0_25px_rgba(102,252,241,0.4)] transition-all duration-500 group"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4" />
|
||||
ดำเนินการชำระเงิน
|
||||
<ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user