180 lines
14 KiB
HTML
180 lines
14 KiB
HTML
<!doctype html>
|
|
<html lang="{{ lang | default(value='sk') }}" data-theme="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{% block title %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock title %}</title>
|
|
<meta name="description" content="{% block meta_description %}{{ t(key="meta-description", lang=lang | default(value='sk')) }}{% endblock meta_description %}">
|
|
<link rel="icon" type="image/x-icon" href="/static/favicon/favicon.ico">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon/favicon-16x16.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png">
|
|
<link rel="manifest" href="/static/favicon/site.webmanifest">
|
|
<script>
|
|
function applyTheme(t) {
|
|
var dark = t === 'dark'
|
|
|| (t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
|
}
|
|
function setTheme(t) {
|
|
localStorage.setItem('theme', t);
|
|
applyTheme(t);
|
|
document.dispatchEvent(new CustomEvent('theme:changed', { detail: t }));
|
|
}
|
|
function currentTheme() { return localStorage.getItem('theme') || 'dark'; }
|
|
applyTheme(currentTheme());
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
|
|
if (currentTheme() === 'system') applyTheme('system');
|
|
});
|
|
function markActiveNav() {
|
|
var path = location.pathname;
|
|
document.querySelectorAll('a[data-nav]').forEach(function (a) {
|
|
var h = a.getAttribute('data-nav');
|
|
var on = h === path || (h !== '/' && path.indexOf(h) === 0);
|
|
if (on) a.setAttribute('aria-current', 'page');
|
|
else a.removeAttribute('aria-current');
|
|
});
|
|
}
|
|
document.addEventListener('DOMContentLoaded', markActiveNav);
|
|
document.addEventListener('htmx:afterSwap', markActiveNav);
|
|
</script>
|
|
<link href="/static/css/app.css?v=2026-06-16" rel="stylesheet" type="text/css">
|
|
{% block head %}{% endblock head %}
|
|
<script src="/static/vendor/htmx/htmx-1.9.12.min.js"></script>
|
|
<script defer src="/static/vendor/alpine/alpinejs-3.14.9.min.js"></script>
|
|
</head>
|
|
<body
|
|
x-data="{ showSidebar: false }"
|
|
class="min-h-screen bg-surface text-on-surface antialiased dark:bg-surface-dark dark:text-on-surface-dark">
|
|
|
|
<!-- dark overlay for the open sidebar on small screens -->
|
|
<div x-cloak x-show="showSidebar" x-transition.opacity aria-hidden="true"
|
|
@click="showSidebar = false"
|
|
class="fixed inset-0 z-30 bg-black/50 md:hidden"></div>
|
|
|
|
<!-- sidebar -->
|
|
<nav aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
|
x-bind:class="showSidebar ? 'translate-x-0' : '-translate-x-60'"
|
|
class="fixed inset-y-0 left-0 z-40 flex w-60 flex-col border-r border-outline bg-surface-alt transition-transform duration-300 md:translate-x-0 dark:border-outline-dark dark:bg-surface-dark-alt">
|
|
|
|
{# Sidebar nav links — adapted from the vendored Penguin UI component
|
|
assets/views/penguinui/sidebar/simple-sidebar.html: Penguin's link
|
|
treatment (hover:bg-primary/5, focus-visible:underline) with the active
|
|
state (bg-primary/10 + text-on-surface-strong) mapped onto our
|
|
data-nav / aria-current so markActiveNav() keeps driving it. #}
|
|
<a href="/admin/dashboard"
|
|
class="flex h-16 items-center gap-2 border-b border-outline px-6 text-lg font-bold tracking-tight text-on-surface-strong dark:border-outline-dark dark:text-on-surface-dark-strong">
|
|
{{ t(key="admin-title", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
|
|
<div class="flex flex-1 flex-col gap-1 overflow-y-auto p-4">
|
|
<a href="/admin/dashboard" data-nav="/admin/dashboard"
|
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
|
{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
<a href="/admin/catalog/products" data-nav="/admin/catalog/products"
|
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
|
{{ t(key="admin-products", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
<a href="/admin/catalog/categories" data-nav="/admin/catalog/categories"
|
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
|
{{ t(key="admin-categories", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
<a href="/admin/orders" data-nav="/admin/orders"
|
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
|
{{ t(key="admin-orders", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
<a href="/admin/shipping" data-nav="/admin/shipping"
|
|
class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-on-surface underline-offset-2 transition hover:bg-primary/5 hover:text-on-surface-strong focus:outline-hidden focus-visible:underline aria-[current=page]:bg-primary/10 aria-[current=page]:text-on-surface-strong dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong dark:aria-[current=page]:bg-primary-dark/10 dark:aria-[current=page]:text-on-surface-dark-strong">
|
|
{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
</div>
|
|
|
|
<div class="border-t border-outline p-4 dark:border-outline-dark">
|
|
<a href="/" class="flex items-center gap-2 rounded-radius px-2 py-1.5 text-sm font-medium text-info underline-offset-2 transition hover:bg-info/5 focus:outline-hidden focus-visible:underline">
|
|
{{ t(key="admin-exit", lang=lang | default(value='sk')) }}
|
|
</a>
|
|
<form method="post" action="/admin/logout">
|
|
<button type="submit" class="flex w-full items-center gap-2 rounded-radius px-2 py-1.5 text-left text-sm font-medium text-danger underline-offset-2 transition hover:bg-danger/5 focus:outline-hidden focus-visible:underline">
|
|
{{ t(key="logout", lang=lang | default(value='sk')) }}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- content column -->
|
|
<div class="flex min-h-screen flex-col md:ml-60">
|
|
<header class="sticky top-0 z-20 flex h-16 items-center gap-4 border-b border-outline bg-surface/95 px-4 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
|
|
<button type="button" @click="showSidebar = !showSidebar" :aria-expanded="showSidebar"
|
|
aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
|
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt md:hidden dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
|
stroke="currentColor" class="size-6">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
|
</svg>
|
|
</button>
|
|
|
|
<span class="text-sm font-semibold text-on-surface-strong dark:text-on-surface-dark-strong">
|
|
{% block crumb %}{{ t(key="admin-title", lang=lang | default(value='sk')) }}{% endblock crumb %}
|
|
</span>
|
|
|
|
<!-- settings (language + theme) dropdown -->
|
|
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative ml-auto">
|
|
<button type="button" @click="open = !open" :aria-expanded="open"
|
|
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
|
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
|
stroke="currentColor" class="size-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round"
|
|
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
</svg>
|
|
</button>
|
|
<div x-show="open" x-cloak @click.outside="open = false" x-transition.origin.top.right
|
|
class="absolute right-0 mt-2 w-56 rounded-radius border border-outline bg-surface p-2 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt">
|
|
<form method="post" action="/lang" hx-boost="false">
|
|
<p class="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
|
|
{{ t(key="settings-language", lang=lang | default(value='sk')) }}
|
|
</p>
|
|
<button type="submit" name="lang" value="en"
|
|
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
|
<span>English</span>
|
|
{% if lang | default(value='sk') == "en" %}<span class="text-primary dark:text-primary-dark">✓</span>{% endif %}
|
|
</button>
|
|
<button type="submit" name="lang" value="sk"
|
|
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
|
<span>Slovenčina</span>
|
|
{% if lang | default(value='sk') == "sk" %}<span class="text-primary dark:text-primary-dark">✓</span>{% endif %}
|
|
</button>
|
|
</form>
|
|
<p class="mt-1 px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
|
|
{{ t(key="settings-theme", lang=lang | default(value='sk')) }}
|
|
</p>
|
|
<div x-data="{ theme: currentTheme() }" @theme:changed.document="theme = $event.detail">
|
|
<button type="button" @click="setTheme('system')"
|
|
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
|
<span>{{ t(key="theme-system", lang=lang | default(value='sk')) }}</span>
|
|
<span x-show="theme === 'system'" class="text-primary dark:text-primary-dark">✓</span>
|
|
</button>
|
|
<button type="button" @click="setTheme('light')"
|
|
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
|
<span>{{ t(key="theme-light", lang=lang | default(value='sk')) }}</span>
|
|
<span x-show="theme === 'light'" class="text-primary dark:text-primary-dark">✓</span>
|
|
</button>
|
|
<button type="button" @click="setTheme('dark')"
|
|
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
|
<span>{{ t(key="theme-dark", lang=lang | default(value='sk')) }}</span>
|
|
<span x-show="theme === 'dark'" class="text-primary dark:text-primary-dark">✓</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="mx-auto w-full max-w-5xl flex-1 px-4 py-8">
|
|
{% block content %}{% endblock content %}
|
|
</main>
|
|
</div>
|
|
</body>
|
|
</html>
|