feat(invites): drop literal token after claim; cleaner UI

Once an invite is claimed, the token has no functional role — claims
are one-way and the link is dead. Stop returning the literal token in
the GET /api/invites response for claimed entries (server/invites.ts
toEntry). The audit trail — claimed_at, claimed_by_display — stays.
Helps a little with data minimization: a compromised inviter account
can no longer see used-up invitation URLs.

Type: InviteEntry.token is now string | null. Callers that still need
to use the token (signup-via-invite tests, the cancel button, the
copy button) are guarded so they only run on entries where the token
is present (i.e. unclaimed). The each-key falls back to a synthetic
composite when token is null so Svelte's keyed-each stays stable.

UI: claimed entries collapse to a single muted line, no card frame,
no URL placeholder:
    ✓ Laget DD.MM.YYYY · godtatt av <bruker> DD.MM.YYYY

Unclaimed entries keep the existing card with copy / cancel buttons.
Heading on the invite section also renamed from "Invitasjonslenker"
to "Invitasjoner" — claimed entries don't have a link anymore so the
older label was misleading.

Tests updated to match by created_at instead of token for the
claimed-invite lookup, and to assert that token is null post-claim.
This commit is contained in:
Ole-Morten Duesund 2026-05-25 20:47:33 +02:00
commit 95f989639d
4 changed files with 46 additions and 31 deletions

View file

@ -45,8 +45,9 @@ export interface InviteEntry {
/** The token itself. The shareable URL is built client-side as
* `${window.location.origin}/invitasjon/${token}` server-side URL
* construction would point at the API host in split-process dev
* environments. */
token: string;
* environments. NULL when the invite has been claimed; the token has
* no functional role after that and we don't keep it in the response. */
token: string | null;
created_at: number;
claimed_at: number | null;
/** Display name (or username) of the user who claimed it, if any. */