feat: implement multiple saved addresses with modal selector in checkout
- Add AddressController with full CRUD API for saved addresses - Implement address management UI in My Account > Addresses - Add modal-based address selector in checkout (Tokopedia-style) - Hide checkout forms when saved address is selected - Add search functionality in address modal - Auto-select default addresses on page load - Fix variable products to show 'Select Options' instead of 'Add to Cart' - Add admin toggle for multiple addresses feature - Clean up debug logs and fix TypeScript errors
This commit is contained in:
@@ -17,6 +17,7 @@ interface ProductCardProps {
|
||||
image?: string;
|
||||
on_sale?: boolean;
|
||||
stock_status?: string;
|
||||
type?: string;
|
||||
};
|
||||
onAddToCart?: (product: any) => void;
|
||||
}
|
||||
@@ -32,9 +33,18 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
'landscape': 'aspect-[4/3]',
|
||||
}[layout.aspect_ratio] || 'aspect-square';
|
||||
|
||||
const isVariable = product.type === 'variable';
|
||||
|
||||
const handleAddToCart = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Variable products need to go to product page for attribute selection
|
||||
if (isVariable) {
|
||||
window.location.href = `/product/${product.slug}`;
|
||||
return;
|
||||
}
|
||||
|
||||
onAddToCart?.(product);
|
||||
};
|
||||
|
||||
@@ -122,7 +132,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="absolute top-2 left-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button className="p-2 bg-white rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center">
|
||||
<button className="font-[inherit] p-2 bg-white rounded-full shadow-md hover:bg-gray-50 flex items-center justify-center">
|
||||
<Heart className="w-4 h-4 block" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -137,7 +147,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
{!isTextOnly && addToCart.show_icon && <ShoppingCart className="w-4 h-4 mr-2" />}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : isVariable ? 'Select Options' : 'Add to Cart'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -145,7 +155,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
|
||||
{/* Content */}
|
||||
<div className={`p-4 flex-1 flex flex-col ${textAlignClass}`}>
|
||||
<h3 className="font-semibold text-gray-900 mb-2 line-clamp-2 group-hover:text-primary transition-colors">
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-2 line-clamp-2 leading-snug group-hover:text-primary transition-colors">
|
||||
{product.name}
|
||||
</h3>
|
||||
|
||||
@@ -153,15 +163,15 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<div className={`flex items-center gap-2 mb-3 ${(layout.card_text_align || 'left') === 'center' ? 'justify-center' : (layout.card_text_align || 'left') === 'right' ? 'justify-end' : ''}`}>
|
||||
{product.on_sale && product.regular_price ? (
|
||||
<>
|
||||
<span className="text-lg font-bold" style={{ color: 'var(--color-primary)' }}>
|
||||
<span className="text-base font-bold" style={{ color: 'var(--color-primary)' }}>
|
||||
{formatPrice(product.sale_price || product.price)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500 line-through">
|
||||
<span className="text-xs text-gray-500 line-through">
|
||||
{formatPrice(product.regular_price)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-lg font-bold text-gray-900">
|
||||
<span className="text-base font-bold text-gray-900">
|
||||
{formatPrice(product.price)}
|
||||
</span>
|
||||
)}
|
||||
@@ -176,7 +186,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
{!isTextOnly && addToCart.show_icon && <ShoppingCart className="w-4 h-4 mr-2" />}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : isVariable ? 'Select Options' : 'Add to Cart'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -224,7 +234,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
{addToCart.show_icon && <ShoppingCart className="w-4 h-4 mr-2" />}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : 'Add to Cart'}
|
||||
{product.stock_status === 'outofstock' ? 'Out of Stock' : isVariable ? 'Select Options' : 'Add to Cart'}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -232,7 +242,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
|
||||
{/* Content */}
|
||||
<div className="text-center">
|
||||
<h3 className="font-medium text-gray-900 mb-2 group-hover:text-primary transition-colors">
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-2 leading-snug group-hover:text-primary transition-colors">
|
||||
{product.name}
|
||||
</h3>
|
||||
|
||||
@@ -240,15 +250,15 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<div className="flex items-center justify-center gap-2 mb-3">
|
||||
{product.on_sale && product.regular_price ? (
|
||||
<>
|
||||
<span className="font-semibold" style={{ color: 'var(--color-primary)' }}>
|
||||
<span className="text-base font-bold" style={{ color: 'var(--color-primary)' }}>
|
||||
{formatPrice(product.sale_price || product.price)}
|
||||
</span>
|
||||
<span className="text-sm text-gray-400 line-through">
|
||||
<span className="text-xs text-gray-400 line-through">
|
||||
{formatPrice(product.regular_price)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="font-semibold text-gray-900">
|
||||
<span className="text-base font-bold text-gray-900">
|
||||
{formatPrice(product.price)}
|
||||
</span>
|
||||
)}
|
||||
@@ -320,7 +330,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
|
||||
{/* Content */}
|
||||
<div className="text-center font-serif">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-3 tracking-wide group-hover:text-primary transition-colors">
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-3 tracking-wide leading-snug group-hover:text-primary transition-colors">
|
||||
{product.name}
|
||||
</h3>
|
||||
|
||||
@@ -328,15 +338,15 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
<div className="flex items-center justify-center gap-3 mb-4">
|
||||
{product.on_sale && product.regular_price ? (
|
||||
<>
|
||||
<span className="text-xl font-medium" style={{ color: 'var(--color-primary)' }}>
|
||||
<span className="text-lg font-semibold" style={{ color: 'var(--color-primary)' }}>
|
||||
{formatPrice(product.sale_price || product.price)}
|
||||
</span>
|
||||
<span className="text-gray-400 line-through">
|
||||
<span className="text-sm text-gray-400 line-through">
|
||||
{formatPrice(product.regular_price)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xl font-medium text-gray-900">
|
||||
<span className="text-lg font-semibold text-gray-900">
|
||||
{formatPrice(product.price)}
|
||||
</span>
|
||||
)}
|
||||
@@ -349,7 +359,7 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
className="w-full font-serif tracking-wider"
|
||||
disabled={product.stock_status === 'outofstock'}
|
||||
>
|
||||
{product.stock_status === 'outofstock' ? 'OUT OF STOCK' : 'ADD TO CART'}
|
||||
{product.stock_status === 'outofstock' ? 'OUT OF STOCK' : isVariable ? 'SELECT OPTIONS' : 'ADD TO CART'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -376,12 +386,12 @@ export function ProductCard({ product, onAddToCart }: ProductCardProps) {
|
||||
</div>
|
||||
|
||||
<div className="p-4 text-center">
|
||||
<h3 className="font-semibold text-gray-900 mb-2">{product.name}</h3>
|
||||
<div className="text-xl font-bold mb-3" style={{ color: 'var(--color-primary)' }}>
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-2 leading-snug">{product.name}</h3>
|
||||
<div className="text-lg font-bold mb-3" style={{ color: 'var(--color-primary)' }}>
|
||||
{formatPrice(product.price)}
|
||||
</div>
|
||||
<Button onClick={handleAddToCart} className="w-full" size="lg">
|
||||
Buy Now
|
||||
{isVariable ? 'Select Options' : 'Buy Now'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user