Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at https://git.elektrine.com/elektrine/tarakan.
This commit is contained in:
commit
af6077b9c3
455 changed files with 78366 additions and 0 deletions
765
lib/tarakan_web/components/layouts.ex
Normal file
765
lib/tarakan_web/components/layouts.ex
Normal file
|
|
@ -0,0 +1,765 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue