feat(kez-chat/web): dashboard shows verified claims
The dashboard's Claims section used to be a "here's what claims are"
teaser with a link to /claims. Now it shows the actual verified rows:
• Empty state → "+ Add your first claim" CTA
• Claims exist, none verified → amber callout pointing at Re-verify
• Verified claims → green rows, one per verified claim:
[GitHub] github:tudisco
✓ Verified via public gist proof ↗
• Trailing summary if relevant: "1 failed verification. 2 not yet
checked. See the claims page for details."
A "Re-verify all" button at the section header re-runs every verifier
in place, so the dashboard stays fresh without a round-trip to /claims.
Uses $derived to bucket claims by verification status. $state.snapshot
before passing each claim to the verifier (same proxy/structuredClone
gotcha as the Claims page).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a8036cc392
commit
109852ed75
@ -4,16 +4,37 @@
|
||||
import { bytesToHex } from "@noble/hashes/utils";
|
||||
import { lookup, ApiError } from "../lib/api.js";
|
||||
import { session } from "../lib/store.svelte.js";
|
||||
import {
|
||||
listClaims,
|
||||
setVerifyResult,
|
||||
type StoredClaim,
|
||||
} from "../lib/claims-store.js";
|
||||
import { verifyClaim } from "../lib/verify.js";
|
||||
|
||||
let registryRecord = $state<any | null>(null);
|
||||
let loading = $state(true);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
let claims = $state<StoredClaim[]>([]);
|
||||
let verifyingAll = $state(false);
|
||||
|
||||
// Derived buckets for the verified-claims section.
|
||||
const verifiedClaims = $derived(
|
||||
claims.filter((c) => c.last_verify?.status === "ok"),
|
||||
);
|
||||
const failedClaims = $derived(
|
||||
claims.filter((c) => c.last_verify?.status === "fail"),
|
||||
);
|
||||
const unverifiedClaims = $derived(
|
||||
claims.filter((c) => !c.last_verify),
|
||||
);
|
||||
|
||||
onMount(async () => {
|
||||
if (!session.unlocked) {
|
||||
push("/unlock");
|
||||
return;
|
||||
}
|
||||
claims = await listClaims();
|
||||
try {
|
||||
registryRecord = await lookup(session.unlocked.handle);
|
||||
} catch (e) {
|
||||
@ -27,6 +48,33 @@
|
||||
}
|
||||
});
|
||||
|
||||
async function reverifyAll() {
|
||||
verifyingAll = true;
|
||||
try {
|
||||
for (const c of claims) {
|
||||
const result = await verifyClaim($state.snapshot(c) as StoredClaim);
|
||||
await setVerifyResult(c.id, result);
|
||||
}
|
||||
claims = await listClaims();
|
||||
} finally {
|
||||
verifyingAll = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Friendly label per channel — used in the verified-claims row. */
|
||||
function channelLabel(ch: string): string {
|
||||
return (
|
||||
{
|
||||
github: "GitHub",
|
||||
dns: "DNS",
|
||||
web: "Website",
|
||||
nostr: "Nostr",
|
||||
bluesky: "Bluesky",
|
||||
ap: "ActivityPub",
|
||||
} as Record<string, string>
|
||||
)[ch] ?? ch;
|
||||
}
|
||||
|
||||
function showSeed() {
|
||||
if (!session.unlocked) return;
|
||||
const hex = bytesToHex(session.unlocked.seed);
|
||||
@ -64,21 +112,95 @@
|
||||
</section>
|
||||
|
||||
<section class="border border-gray-200 rounded-lg p-6 bg-white">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-start justify-between gap-4 mb-4">
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wide">Claims</p>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wide">
|
||||
Verified claims
|
||||
</p>
|
||||
<p class="text-sm text-gray-700 mt-1">
|
||||
Link other accounts (GitHub, your domain, nostr, Bluesky, ActivityPub)
|
||||
to your KEZ identity by signing claims and publishing the proofs.
|
||||
Accounts cryptographically linked to
|
||||
<span class="font-mono">{session.unlocked.handle}@{session.unlocked.server}</span>.
|
||||
Anyone can re-verify these without trusting this server.
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
href="#/claims"
|
||||
class="px-3 py-2 text-sm bg-gray-900 text-white rounded-md hover:bg-gray-700 no-underline"
|
||||
>
|
||||
Manage claims →
|
||||
</a>
|
||||
<div class="flex flex-col gap-2 shrink-0">
|
||||
{#if claims.length > 0}
|
||||
<button
|
||||
class="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
||||
onclick={reverifyAll}
|
||||
disabled={verifyingAll}
|
||||
>
|
||||
{verifyingAll ? "Verifying…" : "Re-verify all"}
|
||||
</button>
|
||||
{/if}
|
||||
<a
|
||||
href="#/claims"
|
||||
class="px-3 py-1.5 text-sm bg-gray-900 text-white rounded-md hover:bg-gray-700 no-underline text-center"
|
||||
>
|
||||
Manage →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if claims.length === 0}
|
||||
<p class="text-sm text-gray-500 italic">
|
||||
No claims yet. Link a GitHub account, a domain, your nostr identity,
|
||||
or anywhere else you'd like to prove you control.
|
||||
</p>
|
||||
<a
|
||||
href="#/claims/add"
|
||||
class="mt-3 inline-block px-3 py-2 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 no-underline"
|
||||
>
|
||||
+ Add your first claim
|
||||
</a>
|
||||
{:else if verifiedClaims.length === 0}
|
||||
<p class="text-sm text-amber-800 bg-amber-50 border border-amber-200 rounded p-3">
|
||||
You have {claims.length} claim{claims.length === 1 ? "" : "s"} but
|
||||
none are verified yet. Publish the proofs on each channel and hit
|
||||
<strong>Re-verify all</strong>.
|
||||
</p>
|
||||
{:else}
|
||||
<ul class="space-y-2">
|
||||
{#each verifiedClaims as c (c.id)}
|
||||
<li class="flex items-center justify-between gap-3 p-3 bg-green-50 border border-green-200 rounded">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="text-xs font-semibold text-green-800 bg-green-200 px-1.5 py-0.5 rounded">
|
||||
{channelLabel(c.channel)}
|
||||
</span>
|
||||
<span class="font-mono text-sm text-gray-900 truncate">
|
||||
{c.envelope.payload.subject}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-green-900">
|
||||
✓ {c.last_verify?.summary}
|
||||
</p>
|
||||
</div>
|
||||
{#if c.last_verify?.evidence_url}
|
||||
<a
|
||||
href={c.last_verify.evidence_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-xs text-green-700 hover:text-green-900 underline shrink-0"
|
||||
>
|
||||
proof ↗
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if failedClaims.length > 0 || unverifiedClaims.length > 0}
|
||||
<p class="mt-3 text-xs text-gray-500">
|
||||
{#if failedClaims.length > 0}
|
||||
{failedClaims.length} failed verification.
|
||||
{/if}
|
||||
{#if unverifiedClaims.length > 0}
|
||||
{unverifiedClaims.length} not yet checked.
|
||||
{/if}
|
||||
See the claims page for details.
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<section class="border border-gray-200 rounded-lg p-6 bg-white">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user