style(account): improve API token UX

- Replace token dismiss button with close icon in top-right corner
- Add terminal icon to token rows matching passkey layout
- Remove token row from DOM immediately on revoke
- Focus token name input when opening the create form
- Clear token name input after successful creation
This commit is contained in:
Jon Seager 2026-02-07 11:25:30 +00:00
parent 902d52b171
commit 4eed3658f2
No known key found for this signature in database
2 changed files with 31 additions and 19 deletions

View file

@ -118,13 +118,16 @@
{% else %}
<div class="flex flex-col gap-2">
{% for token in tokens %}
<div class="flex items-center justify-between gap-4 rounded-md bg-surface-alt px-4 py-3">
<div class="min-w-0">
<span class="block text-sm font-semibold text-text">{{ token.name }}</span>
<span class="block text-xs text-text-muted">
Created {{ token.created_at }}
· {% if let Some(last_used) = token.last_used_at %}Last used {{ last_used }}{% else %}Never used{% endif %}
</span>
<div id="token-row-{{ token.id }}" class="flex items-center justify-between gap-4 rounded-md bg-surface-alt px-4 py-3">
<div class="flex items-center gap-3 min-w-0">
{{ icons::terminal("h-4 w-4 text-accent shrink-0") }}
<div class="min-w-0">
<span class="block text-sm font-semibold text-text">{{ token.name }}</span>
<span class="block text-xs text-text-muted">
Created {{ token.created_at }}
· {% if let Some(last_used) = token.last_used_at %}Last used {{ last_used }}{% else %}Never used{% endif %}
</span>
</div>
</div>
<button
type="button"
@ -147,7 +150,7 @@
<form
data-on:submit="$_creatingToken = true; $_tokenError = ''; @post('/api/v1/tokens', {contentType: 'form'})"
data-on:datastar-fetch="if (!$_creatingToken) return;
if (evt.detail.type === 'finished') { $_creatingToken = false }
if (evt.detail.type === 'finished') { $_creatingToken = false; document.getElementById('token-name').value = '' }
else if (evt.detail.type === 'error') { $_creatingToken = false; $_tokenError = 'Failed to create token.' }"
>
<div class="flex items-end gap-3">
@ -156,6 +159,7 @@
<input
type="text"
name="name"
id="token-name"
required
class="input-field"
placeholder="e.g. laptop-cli, ci-server"
@ -181,8 +185,16 @@
<!-- One-time token display -->
<div data-show="$_tokenCreated" style="display: none"
class="rounded-md border border-green-300 dark:border-green-800 bg-green-50 dark:bg-green-950/40 p-4">
<p class="text-sm font-medium text-green-800 dark:text-green-300">Token created! Copy it now — you won't see it again.</p>
class="relative rounded-md border border-green-300 dark:border-green-800 bg-green-50 dark:bg-green-950/40 p-4">
<button
type="button"
onclick="window.location.reload()"
class="absolute top-3.5 right-3 inline-flex h-6 w-6 items-center justify-center rounded text-green-800 dark:text-green-300 transition hover:text-green-600 dark:hover:text-green-100"
aria-label="Dismiss"
>
{{ icons::x_mark("h-4 w-4") }}
</button>
<p class="pr-6 text-sm font-medium text-green-800 dark:text-green-300">Token created! Copy it now — you won't see it again.</p>
<div class="mt-3 flex items-center gap-2">
<code id="token-value" data-text="$_tokenValue"
class="flex-1 rounded bg-surface px-3 py-2 text-sm font-mono text-text border border-green-200 dark:border-green-800 break-all select-all"></code>
@ -194,20 +206,13 @@
Copy
</button>
</div>
<button
type="button"
onclick="window.location.reload()"
class="mt-2 text-sm text-text-muted hover:text-text"
>
Dismiss
</button>
</div>
<div>
<button
type="button"
data-show="!$_showTokenForm && !$_tokenCreated"
data-on:click="$_showTokenForm = true"
data-on:click="$_showTokenForm = true; setTimeout(() => document.getElementById('token-name').focus(), 50)"
class="rounded-md bg-accent px-4 py-2 text-sm font-medium text-accent-text transition hover:bg-accent-hover"
>
Create Token
@ -439,7 +444,8 @@
try {
const response = await fetch(`/api/v1/tokens/${id}/revoke`, { method: "POST" });
if (response.ok) {
window.location.reload();
const row = document.getElementById(`token-row-${id}`);
if (row) row.remove();
} else {
alert("Failed to revoke token.");
}

View file

@ -158,6 +158,12 @@
</svg>
{% endmacro %}
{% macro terminal(class) %}
<svg class="{{ class }}" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M3.25 3A2.25 2.25 0 0 0 1 5.25v9.5A2.25 2.25 0 0 0 3.25 17h13.5A2.25 2.25 0 0 0 19 14.75v-9.5A2.25 2.25 0 0 0 16.75 3H3.25Zm.943 8.752a.75.75 0 0 1 .055-1.06L6.128 9l-1.88-1.693a.75.75 0 1 1 1.004-1.114l2.5 2.25a.75.75 0 0 1 0 1.114l-2.5 2.25a.75.75 0 0 1-1.06-.055ZM9.75 10.25a.75.75 0 0 0 0 1.5h2.5a.75.75 0 0 0 0-1.5h-2.5Z" clip-rule="evenodd" />
</svg>
{% endmacro %}
{% macro x_mark(class) %}
<svg class="{{ class }}" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z" />