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

@@ -23,21 +23,56 @@ export default function Shop() {
const [sortBy, setSortBy] = useState('');
const { addItem } = useCartStore();
// Map grid columns setting to Tailwind classes
const gridColsClass = {
'2': 'grid-cols-1 sm:grid-cols-2',
'3': 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
'4': 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
'5': 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5',
'6': 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6',
}[shopLayout.grid_columns] || 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4';
// Map grid columns setting to Tailwind classes (responsive)
const gridCols = typeof shopLayout.grid_columns === 'object'
? shopLayout.grid_columns
: { mobile: '2', tablet: '3', desktop: '4' };
// Masonry column classes (CSS columns)
const masonryColsClass = {
'2': 'columns-1 sm:columns-2',
'3': 'columns-1 sm:columns-2 lg:columns-3',
'4': 'columns-1 sm:columns-2 lg:columns-3 xl:columns-4',
}[shopLayout.grid_columns] || 'columns-1 sm:columns-2 lg:columns-3';
// Map to actual Tailwind classes (can't use template literals due to purging)
const mobileClass = {
'1': 'grid-cols-1',
'2': 'grid-cols-2',
'3': 'grid-cols-3',
}[gridCols.mobile] || 'grid-cols-2';
const tabletClass = {
'2': 'md:grid-cols-2',
'3': 'md:grid-cols-3',
'4': 'md:grid-cols-4',
}[gridCols.tablet] || 'md:grid-cols-3';
const desktopClass = {
'2': 'lg:grid-cols-2',
'3': 'lg:grid-cols-3',
'4': 'lg:grid-cols-4',
'5': 'lg:grid-cols-5',
'6': 'lg:grid-cols-6',
}[gridCols.desktop] || 'lg:grid-cols-4';
const gridColsClass = `${mobileClass} ${tabletClass} ${desktopClass}`;
// Masonry column classes
const masonryMobileClass = {
'1': 'columns-1',
'2': 'columns-2',
'3': 'columns-3',
}[gridCols.mobile] || 'columns-2';
const masonryTabletClass = {
'2': 'md:columns-2',
'3': 'md:columns-3',
'4': 'md:columns-4',
}[gridCols.tablet] || 'md:columns-3';
const masonryDesktopClass = {
'2': 'lg:columns-2',
'3': 'lg:columns-3',
'4': 'lg:columns-4',
'5': 'lg:columns-5',
'6': 'lg:columns-6',
}[gridCols.desktop] || 'lg:columns-4';
const masonryColsClass = `${masonryMobileClass} ${masonryTabletClass} ${masonryDesktopClass}`;
const isMasonry = shopLayout.grid_style === 'masonry';
@@ -114,7 +149,7 @@ export default function Shop() {
{search && (
<button
onClick={() => setSearch('')}
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-100 rounded-full transition-colors"
className="font-[inherit] absolute right-3 top-1/2 -translate-y-1/2 p-1 hover:bg-gray-100 rounded-full transition-colors"
>
<X className="h-4 w-4 text-muted-foreground" />
</button>