feat: complete dynamic menu system across all frontends
All checks were successful
All checks were successful
- Add "Merchant Frontend" tab to admin menu-config page - Merchant render endpoint now respects AdminMenuConfig visibility via get_merchant_primary_platform_id() platform resolution - New store menu render endpoint (GET /store/core/menu/render/store) with platform-scoped visibility and store_code interpolation - Store sidebar migrated from hardcoded Jinja2 macros to dynamic Alpine.js x-for rendering with loading skeleton and fallback - Store init-alpine.js: add loadMenuConfig(), expandSectionForCurrentPage() - Include store page route fixes, login template updates, and tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,14 +21,20 @@ function getStoreSidebarSectionsFromStorage() {
|
||||
} catch (e) {
|
||||
console.warn('[STORE INIT-ALPINE] Failed to load sidebar state from localStorage:', e);
|
||||
}
|
||||
// Default: all sections open
|
||||
return {
|
||||
products: true,
|
||||
sales: true,
|
||||
customers: true,
|
||||
shop: true,
|
||||
account: true
|
||||
};
|
||||
// Default: empty (populated dynamically from API response)
|
||||
return {};
|
||||
}
|
||||
|
||||
function findSectionForPage(menuData, pageId) {
|
||||
if (!menuData?.sections) return null;
|
||||
for (const section of menuData.sections) {
|
||||
for (const item of (section.items || [])) {
|
||||
if (item.id === pageId) {
|
||||
return section.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function saveStoreSidebarSectionsToStorage(sections) {
|
||||
@@ -51,6 +57,10 @@ function data() {
|
||||
store: null,
|
||||
storeCode: null,
|
||||
|
||||
// Dynamic menu state
|
||||
menuData: null,
|
||||
menuLoading: false,
|
||||
|
||||
// Sidebar collapsible sections state
|
||||
openSections: getStoreSidebarSectionsFromStorage(),
|
||||
|
||||
@@ -81,8 +91,9 @@ function data() {
|
||||
this.dark = true;
|
||||
}
|
||||
|
||||
// Load store info
|
||||
// Load store info and dynamic menu
|
||||
this.loadStoreInfo();
|
||||
this.loadMenuConfig();
|
||||
|
||||
// Save last visited page (for redirect after login)
|
||||
// Exclude login, logout, onboarding, error pages
|
||||
@@ -111,6 +122,38 @@ function data() {
|
||||
}
|
||||
},
|
||||
|
||||
async loadMenuConfig() {
|
||||
if (this.menuData || this.menuLoading) return;
|
||||
if (typeof apiClient === 'undefined') return;
|
||||
if (!localStorage.getItem('store_token')) return;
|
||||
|
||||
this.menuLoading = true;
|
||||
try {
|
||||
this.menuData = await apiClient.get('/store/core/menu/render/store');
|
||||
// Initialize section open state from response
|
||||
for (const section of (this.menuData?.sections || [])) {
|
||||
if (this.openSections[section.id] === undefined) {
|
||||
this.openSections[section.id] = true;
|
||||
}
|
||||
}
|
||||
saveStoreSidebarSectionsToStorage(this.openSections);
|
||||
this.expandSectionForCurrentPage();
|
||||
} catch (e) {
|
||||
console.debug('Menu config not loaded, using fallback:', e?.message || e);
|
||||
} finally {
|
||||
this.menuLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
expandSectionForCurrentPage() {
|
||||
if (!this.menuData) return;
|
||||
const sectionId = findSectionForPage(this.menuData, this.currentPage);
|
||||
if (sectionId && !this.openSections[sectionId]) {
|
||||
this.openSections[sectionId] = true;
|
||||
saveStoreSidebarSectionsToStorage(this.openSections);
|
||||
}
|
||||
},
|
||||
|
||||
toggleSideMenu() {
|
||||
this.isSideMenuOpen = !this.isSideMenuOpen;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user