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:
Dwindi Ramadhana
2025-12-26 01:16:11 +07:00
parent 9ac09582d2
commit 100f9cce55
27 changed files with 2492 additions and 205 deletions

View File

@@ -63,10 +63,10 @@ export default function AppearanceHeader() {
style,
sticky,
height,
mobile_menu: mobileMenu,
mobile_logo: mobileLogo,
logo_width: logoWidth,
logo_height: logoHeight,
mobileMenu,
mobileLogo,
logoWidth,
logoHeight,
elements,
});
toast.success('Header settings saved successfully');

View File

@@ -11,7 +11,11 @@ import { api } from '@/lib/api';
export default function AppearanceShop() {
const [loading, setLoading] = useState(true);
const [gridColumns, setGridColumns] = useState('3');
const [gridColumns, setGridColumns] = useState({
mobile: '2',
tablet: '3',
desktop: '4'
});
const [gridStyle, setGridStyle] = useState('standard');
const [cardStyle, setCardStyle] = useState('card');
const [aspectRatio, setAspectRatio] = useState('square');
@@ -37,7 +41,11 @@ export default function AppearanceShop() {
const shop = response.data?.pages?.shop;
if (shop) {
setGridColumns(shop.layout?.grid_columns || '3');
setGridColumns(shop.layout?.grid_columns || {
mobile: '2',
tablet: '3',
desktop: '4'
});
setGridStyle(shop.layout?.grid_style || 'standard');
setCardStyle(shop.layout?.card_style || 'card');
setAspectRatio(shop.layout?.aspect_ratio || 'square');
@@ -110,17 +118,55 @@ export default function AppearanceShop() {
title="Layout"
description="Configure shop page layout and product display"
>
<SettingsSection label="Grid Columns" htmlFor="grid-columns">
<Select value={gridColumns} onValueChange={setGridColumns}>
<SelectTrigger id="grid-columns">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2">2 Columns</SelectItem>
<SelectItem value="3">3 Columns</SelectItem>
<SelectItem value="4">4 Columns</SelectItem>
</SelectContent>
</Select>
<SettingsSection label="Grid Columns" description="Set columns for each breakpoint">
<div className="grid grid-cols-3 gap-4">
<div>
<Label htmlFor="grid-columns-mobile" className="text-sm font-medium mb-2 block">Mobile</Label>
<Select value={gridColumns.mobile} onValueChange={(value) => setGridColumns({ ...gridColumns, mobile: value })}>
<SelectTrigger id="grid-columns-mobile">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">1</SelectItem>
<SelectItem value="2">2</SelectItem>
<SelectItem value="3">3</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-gray-500 mt-1">&lt;768px</p>
</div>
<div>
<Label htmlFor="grid-columns-tablet" className="text-sm font-medium mb-2 block">Tablet</Label>
<Select value={gridColumns.tablet} onValueChange={(value) => setGridColumns({ ...gridColumns, tablet: value })}>
<SelectTrigger id="grid-columns-tablet">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2">2</SelectItem>
<SelectItem value="3">3</SelectItem>
<SelectItem value="4">4</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-gray-500 mt-1">768-1024px</p>
</div>
<div>
<Label htmlFor="grid-columns-desktop" className="text-sm font-medium mb-2 block">Desktop</Label>
<Select value={gridColumns.desktop} onValueChange={(value) => setGridColumns({ ...gridColumns, desktop: value })}>
<SelectTrigger id="grid-columns-desktop">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="2">2</SelectItem>
<SelectItem value="3">3</SelectItem>
<SelectItem value="4">4</SelectItem>
<SelectItem value="5">5</SelectItem>
<SelectItem value="6">6</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-gray-500 mt-1">&gt;1024px</p>
</div>
</div>
</SettingsSection>
<SettingsSection label="Grid Style" htmlFor="grid-style" description="Masonry creates a Pinterest-like layout with varying heights">

View File

@@ -12,6 +12,7 @@ import { formatMoney, getStoreCurrency } from '@/lib/currency';
interface CustomerSettings {
auto_register_members: boolean;
multiple_addresses_enabled: boolean;
vip_min_spent: number;
vip_min_orders: number;
vip_timeframe: 'all' | '30' | '90' | '365';
@@ -22,6 +23,7 @@ interface CustomerSettings {
export default function CustomersSettings() {
const [settings, setSettings] = useState<CustomerSettings>({
auto_register_members: false,
multiple_addresses_enabled: true,
vip_min_spent: 1000,
vip_min_orders: 10,
vip_timeframe: 'all',
@@ -119,13 +121,23 @@ export default function CustomersSettings() {
title={__('General')}
description={__('General customer settings')}
>
<ToggleField
id="auto_register_members"
label={__('Auto-register customers as site members')}
description={__('Automatically create WordPress user accounts for new customers when orders are created. Customers will receive login credentials via email and can track their orders.')}
checked={settings.auto_register_members}
onCheckedChange={(checked) => setSettings({ ...settings, auto_register_members: checked })}
/>
<div className="space-y-6">
<ToggleField
id="auto_register_members"
label={__('Auto-register customers as site members')}
description={__('Automatically create WordPress user accounts for new customers when orders are created. Customers will receive login credentials via email and can track their orders.')}
checked={settings.auto_register_members}
onCheckedChange={(checked) => setSettings({ ...settings, auto_register_members: checked })}
/>
<ToggleField
id="multiple_addresses_enabled"
label={__('Enable multiple saved addresses')}
description={__('Allow customers to save multiple billing and shipping addresses in their account. Customers can select from saved addresses during checkout for faster ordering.')}
checked={settings.multiple_addresses_enabled}
onCheckedChange={(checked) => setSettings({ ...settings, multiple_addresses_enabled: checked })}
/>
</div>
</SettingsCard>
<SettingsCard