AkurAI Build
Menu

Public repository / dc540296357442733693769e22cd6944f7c2a721

AkurAI-Mail / src/routes/(admin)/+layout.svelte

Latest change dc540296357442733693769e22cd6944f7c2a721 - refactor: split admin UI into separate routes with shared sidebar layout by Ólafur Búi Ólafsson

<script lang="ts">
  import { page } from "$app/stores";
  let { data, children } = $props();
  let hostname = $derived(data.hostname);
  let currentPath = $derived($page.url.pathname);

  const navItems = [
    { href: "/", label: "Dashboard" },
    { href: "/mailboxes", label: "Mailboxes" },
    { href: "/domains", label: "Domains" },
    { href: "/antispam", label: "Anti-spam" },
    { href: "/dns", label: "DNS" },
    { href: "/status", label: "VM Status" },
    { href: "/webmail", label: "Webmail" }
  ];

  function isActive(href: string) {
    if (href === "/") return currentPath === "/";
    return currentPath.startsWith(href);
  }
</script>

<main class="shell">
  <aside class="rail">
    <a class="brand" href="/" aria-label="AkurAI Mail admin home">
      <span class="mark">A</span>
      <span><strong>AkurAI Mail</strong><small>{hostname}</small></span>
    </a>
    <nav aria-label="Primary">
      {#each navItems as item}
        <a class:active={isActive(item.href)} href={item.href}>{item.label}</a>
      {/each}
    </nav>
    <a class="signout" href="/logout">Sign out</a>
  </aside>

  <section class="workspace">
    {@render children()}
  </section>
</main>

<style>
  :global(*){box-sizing:border-box}
  :global(html),:global(body){margin:0;overflow-x:hidden}
  :global(body){font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#f4f6f5;color:#151d1b}
  :global(button),:global(input),:global(select){font:inherit}
  .shell{min-height:100dvh;display:grid;grid-template-columns:248px minmax(0,1fr)}
  .rail{position:sticky;top:0;height:100dvh;background:#0d1b18;color:#edf7f3;padding:20px;display:flex;flex-direction:column;gap:20px}
  .brand{display:flex;gap:12px;align-items:center;color:inherit;text-decoration:none;min-width:0}
  .brand span:last-child{min-width:0}.brand strong,.brand small{display:block;overflow-wrap:anywhere}.brand small{margin-top:2px;color:#96aaa3;font-size:12px}
  .mark{display:grid;place-items:center;flex:0 0 auto;width:40px;height:40px;border-radius:8px;background:#c8f05a;color:#0d1b18;font-weight:900}
  nav{display:grid;gap:4px}nav a,.signout{color:#dce9e4;text-decoration:none;border-radius:6px;padding:10px 11px;min-height:44px;display:flex;align-items:center}
  nav a:hover,.signout:hover,nav a.active{background:#1c332e}.signout{margin-top:auto;color:#b8c9c3}
  .workspace{padding:26px;display:grid;gap:18px;min-width:0;align-content:start}
  @media(max-width:760px){.shell{grid-template-columns:minmax(0,1fr)}.rail{position:static;height:auto;padding:14px 16px;gap:12px}.brand small{font-size:12px}nav{display:flex;overflow-x:auto;gap:6px;scrollbar-width:none}nav::-webkit-scrollbar{display:none}nav a{white-space:nowrap;background:rgba(255,255,255,.06)}.signout{margin-top:0}.workspace{padding:16px}}
</style>