Some checks failed
- Add cookie to ADMIN resolution chain (cookie → user_pref → "en") - Add explicit MERCHANT resolution (cookie → user_pref → "fr") - Add language selector dropdown to admin and merchant headers - Add languageSelector() function to merchant init-alpine.js - Add flag-icons CSS and i18n.js setup to merchant base template - Add compact flag-based language selector to both login pages - Make lang attribute dynamic on all base and login templates - Pass current_language to login route template context - Update architecture doc with ADMIN/MERCHANT resolution priorities Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
179 lines
6.9 KiB
HTML
179 lines
6.9 KiB
HTML
{# app/templates/admin/base.html #}
|
|
<!DOCTYPE html>
|
|
<html :class="{ 'dark': dark }" x-data="{% block alpine_data %}data(){% endblock %}" lang="{{ current_language|default('en') }}">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>{% block title %}Admin Panel{% endblock %} - Multi-Tenant Platform</title>
|
|
|
|
<!-- 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 v4 (built locally via standalone CLI) -->
|
|
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
|
|
|
<!-- Flag Icons CSS (for language selector) with local fallback -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"
|
|
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/store/flag-icons.min.css') }}';"
|
|
/>
|
|
|
|
<!-- Alpine Cloak -->
|
|
<style>
|
|
[x-cloak] { display: none !important; }
|
|
</style>
|
|
|
|
<!-- Tom Select CSS with CDN fallback -->
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/tom-select@2.4.1/dist/css/tom-select.default.min.css"
|
|
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/store/tom-select.default.min.css') }}';"
|
|
/>
|
|
<!-- Tom Select Dark Mode Overrides -->
|
|
<style>
|
|
.dark .ts-wrapper .ts-control {
|
|
background-color: rgb(55 65 81);
|
|
border-color: rgb(75 85 99);
|
|
color: rgb(209 213 219);
|
|
}
|
|
.dark .ts-wrapper .ts-control input {
|
|
color: rgb(209 213 219);
|
|
}
|
|
.dark .ts-wrapper .ts-control input::placeholder {
|
|
color: rgb(156 163 175);
|
|
}
|
|
.dark .ts-dropdown {
|
|
background-color: rgb(55 65 81);
|
|
border-color: rgb(75 85 99);
|
|
color: rgb(209 213 219);
|
|
}
|
|
.dark .ts-dropdown .option {
|
|
color: rgb(209 213 219);
|
|
}
|
|
.dark .ts-dropdown .option.active {
|
|
background-color: rgb(147 51 234);
|
|
color: white;
|
|
}
|
|
.dark .ts-dropdown .option:hover {
|
|
background-color: rgb(75 85 99);
|
|
}
|
|
.dark .ts-wrapper.focus .ts-control {
|
|
border-color: rgb(147 51 234);
|
|
box-shadow: 0 0 0 1px rgb(147 51 234);
|
|
}
|
|
.dark .ts-dropdown .no-results,
|
|
.dark .ts-dropdown .loading {
|
|
color: rgb(156 163 175);
|
|
}
|
|
</style>
|
|
|
|
<!-- Flatpickr CSS with CDN fallback (loaded on demand via block) -->
|
|
{% block flatpickr_css %}{% endblock %}
|
|
|
|
<!-- Quill CSS with CDN fallback (loaded on demand via block) -->
|
|
{% block quill_css %}{% endblock %}
|
|
|
|
{% block extra_head %}{% endblock %}
|
|
</head>
|
|
<body x-cloak>
|
|
<div class="flex h-screen bg-gray-50 dark:bg-gray-900" :class="{ 'overflow-hidden': isSideMenuOpen }">
|
|
<!-- Sidebar (server-side included) -->
|
|
{% include 'admin/partials/sidebar.html' %}
|
|
|
|
<div class="flex flex-col flex-1 w-full">
|
|
<!-- Header (server-side included) -->
|
|
{% include 'admin/partials/header.html' %}
|
|
|
|
<!-- Main Content -->
|
|
<main class="h-full overflow-y-auto">
|
|
<div class="container px-6 mx-auto grid">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Core Scripts - ORDER MATTERS! -->
|
|
|
|
<!-- 1. FIRST: Log Configuration -->
|
|
<script defer src="{{ url_for('static', path='shared/js/log-config.js') }}"></script>
|
|
|
|
<!-- 2. SECOND: Icons (before Alpine.js) -->
|
|
<script defer src="{{ url_for('static', path='shared/js/icons.js') }}"></script>
|
|
|
|
<!-- 3. THIRD: Alpine.js Base Data -->
|
|
<script src="{{ url_for('core_static', path='admin/js/init-alpine.js') }}"></script>
|
|
|
|
<!-- 4. FOURTH: Utils (standalone utilities) -->
|
|
<script defer src="{{ url_for('static', path='shared/js/utils.js') }}"></script>
|
|
|
|
<!-- 4b. i18n Support -->
|
|
<script defer src="{{ url_for('static', path='shared/js/i18n.js') }}"></script>
|
|
<script>
|
|
// Initialize i18n with current language and preload modules
|
|
// Wrapped in DOMContentLoaded so deferred i18n.js has loaded
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
const modules = {% block i18n_modules %}[]{% endblock %};
|
|
await I18n.init('{{ current_language | default("en") }}', modules);
|
|
});
|
|
</script>
|
|
|
|
<!-- 5. FIFTH: API Client (depends on Utils) -->
|
|
<script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
|
|
|
|
<!-- 6. SIXTH: Tom Select with CDN fallback -->
|
|
<script>
|
|
(function() {
|
|
var script = document.createElement('script');
|
|
script.src = 'https://cdn.jsdelivr.net/npm/tom-select@2.4.1/dist/js/tom-select.complete.min.js';
|
|
script.onerror = function() {
|
|
console.warn('Tom Select CDN failed, loading local copy...');
|
|
var fallbackScript = document.createElement('script');
|
|
fallbackScript.src = '{{ url_for("static", path="shared/js/lib/tom-select.complete.min.js") }}';
|
|
document.head.appendChild(fallbackScript);
|
|
};
|
|
document.head.appendChild(script);
|
|
})();
|
|
</script>
|
|
|
|
<!-- 7. SEVENTH: Store Selector (depends on Tom Select and API Client) -->
|
|
<script defer src="{{ url_for('core_static', path='shared/js/store-selector.js') }}"></script>
|
|
|
|
<!-- 8a. Alpine.js Collapse Plugin (must load before Alpine) -->
|
|
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.13.3/dist/cdn.min.js"></script>
|
|
|
|
<!-- 8b. Alpine.js v3 with CDN fallback (with defer) -->
|
|
<script>
|
|
(function() {
|
|
var script = document.createElement('script');
|
|
script.defer = true;
|
|
script.src = 'https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js';
|
|
|
|
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>
|
|
|
|
<!-- 9. OPTIONAL: Chart.js with CDN fallback (loaded on demand via block) -->
|
|
{% block chartjs_script %}{% endblock %}
|
|
|
|
<!-- 10. OPTIONAL: Flatpickr with CDN fallback (loaded on demand via block) -->
|
|
{% block flatpickr_script %}{% endblock %}
|
|
|
|
<!-- 11. OPTIONAL: Quill with CDN fallback (loaded on demand via block) -->
|
|
{% block quill_script %}{% endblock %}
|
|
|
|
<!-- 12. LAST: Page-specific scripts -->
|
|
{% block extra_scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|