Files
orion/app/templates/store/partials/sidebar.html
Samir Boulahtit cfce6c0ca4
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
fix: loyalty module end-to-end — merchant route, store menus, sidebar, API error handling
- Add merchant loyalty overview route and template (was 404)
- Fix store loyalty route paths to match menu URLs (/{store_code}/loyalty/...)
- Add loyalty rewards card to storefront account dashboard
- Fix merchant overview to resolve merchant via get_merchant_for_current_user_page
- Fix store login to use store's primary platform for JWT token (interim fix)
- Fix apiClient to attach status/errorCode to thrown errors (fixes error.status
  checks in 12+ JS files — loyalty settings, terminal, email templates, etc.)
- Hide "Add Product" sidebar button when catalog module is not enabled
- Add proposal doc for proper platform detection in store login flow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:52:11 +01:00

157 lines
8.3 KiB
HTML

{# app/templates/store/partials/sidebar.html #}
{# Dynamic sidebar driven by menu discovery API - matching merchant/admin pattern #}
{# ============================================================================
SIDEBAR CONTENT (shared between desktop and mobile)
============================================================================ #}
{% macro sidebar_content() %}
<div class="py-4 text-gray-500 dark:text-gray-400">
<!-- Store Branding -->
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200 flex items-center"
:href="`/store/${storeCode}/dashboard`">
<span class="text-2xl mr-2">🏪</span>
<span x-text="store?.name || 'Store Portal'"></span>
</a>
<!-- Loading skeleton -->
<div x-show="menuLoading" class="mt-6 px-6 space-y-4 animate-pulse">
<div class="h-8 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
<div class="h-8 bg-gray-200 dark:bg-gray-700 rounded w-full"></div>
<div class="h-8 bg-gray-200 dark:bg-gray-700 rounded w-5/6"></div>
</div>
<!-- Dynamic menu from API -->
<div x-show="!menuLoading && menuData" x-cloak>
<template x-for="section in (menuData?.sections || [])" :key="section.id">
<div>
{# Unlabeled sections (e.g. "main" with dashboard) render items directly #}
<template x-if="!section.label">
<ul class="mt-6">
<template x-for="item in section.items" :key="item.id">
<li class="relative px-6 py-3">
<span x-show="currentPage === item.id" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === item.id ? 'text-gray-800 dark:text-gray-100' : ''"
:href="item.url">
<span x-html="$icon(item.icon, 'w-5 h-5')"></span>
<span class="ml-4" x-text="item.label"></span>
</a>
</li>
</template>
</ul>
</template>
{# Labeled sections are collapsible #}
<template x-if="section.label">
<div>
<div class="px-6 my-4">
<hr class="border-gray-200 dark:border-gray-700" />
</div>
<button
@click="toggleSection(section.id)"
class="flex items-center justify-between w-full px-6 py-2 text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
>
<span class="flex items-center" x-text="section.label"></span>
<span
x-html="$icon('chevron-down', 'w-4 h-4 transition-transform duration-200')"
:class="{ 'rotate-180': openSections[section.id] }"
></span>
</button>
<ul
x-show="openSections[section.id]"
x-transition:enter="transition-all duration-200 ease-out"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition-all duration-150 ease-in"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-2"
class="mt-1 overflow-hidden"
>
<template x-for="item in section.items" :key="item.id">
<li class="relative px-6 py-3">
<span x-show="currentPage === item.id" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === item.id ? 'text-gray-800 dark:text-gray-100' : ''"
:href="item.url">
<span x-html="$icon(item.icon, 'w-5 h-5')"></span>
<span class="ml-4" x-text="item.label"></span>
</a>
</li>
</template>
</ul>
</div>
</template>
</div>
</template>
</div>
<!-- Fallback: static dashboard link + warning (if menu API fails) -->
<div x-show="!menuLoading && !menuData" x-cloak>
<ul class="mt-6">
<li class="relative px-6 py-3">
<span x-show="currentPage === 'dashboard'" class="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg" aria-hidden="true"></span>
<a class="inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200"
:class="currentPage === 'dashboard' ? 'text-gray-800 dark:text-gray-100' : ''"
:href="`/store/${storeCode}/dashboard`">
<span x-html="$icon('home', 'w-5 h-5')"></span>
<span class="ml-4">Dashboard</span>
</a>
</li>
</ul>
<div class="px-6 mt-4">
<p class="text-xs text-gray-400 dark:text-gray-500">
Menu unavailable. Showing default items only.
</p>
</div>
</div>
{% if 'catalog' in enabled_modules %}
<!-- Quick Actions (static, outside dynamic menu) -->
<div class="px-6 my-6">
<button class="flex items-center justify-between w-full px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple"
@click="$dispatch('open-add-product-modal')">
<span x-html="$icon('plus', 'w-4 h-4')"></span>
<span class="ml-2">Add Product</span>
</button>
</div>
{% endif %}
</div>
{% endmacro %}
{# ============================================================================
DESKTOP SIDEBAR
============================================================================ #}
<aside class="z-20 hidden w-64 overflow-y-auto bg-white dark:bg-gray-800 md:block flex-shrink-0">
{{ sidebar_content() }}
</aside>
{# ============================================================================
MOBILE SIDEBAR
============================================================================ #}
<!-- Mobile sidebar backdrop -->
<div x-show="isSideMenuOpen"
x-transition:enter="transition ease-in-out duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in-out duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-10 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"></div>
<!-- Mobile sidebar panel -->
<aside class="fixed inset-y-0 z-20 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 md:hidden"
x-show="isSideMenuOpen"
x-transition:enter="transition ease-in-out duration-150"
x-transition:enter-start="opacity-0 transform -translate-x-20"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in-out duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0 transform -translate-x-20"
@click.away="closeSideMenu"
@keydown.escape="closeSideMenu">
{{ sidebar_content() }}
</aside>