Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
765 lines
31 KiB
Elixir
765 lines
31 KiB
Elixir
defmodule TarakanWeb.Layouts do
|
|
@moduledoc """
|
|
This module holds layouts and related functionality
|
|
used by your application.
|
|
"""
|
|
use TarakanWeb, :html
|
|
|
|
# Embed all files in layouts/* within this module.
|
|
# The default root.html.heex file contains the HTML
|
|
# skeleton of your application, namely HTML headers
|
|
# and other static content.
|
|
embed_templates "layouts/*"
|
|
|
|
@doc """
|
|
The Tarakan mark: a blade asterisk, six blades off one centre at (33,33),
|
|
every outer tip a point. The spine is the heavy axis (full width y=18..48);
|
|
the four diagonals carry their widest section a third of the way out and
|
|
converge at the centre, which the spine keeps solid.
|
|
"""
|
|
attr :class, :any, default: "h-6 w-6"
|
|
|
|
def logo_mark(assigns) do
|
|
~H"""
|
|
<svg
|
|
class={@class}
|
|
viewBox="0 0 66 66"
|
|
fill="currentColor"
|
|
aria-hidden="true"
|
|
data-tarakan-logo
|
|
>
|
|
<polygon points="33,0 41,18 41,48 33,66 25,48 25,18" />
|
|
<polygon points="0,0 18,26 33,33 26,18" />
|
|
<polygon points="66,0 48,26 33,33 40,18" />
|
|
<polygon points="0,66 18,40 33,33 26,48" />
|
|
<polygon points="66,66 48,40 33,33 40,48" />
|
|
</svg>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Shared page shell: gutters, max width, and vertical padding under the nav.
|
|
|
|
All app pages should use this so spacing stays consistent. Pass `class` only
|
|
for extras (e.g. `space-y-5`), not for competing padding.
|
|
"""
|
|
attr :id, :string, default: nil
|
|
attr :width, :atom, default: :wide, values: [:wide, :focused, :compact, :form]
|
|
attr :class, :any, default: nil
|
|
attr :rest, :global
|
|
slot :inner_block, required: true
|
|
|
|
def page(assigns) do
|
|
~H"""
|
|
<div
|
|
id={@id}
|
|
class={
|
|
[
|
|
"mx-auto w-full min-w-0 px-4 sm:px-8",
|
|
# Consistent clearance from sticky nav + bottom breathing room.
|
|
"pt-6 pb-10 sm:pt-10 sm:pb-12",
|
|
@width == :wide && "max-w-[90rem]",
|
|
@width == :focused && "max-w-3xl",
|
|
@width == :compact && "max-w-xl",
|
|
@width == :form && "max-w-md",
|
|
@class
|
|
]
|
|
}
|
|
{@rest}
|
|
>
|
|
{render_slot(@inner_block)}
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Renders your app layout.
|
|
|
|
This function is typically invoked from every template,
|
|
and it often contains your application menu, sidebar,
|
|
or similar.
|
|
|
|
## Examples
|
|
|
|
<Layouts.app flash={@flash}>
|
|
<h1>Content</h1>
|
|
</Layouts.app>
|
|
|
|
"""
|
|
attr :flash, :map, required: true, doc: "the map of flash messages"
|
|
|
|
attr :current_scope, :map,
|
|
default: nil,
|
|
doc: "the current [scope](https://phoenix.hexdocs.pm/scopes.html)"
|
|
|
|
attr :current_path, :string,
|
|
default: nil,
|
|
doc: "request path, supplied by TarakanWeb.CurrentPath; drives the nav highlight"
|
|
|
|
slot :inner_block, required: true
|
|
|
|
def app(assigns) do
|
|
~H"""
|
|
<div class="min-h-dvh min-w-0 overflow-x-clip bg-ground text-ink antialiased">
|
|
<header
|
|
id="site-header"
|
|
class="sticky top-0 z-40 border-b-2 border-strong bg-ground pt-[env(safe-area-inset-top)]"
|
|
>
|
|
<div
|
|
aria-hidden="true"
|
|
class="pointer-events-none absolute inset-x-0 -bottom-0.5 h-0.5 bg-gradient-to-r from-signal via-signal/40 to-transparent"
|
|
>
|
|
</div>
|
|
|
|
<div class="flex h-14 w-full min-w-0 items-center gap-2 px-3 md:items-stretch md:gap-0 md:px-0">
|
|
<%!-- Brand --%>
|
|
<.link
|
|
navigate={~p"/"}
|
|
aria-label="Tarakan home"
|
|
class="flex h-9 shrink-0 items-center gap-2 md:h-auto md:border-r-2 md:border-strong md:px-8"
|
|
>
|
|
<span class="font-display text-sm font-bold uppercase tracking-[0.12em] text-ink md:text-base md:tracking-[0.12em]">
|
|
Tarakan
|
|
</span>
|
|
</.link>
|
|
|
|
<%!-- Desktop primary nav --%>
|
|
<nav
|
|
aria-label="Primary"
|
|
class={[
|
|
"hidden min-w-0 flex-1 items-stretch overflow-x-auto overscroll-x-contain md:flex",
|
|
"[-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
]}
|
|
>
|
|
<.nav_link path={~p"/explore"} current={@current_path} label="Explore" />
|
|
<.nav_link path={~p"/infestations"} current={@current_path} label="Infestations" />
|
|
<.nav_link path={~p"/jobs"} current={@current_path} label="Jobs" />
|
|
<.nav_link path={~p"/bounties"} current={@current_path} label="Contracts" />
|
|
<.nav_link path={~p"/agents"} current={@current_path} label="Agents" />
|
|
<.nav_link path={~p"/leaderboard"} current={@current_path} label="Leaderboard" />
|
|
<.nav_link path={~p"/models"} current={@current_path} label="Models" />
|
|
<.nav_link path={~p"/pricing"} current={@current_path} label="Pricing" />
|
|
</nav>
|
|
|
|
<%!-- Desktop utilities --%>
|
|
<div class="ml-auto hidden h-14 items-stretch md:flex">
|
|
<div
|
|
id="theme-toggle"
|
|
class="flex items-stretch border-l-2 border-rule"
|
|
role="group"
|
|
aria-label="Color theme"
|
|
>
|
|
<button
|
|
type="button"
|
|
data-theme-option="light"
|
|
aria-pressed="false"
|
|
aria-label="Light theme"
|
|
phx-click={JS.dispatch("tarakan:set-theme", detail: %{theme: "light"})}
|
|
class="flex w-10 cursor-pointer items-center justify-center text-ink-faint transition hover:bg-panel hover:text-ink aria-pressed:bg-panel aria-pressed:text-ink"
|
|
>
|
|
<.icon name="hero-sun-micro" class="size-3.5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-theme-option="system"
|
|
aria-pressed="false"
|
|
aria-label="Follow system theme"
|
|
phx-click={JS.dispatch("tarakan:set-theme", detail: %{theme: "system"})}
|
|
class="flex w-10 cursor-pointer items-center justify-center text-ink-faint transition hover:bg-panel hover:text-ink aria-pressed:bg-panel aria-pressed:text-ink"
|
|
>
|
|
<.icon name="hero-computer-desktop-micro" class="size-3.5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-theme-option="dark"
|
|
aria-pressed="false"
|
|
aria-label="Dark theme"
|
|
phx-click={JS.dispatch("tarakan:set-theme", detail: %{theme: "dark"})}
|
|
class="flex w-10 cursor-pointer items-center justify-center text-ink-faint transition hover:bg-panel hover:text-ink aria-pressed:bg-panel aria-pressed:text-ink"
|
|
>
|
|
<.icon name="hero-moon-micro" class="size-3.5" />
|
|
</button>
|
|
</div>
|
|
|
|
<%= if @current_scope && @current_scope.account do %>
|
|
<details
|
|
id="current-account"
|
|
class="relative flex items-stretch"
|
|
phx-click-away={JS.remove_attribute("open", to: "#current-account")}
|
|
>
|
|
<summary class="flex cursor-pointer list-none items-center gap-1.5 border-l-2 border-rule px-5 font-mono text-xs text-ink-muted transition hover:bg-panel hover:text-ink [&::-webkit-details-marker]:hidden">
|
|
<span class="max-w-[16ch] truncate">@{@current_scope.account.handle}</span>
|
|
<.icon name="hero-chevron-down-micro" class="size-3 shrink-0 text-ink-faint" />
|
|
</summary>
|
|
<nav
|
|
aria-label="Account"
|
|
class="absolute right-0 top-full z-50 w-56 divide-y divide-rule border-2 border-strong bg-ground shadow-2xl"
|
|
>
|
|
<.link
|
|
id="header-profile"
|
|
navigate={"/" <> @current_scope.account.handle}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Your contributions
|
|
</.link>
|
|
<.link
|
|
navigate={~p"/accounts/settings"}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Settings
|
|
</.link>
|
|
<.link
|
|
navigate={~p"/accounts/billing"}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Billing
|
|
</.link>
|
|
<.link
|
|
navigate={~p"/alerts"}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Alerts
|
|
</.link>
|
|
<.link
|
|
id="header-report-content"
|
|
navigate={~p"/moderation/report"}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Report content
|
|
</.link>
|
|
<.link
|
|
:if={
|
|
@current_scope.account_state == "active" &&
|
|
@current_scope.platform_role in ["moderator", "admin"]
|
|
}
|
|
id="header-moderation-queue"
|
|
navigate={~p"/moderation/queue"}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Moderation queue
|
|
</.link>
|
|
<.link
|
|
:if={@current_scope.platform_role == "admin"}
|
|
id="header-admin-dashboard"
|
|
navigate={~p"/admin"}
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-signal transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Administration
|
|
</.link>
|
|
<.link
|
|
href={~p"/accounts/log-out"}
|
|
method="delete"
|
|
class="block px-4 py-2.5 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint transition hover:bg-panel hover:text-signal"
|
|
>
|
|
Sign out
|
|
</.link>
|
|
</nav>
|
|
</details>
|
|
<.link
|
|
id="header-add-repository"
|
|
navigate={~p"/repositories/new"}
|
|
class="inline-flex items-center gap-1.5 border-l-2 border-strong bg-btn px-5 font-display text-xs uppercase tracking-[0.12em] text-btn-fg transition hover:opacity-90"
|
|
>
|
|
<.icon name="hero-plus-micro" class="size-3.5" /> Add repository
|
|
</.link>
|
|
<% else %>
|
|
<.link
|
|
id="header-login"
|
|
navigate={~p"/accounts/log-in"}
|
|
class="inline-flex items-center border-l-2 border-rule px-5 font-display text-xs uppercase tracking-[0.12em] text-ink transition hover:bg-panel"
|
|
>
|
|
Sign in
|
|
</.link>
|
|
<.link
|
|
id="header-register"
|
|
navigate={~p"/accounts/register"}
|
|
class="inline-flex items-center border-l-2 border-strong bg-btn px-5 font-display text-xs uppercase tracking-[0.12em] text-btn-fg transition hover:opacity-90"
|
|
>
|
|
Join Tarakan
|
|
</.link>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%!-- Mobile: one primary action + menu --%>
|
|
<div class="ml-auto flex items-center gap-1.5 md:hidden">
|
|
<.link
|
|
:if={@current_scope && @current_scope.account}
|
|
id="header-add-repository-mobile"
|
|
navigate={~p"/repositories/new"}
|
|
class="inline-flex h-9 items-center gap-1 bg-btn px-3 font-display text-[11px] uppercase tracking-[0.12em] text-btn-fg transition hover:opacity-90"
|
|
>
|
|
<.icon name="hero-plus-micro" class="size-3.5" /> Add
|
|
</.link>
|
|
<.link
|
|
:if={is_nil(@current_scope) || is_nil(@current_scope.account)}
|
|
id="header-login-mobile"
|
|
navigate={~p"/accounts/log-in"}
|
|
class="inline-flex h-9 items-center border border-strong px-3 font-display text-[11px] uppercase tracking-[0.12em] text-ink transition hover:bg-panel"
|
|
>
|
|
Sign in
|
|
</.link>
|
|
|
|
<details
|
|
id="site-nav-mobile"
|
|
class="relative"
|
|
phx-click-away={JS.remove_attribute("open", to: "#site-nav-mobile")}
|
|
>
|
|
<summary
|
|
class="flex h-9 w-9 cursor-pointer list-none items-center justify-center border border-strong text-ink transition hover:bg-panel [&::-webkit-details-marker]:hidden"
|
|
aria-label="Open menu"
|
|
>
|
|
<.icon name="hero-bars-3-mini" class="size-5" />
|
|
</summary>
|
|
|
|
<div class="absolute right-0 top-[calc(100%+0.4rem)] z-50 w-[min(18.5rem,calc(100vw-1.5rem))] border-2 border-strong bg-ground shadow-2xl">
|
|
<nav aria-label="Site" class="divide-y divide-rule">
|
|
<.mobile_nav_link path={~p"/explore"} current={@current_path} label="Explore" />
|
|
<.mobile_nav_link
|
|
path={~p"/infestations"}
|
|
current={@current_path}
|
|
label="Infestations"
|
|
/>
|
|
<.mobile_nav_link path={~p"/jobs"} current={@current_path} label="Jobs" />
|
|
<.mobile_nav_link path={~p"/bounties"} current={@current_path} label="Contracts" />
|
|
<.mobile_nav_link path={~p"/agents"} current={@current_path} label="Agents" />
|
|
<.mobile_nav_link
|
|
path={~p"/leaderboard"}
|
|
current={@current_path}
|
|
label="Leaderboard"
|
|
/>
|
|
<.mobile_nav_link path={~p"/models"} current={@current_path} label="Models" />
|
|
<.mobile_nav_link path={~p"/pricing"} current={@current_path} label="Pricing" />
|
|
</nav>
|
|
|
|
<div
|
|
id="theme-toggle-mobile"
|
|
class="flex border-t-2 border-strong"
|
|
role="group"
|
|
aria-label="Color theme"
|
|
>
|
|
<button
|
|
type="button"
|
|
data-theme-option="light"
|
|
aria-pressed="false"
|
|
aria-label="Light theme"
|
|
phx-click={JS.dispatch("tarakan:set-theme", detail: %{theme: "light"})}
|
|
class="flex h-11 flex-1 cursor-pointer items-center justify-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint transition hover:bg-panel hover:text-ink aria-pressed:bg-panel aria-pressed:text-ink"
|
|
>
|
|
<.icon name="hero-sun-micro" class="size-3.5" /> Light
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-theme-option="system"
|
|
aria-pressed="false"
|
|
aria-label="Follow system theme"
|
|
phx-click={JS.dispatch("tarakan:set-theme", detail: %{theme: "system"})}
|
|
class="flex h-11 flex-1 cursor-pointer items-center justify-center gap-1.5 border-x border-rule font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint transition hover:bg-panel hover:text-ink aria-pressed:bg-panel aria-pressed:text-ink"
|
|
>
|
|
<.icon name="hero-computer-desktop-micro" class="size-3.5" /> Auto
|
|
</button>
|
|
<button
|
|
type="button"
|
|
data-theme-option="dark"
|
|
aria-pressed="false"
|
|
aria-label="Dark theme"
|
|
phx-click={JS.dispatch("tarakan:set-theme", detail: %{theme: "dark"})}
|
|
class="flex h-11 flex-1 cursor-pointer items-center justify-center gap-1.5 font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint transition hover:bg-panel hover:text-ink aria-pressed:bg-panel aria-pressed:text-ink"
|
|
>
|
|
<.icon name="hero-moon-micro" class="size-3.5" /> Dark
|
|
</button>
|
|
</div>
|
|
|
|
<div class="border-t-2 border-strong">
|
|
<%= if @current_scope && @current_scope.account do %>
|
|
<p class="px-4 py-2.5 font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint">
|
|
@{@current_scope.account.handle}
|
|
</p>
|
|
<.link
|
|
id="header-profile-mobile"
|
|
navigate={"/" <> @current_scope.account.handle}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Your contributions
|
|
</.link>
|
|
<.link
|
|
navigate={~p"/accounts/settings"}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Settings
|
|
</.link>
|
|
<.link
|
|
navigate={~p"/accounts/billing"}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Billing
|
|
</.link>
|
|
<.link
|
|
navigate={~p"/alerts"}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Alerts
|
|
</.link>
|
|
<.link
|
|
id="header-report-content-mobile"
|
|
navigate={~p"/moderation/report"}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Report content
|
|
</.link>
|
|
<.link
|
|
:if={
|
|
@current_scope.account_state == "active" &&
|
|
@current_scope.platform_role in ["moderator", "admin"]
|
|
}
|
|
id="header-moderation-queue-mobile"
|
|
navigate={~p"/moderation/queue"}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-muted transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Moderation queue
|
|
</.link>
|
|
<.link
|
|
:if={@current_scope.platform_role == "admin"}
|
|
id="header-admin-dashboard-mobile"
|
|
navigate={~p"/admin"}
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-signal transition hover:bg-panel hover:text-ink"
|
|
>
|
|
Administration
|
|
</.link>
|
|
<.link
|
|
href={~p"/accounts/log-out"}
|
|
method="delete"
|
|
class="block px-4 py-3 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint transition hover:bg-panel hover:text-signal"
|
|
>
|
|
Sign out
|
|
</.link>
|
|
<% else %>
|
|
<.link
|
|
id="header-register-mobile"
|
|
navigate={~p"/accounts/register"}
|
|
class="block bg-btn px-4 py-3.5 text-center font-mono text-[11px] uppercase tracking-[0.12em] text-btn-fg transition hover:opacity-90"
|
|
>
|
|
Join Tarakan
|
|
</.link>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="min-w-0">
|
|
{render_slot(@inner_block)}
|
|
</main>
|
|
|
|
<footer class="relative overflow-hidden border-t-2 border-strong pb-[env(safe-area-inset-bottom)]">
|
|
<%!-- Oversized mark bleeding off the corner. Kept in --rule so it reads as
|
|
surface texture, never as signal: red stays reserved for live state. --%>
|
|
<.logo_mark class="pointer-events-none absolute -bottom-28 -right-16 hidden size-[26rem] text-rule lg:block" />
|
|
|
|
<div class="relative mx-auto w-full max-w-[90rem] px-4 py-12 sm:px-8">
|
|
<div class="flex flex-col gap-10 lg:flex-row lg:justify-between lg:gap-16">
|
|
<div class="shrink-0">
|
|
<div class="flex items-center gap-4">
|
|
<.logo_mark class="size-12 shrink-0 text-ink sm:size-14" />
|
|
<p class="font-display text-3xl font-bold uppercase leading-none tracking-[0.12em] text-ink sm:text-4xl">
|
|
Tarakan
|
|
</p>
|
|
</div>
|
|
<p class="mt-6 font-mono text-[11px] uppercase tracking-[0.12em] text-ink-faint">
|
|
from
|
|
<a
|
|
id="footer-elektrine"
|
|
href="https://elektrine.com"
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
elektrine
|
|
</a>
|
|
</p>
|
|
</div>
|
|
|
|
<nav
|
|
aria-label="Footer"
|
|
class="grid grid-cols-2 gap-x-8 gap-y-8 text-sm sm:grid-cols-3 lg:gap-x-16"
|
|
>
|
|
<div>
|
|
<h2 class="font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint">
|
|
Registry
|
|
</h2>
|
|
<%!-- Mirrors the primary nav, in the same order. --%>
|
|
<ul class="mt-3 space-y-2">
|
|
<li>
|
|
<.link navigate={~p"/"} class="text-ink-muted transition hover:text-ink">
|
|
Registry
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link navigate={~p"/explore"} class="text-ink-muted transition hover:text-ink">
|
|
Explore
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/infestations"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Infestations
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link navigate={~p"/jobs"} class="text-ink-muted transition hover:text-ink">
|
|
Jobs
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link navigate={~p"/bounties"} class="text-ink-muted transition hover:text-ink">
|
|
Contracts
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link navigate={~p"/agents"} class="text-ink-muted transition hover:text-ink">
|
|
Agents
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/leaderboard"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Leaderboard
|
|
</.link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div>
|
|
<h2 class="font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint">
|
|
Platform
|
|
</h2>
|
|
<ul class="mt-3 space-y-2">
|
|
<li>
|
|
<.link navigate={~p"/pricing"} class="text-ink-muted transition hover:text-ink">
|
|
Pricing
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/services/disclosure"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Managed disclosure
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/policies/disclosure"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Disclosure policy
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/policies/content"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Content policy
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="/.well-known/security.txt"
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
security.txt
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="col-span-2 sm:col-span-1">
|
|
<h2 class="font-mono text-[10px] uppercase tracking-[0.12em] text-ink-faint">
|
|
Account
|
|
</h2>
|
|
<ul class="mt-3 space-y-2">
|
|
<%= if @current_scope && @current_scope.account do %>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/moderation/report"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Report content
|
|
</.link>
|
|
</li>
|
|
<li :if={
|
|
@current_scope.account_state == "active" &&
|
|
@current_scope.platform_role in ["moderator", "admin"]
|
|
}>
|
|
<.link
|
|
navigate={~p"/moderation/queue"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Moderation queue
|
|
</.link>
|
|
</li>
|
|
<li :if={@current_scope.platform_role == "admin"}>
|
|
<.link
|
|
id="footer-admin-dashboard"
|
|
navigate={~p"/admin"}
|
|
class={["text-signal transition hover:text-ink"]}
|
|
>
|
|
Administration
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/accounts/settings"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Settings
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
href={~p"/accounts/log-out"}
|
|
method="delete"
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Sign out
|
|
</.link>
|
|
</li>
|
|
<% else %>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/accounts/register"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Join Tarakan
|
|
</.link>
|
|
</li>
|
|
<li>
|
|
<.link
|
|
navigate={~p"/accounts/log-in"}
|
|
class="text-ink-muted transition hover:text-ink"
|
|
>
|
|
Sign in
|
|
</.link>
|
|
</li>
|
|
<% end %>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<.flash_group flash={@flash} />
|
|
</div>
|
|
"""
|
|
end
|
|
|
|
attr :path, :string, required: true
|
|
attr :current, :string, default: nil
|
|
attr :label, :string, required: true
|
|
|
|
# Server-rendered highlight. This used to be a JavaScript hook that toggled
|
|
# classes after paint, which meant every page rendered with nothing active for
|
|
# a frame, and dead routes like /pricing never highlighted at all because they
|
|
# have no LiveSocket to run the hook.
|
|
defp nav_link(assigns) do
|
|
assigns =
|
|
assign(assigns, :active, TarakanWeb.CurrentPath.active?(assigns.current, assigns.path))
|
|
|
|
~H"""
|
|
<.link
|
|
navigate={@path}
|
|
aria-current={@active && "page"}
|
|
class={[
|
|
"inline-flex shrink-0 items-center border-r-2 border-rule px-5 font-mono",
|
|
"text-[11px] uppercase tracking-[0.12em] transition hover:bg-panel hover:text-ink",
|
|
@active && "bg-panel text-ink shadow-[inset_0_-2px_0_0_var(--color-signal)]",
|
|
!@active && "text-ink-faint"
|
|
]}
|
|
>
|
|
{@label}
|
|
</.link>
|
|
"""
|
|
end
|
|
|
|
attr :path, :string, required: true
|
|
attr :current, :string, default: nil
|
|
attr :label, :string, required: true
|
|
|
|
defp mobile_nav_link(assigns) do
|
|
assigns =
|
|
assign(assigns, :active, TarakanWeb.CurrentPath.active?(assigns.current, assigns.path))
|
|
|
|
~H"""
|
|
<.link
|
|
navigate={@path}
|
|
aria-current={@active && "page"}
|
|
class={[
|
|
"block px-4 py-3.5 font-mono text-[11px] uppercase tracking-[0.12em]",
|
|
"transition hover:bg-panel",
|
|
@active && "bg-panel text-ink shadow-[inset_2px_0_0_0_var(--color-signal)]",
|
|
!@active && "text-ink"
|
|
]}
|
|
>
|
|
{@label}
|
|
</.link>
|
|
"""
|
|
end
|
|
|
|
@doc """
|
|
Shows the flash group with standard titles and content.
|
|
|
|
## Examples
|
|
|
|
<.flash_group flash={@flash} />
|
|
"""
|
|
attr :flash, :map, required: true, doc: "the map of flash messages"
|
|
attr :id, :string, default: "flash-group", doc: "the optional id of flash container"
|
|
|
|
def flash_group(assigns) do
|
|
~H"""
|
|
<div id={@id} aria-live="polite">
|
|
<.flash kind={:info} flash={@flash} />
|
|
<.flash kind={:error} flash={@flash} />
|
|
|
|
<.flash
|
|
id="client-error"
|
|
kind={:error}
|
|
auto_dismiss={false}
|
|
title={gettext("We can't find the internet")}
|
|
phx-disconnected={
|
|
show(".phx-client-error #client-error")
|
|
|> JS.remove_attribute("hidden", to: ".phx-client-error #client-error")
|
|
}
|
|
phx-connected={hide("#client-error") |> JS.set_attribute({"hidden", ""})}
|
|
hidden
|
|
>
|
|
{gettext("Attempting to reconnect")}
|
|
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
|
</.flash>
|
|
|
|
<.flash
|
|
id="server-error"
|
|
kind={:error}
|
|
auto_dismiss={false}
|
|
title={gettext("Something went wrong!")}
|
|
phx-disconnected={
|
|
show(".phx-server-error #server-error")
|
|
|> JS.remove_attribute("hidden", to: ".phx-server-error #server-error")
|
|
}
|
|
phx-connected={hide("#server-error") |> JS.set_attribute({"hidden", ""})}
|
|
hidden
|
|
>
|
|
{gettext("Attempting to reconnect")}
|
|
<.icon name="hero-arrow-path" class="ml-1 size-3 motion-safe:animate-spin" />
|
|
</.flash>
|
|
</div>
|
|
"""
|
|
end
|
|
end
|