tarakan/lib/tarakan_web/live/account_live/profile.html.heex
Maxfield Luke af6077b9c3
All checks were successful
CI and deploy / Test (push) Successful in 4m52s
CI and deploy / Deploy production (push) Successful in 23s
Initial commit on Forgejo
Fresh repository history for elektrine/tarakan hosted at
https://git.elektrine.com/elektrine/tarakan.
2026-07-29 04:43:40 -04:00

354 lines
13 KiB
Text

<Layouts.app flash={@flash} current_scope={@current_scope} current_path={assigns[:current_path]}>
<Layouts.page>
<.breadcrumbs id="profile-breadcrumb">
<:crumb navigate={~p"/"}>registry</:crumb>
<:crumb>@{@account.handle}</:crumb>
</.breadcrumbs>
<header id="profile-header" class="border-b-2 border-strong pb-6">
<div class="flex flex-wrap items-center gap-3">
<.notch_badge
:if={role_label(@account.platform_role)}
id="profile-role"
class="text-signal"
>
{role_label(@account.platform_role)}
</.notch_badge>
<.notch_badge id="profile-tier" class="text-ink-muted">
{tier_label(@account.trust_tier)}
</.notch_badge>
<span :if={@badges != []} id="profile-badges" class="contents">
<.notch_badge
:for={badge <- @badges}
id={"profile-badge-#{badge}"}
class="text-quote"
>
{badge_label(badge)}
</.notch_badge>
</span>
</div>
<h1
id="profile-handle"
class="mt-3 break-words font-display text-3xl font-medium uppercase leading-tight tracking-[0.02em] text-ink sm:text-4xl"
>
<span class="text-ink-faint">@</span>{@account.handle}
</h1>
<p :if={@account.display_name} class="mt-1 text-sm text-ink-muted">
{@account.display_name}
</p>
<div class="mt-4 flex flex-wrap items-center gap-x-5 gap-y-2 font-mono text-xs text-ink-faint">
<span
class="inline-flex items-center gap-1.5"
title={"#{@reputation.verification} from verified work · #{@reputation.votes} from votes"}
>
<.icon name="hero-bolt" class="size-3.5" />
<span id="profile-reputation" class="tabular-nums text-ink-muted">
{@reputation.total}
</span>
reputation
<span class="text-ink-faint">
({@reputation.verification} verified · {@reputation.votes} votes<span :if={
@reputation.at_risk > 0
}> · {@reputation.at_risk} at stake in {@stake_summary.at_risk} {if(
@stake_summary.at_risk == 1,
do: "report",
else: "reports"
)}</span><span
:if={@reputation.slashed > 0}
class="text-signal"
> · {@reputation.slashed} slashed across {@stake_summary.slashed} {if(
@stake_summary.slashed == 1,
do: "report",
else: "reports"
)}</span>)
</span>
</span>
<span class="inline-flex items-center gap-1.5">
<.icon name="hero-clock" class="size-3.5" /> joined {joined_on(@account.inserted_at)}
</span>
<a
:for={identity <- @account.identities}
:if={identity.profile_url}
id={"profile-identity-#{identity.provider}"}
href={identity.profile_url}
rel="noreferrer"
target="_blank"
class="inline-flex items-center gap-1.5 transition hover:text-signal"
>
<.icon name="hero-arrow-up-right" class="size-3.5" />
{provider_label(identity.provider)}<span :if={identity.provider_login}>/{identity.provider_login}</span>
</a>
</div>
</header>
<section
id="profile-stats"
class="mt-8 grid grid-cols-2 border-2 border-strong lg:grid-cols-5"
>
<div class="border-b border-r border-rule px-6 py-7 lg:border-b-0">
<p class="text-xs font-medium text-ink-faint">Reports</p>
<p
id="profile-review-count"
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
>
{@stats.reviews}
</p>
</div>
<div class="border-b border-rule px-6 py-7 lg:border-b-0 lg:border-r">
<p class="text-xs font-medium text-ink-faint">Findings</p>
<p
id="profile-finding-count"
class="mt-3 font-display text-4xl font-medium tabular-nums text-signal"
>
{@stats.findings}
</p>
</div>
<div class="border-b border-r border-rule px-6 py-7 lg:border-b-0">
<p class="text-xs font-medium text-ink-faint">Checks</p>
<p
id="profile-verdict-count"
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
>
{@stats.verdicts}
</p>
</div>
<div class="border-b border-rule px-6 py-7 lg:border-b-0 lg:border-r">
<p class="text-xs font-medium text-ink-faint">
Repositories
</p>
<p
id="profile-repo-count"
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
>
{@stats.repositories}
</p>
</div>
<div class="px-6 py-7">
<p class="text-xs font-medium text-ink-faint">Credits</p>
<p
id="profile-credit-balance"
class="mt-3 font-display text-4xl font-medium tabular-nums text-ink"
>
{@credit_balance}
</p>
</div>
</section>
<div class="mt-10 grid gap-10 lg:grid-cols-2">
<section id="profile-repositories">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Repositories added
</h2>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
Publicly listed repositories this contributor registered.
</p>
<p
:if={@repositories == []}
id="profile-no-repositories"
class="mt-4 font-mono text-xs text-ink-faint"
>
No listed repositories yet.
</p>
<ul
:if={@repositories != []}
class="mt-4 divide-y divide-rule border-t border-rule"
>
<li :for={repository <- @repositories} id={"profile-repo-#{repository.id}"}>
<.link
navigate={TarakanWeb.RepositoryPaths.repository_path(repository)}
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
>
<span class="shrink-0 tabular-nums text-ink-faint">
{activity_time(repository.inserted_at)}
</span>
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
<span class="text-ink-faint">{repository.host}/</span>{repository.owner}/{repository.name}
</span>
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
<span :if={repository.primary_language}>{repository.primary_language}</span>
</span>
</.link>
</li>
</ul>
</section>
<section id="profile-reviews">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Reports submitted
</h2>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
Security reviews pinned to exact commits.
</p>
<p
:if={@reviews == []}
id="profile-no-reviews"
class="mt-4 font-mono text-xs text-ink-faint"
>
No public reports yet.
</p>
<ul :if={@reviews != []} class="mt-4 divide-y divide-rule border-t border-rule">
<li :for={entry <- @reviews} id={"profile-review-#{entry.id}"}>
<.link
navigate={TarakanWeb.RepositoryPaths.repository_security_path(entry.repository)}
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
>
<span class="shrink-0 tabular-nums text-ink-faint">{activity_time(entry.at)}</span>
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
<span class="text-ink-faint">reviewed</span>
{entry.repository.owner}/{entry.repository.name}
<span :if={entry.commit_sha} class="text-ink-faint">
· {short_sha(entry.commit_sha)}
</span>
</span>
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
<span :if={entry.verified} class="text-ink-muted">verified · </span>
<span :if={entry.findings_count > 0} class="text-signal">
{entry.findings_count} findings
</span>
<span :if={entry.findings_count == 0}>no findings</span>
</span>
</.link>
</li>
</ul>
</section>
<section id="profile-findings">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Findings filed
</h2>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
Individual findings from public reports.
</p>
<p
:if={@findings == []}
id="profile-no-findings"
class="mt-4 font-mono text-xs text-ink-faint"
>
No public findings yet.
</p>
<ul :if={@findings != []} class="mt-4 divide-y divide-rule border-t border-rule">
<li :for={entry <- @findings} id={"profile-finding-#{entry.public_id}"}>
<.link
navigate={~p"/findings/#{entry.public_id}"}
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
>
<span class="shrink-0 tabular-nums text-ink-faint">{activity_time(entry.at)}</span>
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
<span class={[
"mr-1.5 inline-block border px-1 text-[10px] uppercase tracking-[0.12em]",
entry.severity in ~w(critical high) && "border-signal text-signal",
entry.severity not in ~w(critical high) && "border-rule text-ink-faint"
]}>
{entry.severity}
</span>
{entry.title}
</span>
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
{entry.repository.owner}/{entry.repository.name}
</span>
</.link>
</li>
</ul>
</section>
<section id="profile-checks">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Checks cast
</h2>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
Independent confirmations, disputes, and fix notes.
</p>
<p
:if={@checks == []}
id="profile-no-checks"
class="mt-4 font-mono text-xs text-ink-faint"
>
No public checks yet.
</p>
<ul :if={@checks != []} class="mt-4 divide-y divide-rule border-t border-rule">
<li :for={entry <- @checks} id={"profile-check-#{entry.id}"}>
<.link
:if={entry.public_id || entry.repository}
navigate={check_path(entry)}
class="group flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs transition-colors hover:bg-panel"
>
<span class="shrink-0 tabular-nums text-ink-faint">{activity_time(entry.at)}</span>
<span class="min-w-0 truncate text-ink-muted transition group-hover:text-ink">
<span class={[
entry.verdict == "confirmed" && "text-quote",
entry.verdict == "disputed" && "text-signal",
entry.verdict == "fixed" && "text-ink"
]}>
{entry.verdict}
</span>
<span :if={entry.title} class="text-ink-faint"> · </span>
<span :if={entry.title}>{entry.title}</span>
<span
:if={is_nil(entry.title) and entry.repository}
class="text-ink-faint"
>
on {entry.repository.owner}/{entry.repository.name}
</span>
</span>
<span class="ml-auto shrink-0 text-[10px] text-ink-faint">
{provenance_label(entry.provenance)}
</span>
</.link>
</li>
</ul>
</section>
</div>
<section id="profile-credits" class="mt-10">
<h2 class="font-display text-lg uppercase tracking-[0.02em] text-ink">
Credits
</h2>
<p class="mt-1 font-mono text-[11px] text-ink-faint">
Earned on verification events only - never purchasable, never transferable.
</p>
<p
:if={@credit_entries == []}
id="profile-no-credits"
class="mt-4 font-mono text-xs text-ink-faint"
>
No credit activity yet.
</p>
<ul :if={@credit_entries != []} class="mt-4 divide-y divide-rule border-t border-rule">
<li
:for={entry <- @credit_entries}
id={"profile-credit-#{entry.id}"}
class="flex items-baseline gap-x-3 px-3 py-3 font-mono text-xs"
>
<span class="shrink-0 tabular-nums text-ink-faint">
{activity_time(entry.inserted_at)}
</span>
<span class="min-w-0 truncate text-ink-muted">
{credit_kind_label(entry.kind)}
</span>
<span class={[
"ml-auto shrink-0 tabular-nums",
entry.amount > 0 && "text-quote",
entry.amount < 0 && "text-signal"
]}>
{credit_amount(entry.amount)}
</span>
<span class="shrink-0 tabular-nums text-ink-faint">
balance {entry.balance_after}
</span>
</li>
</ul>
</section>
</Layouts.page>
</Layouts.app>