feat(merchant): extract merchant portal as first-class frontend with auth, Tailwind fixes, and Gitea CI
Some checks failed
Some checks failed
- Extract login/dashboard from billing module into core (matching admin pattern) - Add merchant auth API with path-isolated cookies (path=/merchants) - Add merchant base layout with sidebar/header partials and Alpine.js init - Add frontend detection and login redirect for MERCHANT type - Wire merchant token in shared api-client.js (get/clear) - Migrate billing templates to merchant base with dark mode support - Fix Tailwind: rename shop→storefront in sources and config - DRY Makefile tailwind targets with TAILWIND_FRONTENDS loop - Rebuild all Tailwind outputs (production minified) - Add Gitea Actions CI workflow (ruff, pytest, architecture, docs) - Add Gitea deployment documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,196 +1,80 @@
|
||||
{# app/templates/merchant/base.html #}
|
||||
{# Base template for the merchant billing portal #}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html :class="{ 'dark': dark }" x-data="{% block alpine_data %}data(){% endblock %}" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{% block title %}Merchant Portal{% endblock %} - Wizamart</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
<!-- Fonts: Local fallback + Google Fonts -->
|
||||
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Tailwind CSS -->
|
||||
<link rel="stylesheet" href="/static/admin/css/tailwind.output.css" />
|
||||
<!-- Tailwind CSS v4 (built locally via standalone CLI) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='merchant/css/tailwind.output.css') }}" />
|
||||
|
||||
<!-- Alpine Cloak -->
|
||||
<style>
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-gray-50 font-sans" x-data="merchantApp()" x-cloak>
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<body x-cloak>
|
||||
<div class="flex h-screen bg-gray-50 dark:bg-gray-900" :class="{ 'overflow-hidden': isSideMenuOpen }">
|
||||
<!-- Sidebar (server-side included) -->
|
||||
{% include 'merchant/partials/sidebar.html' %}
|
||||
|
||||
<!-- Sidebar -->
|
||||
<aside class="hidden md:flex md:flex-shrink-0">
|
||||
<div class="flex flex-col w-64 bg-indigo-900">
|
||||
<!-- Logo / Brand -->
|
||||
<div class="flex items-center h-16 px-6 bg-indigo-950">
|
||||
<a href="/merchants/billing/" class="flex items-center space-x-2">
|
||||
<svg class="w-8 h-8 text-indigo-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||
</svg>
|
||||
<span class="text-lg font-bold text-white">Merchant Portal</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 w-full">
|
||||
<!-- Header (server-side included) -->
|
||||
{% include 'merchant/partials/header.html' %}
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
|
||||
<a href="/merchants/billing/"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
||||
:class="currentPath === '/merchants/billing/' ? 'bg-indigo-800 text-white' : 'text-indigo-200 hover:bg-indigo-800 hover:text-white'">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-4 0h4"/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</a>
|
||||
<a href="/merchants/billing/subscriptions"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
||||
:class="currentPath.startsWith('/merchants/billing/subscription') ? 'bg-indigo-800 text-white' : 'text-indigo-200 hover:bg-indigo-800 hover:text-white'">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/>
|
||||
</svg>
|
||||
Subscriptions
|
||||
</a>
|
||||
<a href="/merchants/billing/billing"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
||||
:class="currentPath.startsWith('/merchants/billing/billing') ? 'bg-indigo-800 text-white' : 'text-indigo-200 hover:bg-indigo-800 hover:text-white'">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
Billing History
|
||||
</a>
|
||||
|
||||
<div class="pt-4 mt-4 border-t border-indigo-800">
|
||||
<p class="px-3 mb-2 text-xs font-semibold tracking-wider text-indigo-400 uppercase">Account</p>
|
||||
</div>
|
||||
<a href="/merchants/account/stores"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
||||
:class="currentPath.startsWith('/merchants/account/stores') ? 'bg-indigo-800 text-white' : 'text-indigo-200 hover:bg-indigo-800 hover:text-white'">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/>
|
||||
</svg>
|
||||
Stores
|
||||
</a>
|
||||
<a href="/merchants/account/profile"
|
||||
class="flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors"
|
||||
:class="currentPath.startsWith('/merchants/account/profile') ? 'bg-indigo-800 text-white' : 'text-indigo-200 hover:bg-indigo-800 hover:text-white'">
|
||||
<svg class="w-5 h-5 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
||||
</svg>
|
||||
Profile
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="flex flex-col flex-1 w-full overflow-hidden">
|
||||
|
||||
<!-- Top Header -->
|
||||
<header class="flex items-center justify-between h-16 px-6 bg-white border-b border-gray-200">
|
||||
<!-- Mobile menu button -->
|
||||
<button @click="sidebarOpen = !sidebarOpen" class="md:hidden p-2 rounded-md text-gray-500 hover:text-gray-700 focus:outline-none">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<!-- Merchant info and logout -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-sm font-medium text-gray-700" x-text="merchantName || 'Merchant'"></span>
|
||||
<button @click="logout()"
|
||||
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 transition-colors">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
|
||||
</svg>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Mobile Sidebar Overlay -->
|
||||
<div x-show="sidebarOpen" x-cloak
|
||||
class="fixed inset-0 z-40 md:hidden"
|
||||
@click="sidebarOpen = false">
|
||||
<div class="fixed inset-0 bg-gray-600 bg-opacity-50"></div>
|
||||
<div class="fixed inset-y-0 left-0 w-64 bg-indigo-900 z-50">
|
||||
<div class="flex items-center justify-between h-16 px-6 bg-indigo-950">
|
||||
<span class="text-lg font-bold text-white">Merchant Portal</span>
|
||||
<button @click="sidebarOpen = false" class="text-indigo-300 hover:text-white">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<nav class="px-4 py-6 space-y-1">
|
||||
<a href="/merchants/billing/" class="flex items-center px-3 py-2 text-sm font-medium text-indigo-200 rounded-lg hover:bg-indigo-800 hover:text-white">Dashboard</a>
|
||||
<a href="/merchants/billing/subscriptions" class="flex items-center px-3 py-2 text-sm font-medium text-indigo-200 rounded-lg hover:bg-indigo-800 hover:text-white">Subscriptions</a>
|
||||
<a href="/merchants/billing/billing" class="flex items-center px-3 py-2 text-sm font-medium text-indigo-200 rounded-lg hover:bg-indigo-800 hover:text-white">Billing History</a>
|
||||
<a href="/merchants/account/stores" class="flex items-center px-3 py-2 text-sm font-medium text-indigo-200 rounded-lg hover:bg-indigo-800 hover:text-white">Stores</a>
|
||||
<a href="/merchants/account/profile" class="flex items-center px-3 py-2 text-sm font-medium text-indigo-200 rounded-lg hover:bg-indigo-800 hover:text-white">Profile</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="container px-6 py-8 mx-auto">
|
||||
<!-- Main Content -->
|
||||
<main class="h-full overflow-y-auto">
|
||||
<div class="container px-6 mx-auto grid">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"></script>
|
||||
<!-- Core Scripts - ORDER MATTERS! -->
|
||||
|
||||
<!-- Base merchant app data -->
|
||||
<!-- 1. FIRST: Log Configuration -->
|
||||
<script src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
|
||||
|
||||
<!-- 2. SECOND: Icons (before Alpine.js) -->
|
||||
<script src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
|
||||
|
||||
<!-- 3. THIRD: Alpine.js Base Data -->
|
||||
<script src="{{ url_for('core_static', path='merchant/js/init-alpine.js') }}"></script>
|
||||
|
||||
<!-- 4. FOURTH: Utils (standalone utilities) -->
|
||||
<script src="{{ url_for('static', path='shared/js/utils.js') }}"></script>
|
||||
|
||||
<!-- 5. FIFTH: API Client (depends on Utils) -->
|
||||
<script src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
|
||||
|
||||
<!-- 6. SIXTH: Alpine.js v3 with CDN fallback (with defer) -->
|
||||
<script>
|
||||
function merchantApp() {
|
||||
return {
|
||||
sidebarOpen: false,
|
||||
currentPath: window.location.pathname,
|
||||
merchantName: '',
|
||||
(function() {
|
||||
var script = document.createElement('script');
|
||||
script.defer = true;
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
|
||||
|
||||
init() {
|
||||
// Load merchant name from token/cookie
|
||||
const token = this.getToken();
|
||||
if (token) {
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
this.merchantName = payload.merchant_name || payload.sub || 'Merchant';
|
||||
} catch (e) {
|
||||
this.merchantName = 'Merchant';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getToken() {
|
||||
// Read merchant_token from cookie
|
||||
const match = document.cookie.match(/(?:^|;\s*)merchant_token=([^;]*)/);
|
||||
return match ? decodeURIComponent(match[1]) : null;
|
||||
},
|
||||
|
||||
logout() {
|
||||
// Clear merchant_token cookie
|
||||
document.cookie = 'merchant_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
window.location.href = '/merchants/login';
|
||||
}
|
||||
script.onerror = function() {
|
||||
console.warn('Alpine.js CDN failed, loading local copy...');
|
||||
var fallbackScript = document.createElement('script');
|
||||
fallbackScript.defer = true;
|
||||
fallbackScript.src = '{{ url_for("static", path="shared/js/lib/alpine.min.js") }}';
|
||||
document.head.appendChild(fallbackScript);
|
||||
};
|
||||
}
|
||||
|
||||
document.head.appendChild(script);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- 7. LAST: Page-specific scripts -->
|
||||
{% block extra_scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
70
app/templates/merchant/partials/header.html
Normal file
70
app/templates/merchant/partials/header.html
Normal file
@@ -0,0 +1,70 @@
|
||||
{# app/templates/merchant/partials/header.html #}
|
||||
<header class="z-10 py-4 bg-white shadow-md dark:bg-gray-800">
|
||||
<div class="container flex items-center justify-between h-full px-6 mx-auto text-purple-600 dark:text-purple-300">
|
||||
<!-- Mobile hamburger -->
|
||||
<button class="p-1 mr-5 -ml-1 rounded-md md:hidden focus:outline-none focus:shadow-outline-purple"
|
||||
@click="toggleSideMenu"
|
||||
aria-label="Menu">
|
||||
<span x-html="$icon('menu', 'w-6 h-6')"></span>
|
||||
</button>
|
||||
|
||||
<!-- Spacer -->
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<ul class="flex items-center flex-shrink-0 space-x-6">
|
||||
<!-- Theme toggler -->
|
||||
<li class="flex">
|
||||
<button class="rounded-md focus:outline-none focus:shadow-outline-purple"
|
||||
@click="toggleTheme"
|
||||
aria-label="Toggle color mode">
|
||||
<template x-if="!dark">
|
||||
<span x-html="$icon('moon', 'w-5 h-5')"></span>
|
||||
</template>
|
||||
<template x-if="dark">
|
||||
<span x-html="$icon('sun', 'w-5 h-5')"></span>
|
||||
</template>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- Profile menu -->
|
||||
<li class="relative" x-data="{ profileOpen: false }">
|
||||
<button class="align-middle rounded-full focus:shadow-outline-purple focus:outline-none"
|
||||
@click="profileOpen = !profileOpen"
|
||||
@keydown.escape="profileOpen = false"
|
||||
aria-label="Account"
|
||||
aria-haspopup="true">
|
||||
<div class="w-8 h-8 rounded-full bg-purple-600 flex items-center justify-center text-white font-semibold">
|
||||
<span x-text="merchantName?.charAt(0).toUpperCase() || '?'"></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<ul x-show="profileOpen"
|
||||
x-cloak
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click.away="profileOpen = false"
|
||||
@keydown.escape="profileOpen = false"
|
||||
class="absolute right-0 w-56 p-2 mt-2 space-y-2 text-gray-600 bg-white border border-gray-100 rounded-md shadow-md dark:border-gray-700 dark:text-gray-300 dark:bg-gray-700 z-50"
|
||||
style="display: none;"
|
||||
aria-label="submenu">
|
||||
<li class="flex">
|
||||
<a class="inline-flex items-center w-full px-2 py-1 text-sm font-semibold transition-colors duration-150 rounded-md hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-200"
|
||||
href="/merchants/account/profile">
|
||||
<span x-html="$icon('user', 'w-4 h-4 mr-3')"></span>
|
||||
<span>Profile</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="flex">
|
||||
<button
|
||||
@click="handleLogout()"
|
||||
class="inline-flex items-center w-full px-2 py-1 text-sm font-semibold transition-colors duration-150 rounded-md hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-200 text-left">
|
||||
<span x-html="$icon('logout', 'w-4 h-4 mr-3')"></span>
|
||||
<span>Log out</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
127
app/templates/merchant/partials/sidebar.html
Normal file
127
app/templates/merchant/partials/sidebar.html
Normal file
@@ -0,0 +1,127 @@
|
||||
{# app/templates/merchant/partials/sidebar.html #}
|
||||
{# Collapsible sidebar sections with localStorage persistence - matching store pattern #}
|
||||
|
||||
{# ============================================================================
|
||||
REUSABLE MACROS FOR SIDEBAR ITEMS
|
||||
============================================================================ #}
|
||||
|
||||
{# Macro for collapsible section header #}
|
||||
{% macro section_header(title, section_key, icon=none) %}
|
||||
<div class="px-6 my-4">
|
||||
<hr class="border-gray-200 dark:border-gray-700" />
|
||||
</div>
|
||||
<button
|
||||
@click="toggleSection('{{ section_key }}')"
|
||||
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">
|
||||
{% if icon %}
|
||||
<span x-html="$icon('{{ icon }}', 'w-4 h-4 mr-2 text-gray-400')"></span>
|
||||
{% endif %}
|
||||
{{ title }}
|
||||
</span>
|
||||
<span
|
||||
x-html="$icon('chevron-down', 'w-4 h-4 transition-transform duration-200')"
|
||||
:class="{ 'rotate-180': openSections.{{ section_key }} }"
|
||||
></span>
|
||||
</button>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro for collapsible section content wrapper #}
|
||||
{% macro section_content(section_key) %}
|
||||
<ul
|
||||
x-show="openSections.{{ section_key }}"
|
||||
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"
|
||||
>
|
||||
{{ caller() }}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
|
||||
{# Macro for menu item - uses static href (no storeCode needed) #}
|
||||
{% macro menu_item(page_id, path, icon, label) %}
|
||||
<li class="relative px-6 py-3">
|
||||
<span x-show="currentPage === '{{ page_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 === '{{ page_id }}' ? 'text-gray-800 dark:text-gray-100' : ''"
|
||||
href="{{ path }}">
|
||||
<span x-html="$icon('{{ icon }}', 'w-5 h-5')"></span>
|
||||
<span class="ml-4">{{ label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
{# ============================================================================
|
||||
SIDEBAR CONTENT (shared between desktop and mobile)
|
||||
============================================================================ #}
|
||||
|
||||
{% macro sidebar_content() %}
|
||||
<div class="py-4 text-gray-500 dark:text-gray-400">
|
||||
<!-- Merchant Branding -->
|
||||
<a class="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200 flex items-center"
|
||||
href="/merchants/dashboard">
|
||||
<span x-html="$icon('lightning-bolt', 'w-6 h-6 mr-2 text-purple-600')"></span>
|
||||
<span>Merchant Portal</span>
|
||||
</a>
|
||||
|
||||
<!-- Dashboard (always visible) -->
|
||||
<ul class="mt-6">
|
||||
{{ menu_item('dashboard', '/merchants/dashboard', 'home', 'Dashboard') }}
|
||||
</ul>
|
||||
|
||||
<!-- Billing Section -->
|
||||
{{ section_header('Billing', 'billing', 'credit-card') }}
|
||||
{% call section_content('billing') %}
|
||||
{{ menu_item('subscriptions', '/merchants/billing/subscriptions', 'clipboard-list', 'Subscriptions') }}
|
||||
{{ menu_item('billing', '/merchants/billing/billing', 'currency-euro', 'Billing History') }}
|
||||
{% endcall %}
|
||||
|
||||
<!-- Account Section -->
|
||||
{{ section_header('Account', 'account', 'cog') }}
|
||||
{% call section_content('account') %}
|
||||
{{ menu_item('stores', '/merchants/account/stores', 'shopping-bag', 'Stores') }}
|
||||
{{ menu_item('profile', '/merchants/account/profile', 'user', 'Profile') }}
|
||||
{% endcall %}
|
||||
</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>
|
||||
Reference in New Issue
Block a user