diff --git a/dashboard/src/routes/profile/+page.svelte b/dashboard/src/routes/profile/+page.svelte
new file mode 100644
index 0000000..ce9bf00
--- /dev/null
+++ b/dashboard/src/routes/profile/+page.svelte
@@ -0,0 +1,760 @@
+
+
+
+{#if me}
+
+
+
{me.username}
+
+
+
+
+
Email
+ {me.email ?? 'No email set'}
+
+
+
User ID
+ {me.id}
+
+
+
+{/if}
+
+{#if deniedFromUsers}
+
+ You don't have access to the Users page. Ask an admin if you need to manage users.
+
+{/if}
+
+{#if banner}
+ {banner.message}
+{/if}
+
+
+
+ API keys
+ {#if !mintOpen && !reveal}
+ + Mint API key
+ {/if}
+
+
+ {#if reveal}
+
+
Save this token now — it will never be shown again.
+
+ Paste it into your CLI config or external integration. PiCloud only ever stores a hash; if
+ you lose it, mint a new one.
+
+
+ {reveal.raw_token}
+
+ {copyState === 'copied' ? 'Copied ✓' : 'Copy'}
+
+
+
+
+ I've saved this token somewhere safe.
+
+
+
+ Done
+
+
+
+ {/if}
+
+ {#if mintOpen}
+
+ {/if}
+
+ {#if loadError}
+
+ {loadError}
+ Retry
+
+ {:else if keys.length === 0 && !reveal && !mintOpen}
+
+ No API keys yet. Mint one to authenticate the CLI or external integrations.
+
+ {:else if keys.length > 0}
+
+
+
Name
+
Prefix
+
Scopes
+
Binding
+
Created
+
Last used
+
Expires
+
+
+ {#each keys as key (key.id)}
+
+
{key.name}
+
{key.prefix}
+
+ {#each key.scopes as s (s)}
+ {s}
+ {/each}
+
+
{appLabel(key.app_id)}
+
{shortDate(key.created_at)}
+
{relative(key.last_used_at)}
+
{key.expires_at ? shortDate(key.expires_at) : 'Never'}
+
+ openRevoke(key)}
+ aria-label="Revoke {key.name}"
+ >
+ Revoke
+
+
+
+ {/each}
+
+ {/if}
+
+
+{#if revokeTarget}
+ (revokeTarget = null)}
+ >
+
+ Revoking {revokeTarget.name} ({revokeTarget.prefix}) takes
+ effect immediately. Any CLI or integration using it will start returning 401
+ on the next request.
+
+ This can't be undone — mint a new key if you need one again.
+
+{/if}
+
+