diff --git a/.gitignore b/.gitignore index 3116106..f14aaa3 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ pip-delete-this-directory.txt # macOS .DS_Store + +# Saved design reference pages (contain third-party account PII; never commit) +reference/ diff --git a/app/activate/page.tsx b/app/activate/page.tsx index 98a3fee..dd0b707 100644 --- a/app/activate/page.tsx +++ b/app/activate/page.tsx @@ -4,7 +4,6 @@ import { AuthShell } from "@/components/AuthShell"; import { Button } from "@/components/Button"; import { Alert } from "@/components/Alert"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { getCurrentSession } from "@/lib/server/session"; import { mintActivationCsrf } from "@/lib/server/activationCsrf"; import { getActivationForUser } from "@/lib/server/services/activation"; @@ -49,16 +48,16 @@ export default async function ActivatePage({
{activation.app.name.slice(0, 2)}
-
- authorized +
+ Authorized
-

+

{activation.app.name}

@@ -71,7 +70,7 @@ export default async function ActivatePage({
@@ -104,24 +103,24 @@ export default async function ActivatePage({
{activation.app.name.slice(0, 2)}
-
- authorize app +
+ Authorize app
-

+

{activation.app.name}

{expired ? ( - expired + Expired ) : ( - - expires {activation.expiresAt.slice(0, 16).replace("T", " ")} + + Expires {activation.expiresAt.slice(0, 16).replace("T", " ")} )}
@@ -129,19 +128,19 @@ export default async function ActivatePage({
- - + + {requiredProduct && ( - {blocked ? "missing" : "active"} + {blocked ? "Missing" : "Active"} } /> @@ -150,8 +149,8 @@ export default async function ActivatePage({
-
- will share +
+ Will share
{standardScopes.map((scope) => { @@ -161,8 +160,8 @@ export default async function ActivatePage({ key={scope} className="flex items-baseline gap-3 py-2.5 border-b border-rule" > - - {item.label} + + {item.label} - + {item.label} - - optional + + Optional ); @@ -202,7 +201,7 @@ export default async function ActivatePage({ {blocked && !expired && requiredProduct && (
- active {requiredProduct} subscription required to approve + Active {requiredProduct} subscription required to approve
)} @@ -210,7 +209,7 @@ export default async function ActivatePage({ {error === "csrf" && (
- your confirmation expired. review and approve again. + Your confirmation expired. Review and approve again.
)} @@ -223,7 +222,7 @@ export default async function ActivatePage({
@@ -253,10 +252,10 @@ function DetailRow({ }) { return (
- + {label} - + {value} {right} diff --git a/app/admin/activation-requests/page.tsx b/app/admin/activation-requests/page.tsx index aca276d..8e3edf4 100644 --- a/app/admin/activation-requests/page.tsx +++ b/app/admin/activation-requests/page.tsx @@ -1,6 +1,5 @@ import { Section, Empty, Row, RowLabel, RowValue } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { query } from "@/lib/server/db"; import { getCurrentSession } from "@/lib/server/session"; import { redirect } from "next/navigation"; @@ -51,35 +50,31 @@ export default async function AdminActivationRequestsPage() { data-mount-stagger >
-
- $ - - admin.activations - - · - - {String(requests.length).padStart(2, "0")} +
+ admin.activations + + {requests.length}
-

- activation requests +

+ Activation requests

{requests.length === 0 ? ( - no activation requests + No activation requests ) : ( requests.map(req => ( - + {req.app_name} @@ -90,11 +85,10 @@ export default async function AdminActivationRequestsPage() { {req.approved_username && ( <> - @{req.approved_username} - + · )} {req.requested_subject && ( @@ -102,12 +96,12 @@ export default async function AdminActivationRequestsPage() { sub:{req.requested_subject} - + · )} {req.public_id} - + {req.created_at?.slice(0, 10)} diff --git a/app/admin/bans/page.tsx b/app/admin/bans/page.tsx index 81f32cd..2ffdd5e 100644 --- a/app/admin/bans/page.tsx +++ b/app/admin/bans/page.tsx @@ -1,6 +1,5 @@ import { Section, Empty } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { query } from "@/lib/server/db"; import { getCurrentSession } from "@/lib/server/session"; import { redirect } from "next/navigation"; @@ -41,25 +40,20 @@ export default async function AdminBansPage() { data-mount-stagger >
-
- $ - - admin.bans - - · - - {String(active.length).padStart(2, "0")} active +
+ + {active.length} active
-

- bans +

+ Bans

-
+
{active.length === 0 ? ( - no active bans + No active bans ) : ( active.map(ban => (
{ban.reason}
)} -
- - {ban.created_at?.slice(0, 10)} - +
+ {ban.created_at?.slice(0, 10)} {ban.expires_at && ( <> - + · expires {ban.expires_at.slice(0, 10)} )} @@ -92,9 +84,9 @@ export default async function AdminBansPage() {
@@ -105,7 +97,7 @@ export default async function AdminBansPage() { {revoked.length > 0 && (
-
+
{revoked.map(ban => (
{ban.created_at?.slice(0, 10)} - + · revoked {ban.revoked_at?.slice(0, 10)}
diff --git a/app/admin/keys/page.tsx b/app/admin/keys/page.tsx index 69a6774..e10d578 100644 --- a/app/admin/keys/page.tsx +++ b/app/admin/keys/page.tsx @@ -1,7 +1,6 @@ import { createHash, createPrivateKey, createPublicKey } from "crypto"; import { Section, Row, RowLabel, RowValue } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { oauthProfileVersions, oidcSigningKeys } from "@/lib/server/config"; export const dynamic = "force-dynamic"; @@ -33,27 +32,23 @@ export default function AdminKeysPage() { data-mount-stagger >
-
- $ - - admin.keys - - · - +
+ admin.keys + {String(keys.length).padStart(2, "0")}
-

- signing keys +

+ Signing keys

-

- oidc keys are environment-backed. active keys sign new tokens, retired - keys stay in jwks, and revoked keys are removed from jwks. +

+ OIDC keys are environment-backed. Active keys sign new tokens, retired + keys stay in JWKS, and revoked keys are removed from JWKS.

-
+
{keys.map(key => ( @@ -63,7 +58,6 @@ export default function AdminKeysPage() { {key.fingerprint} - {key.status} @@ -73,23 +67,23 @@ export default function AdminKeysPage() {
-
+
- add + Add - append a new active entry to OIDC_SIGNING_KEYS_JSON + Append a new active entry to OIDC_SIGNING_KEYS_JSON - retire - mark the previous key retired after deploy + Retire + Mark the previous key retired after deploy - revoke + Revoke - mark compromised keys revoked and redeploy immediately + Mark compromised keys revoked and redeploy immediately @@ -97,7 +91,7 @@ export default function AdminKeysPage() {
-
+
{oauthProfileVersions.map(profile => ( diff --git a/app/admin/oauth-clients/page.tsx b/app/admin/oauth-clients/page.tsx index 25a2569..3445710 100644 --- a/app/admin/oauth-clients/page.tsx +++ b/app/admin/oauth-clients/page.tsx @@ -1,6 +1,5 @@ import { Empty, Section } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { listPendingOAuthClientRegistrationRequests } from "@/lib/server/repositories/oauthClientRegistrations"; import { approveOAuthClientRegistrationAction, @@ -18,29 +17,28 @@ export default async function AdminOAuthClientsPage() { data-mount-stagger >
-
- $ - - admin.oauth.clients - +
+ Admin + · + OAuth clients · - + {String(requests.length).padStart(2, "0")} pending
-

- oauth client reviews +

+ OAuth client reviews

{requests.length === 0 ? ( - no pending clients + No pending clients ) : ( requests.map(request => (
-
+
redirects
{request.redirectUris.join(", ")}
-
+
grants
{request.grantTypes.join(", ")}
-
+
scopes
{request.scopes.join(", ")}
-
+
requester
@@ -98,25 +96,23 @@ export default async function AdminOAuthClientsPage() {
-
+
diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 5d8b8b7..445604b 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,6 +1,5 @@ import { Section, Empty } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { query, queryOne } from "@/lib/server/db"; import { decideBearerAction } from "./actions"; @@ -59,38 +58,33 @@ export default async function AdminPage() { data-mount-stagger >
-
- $ - - admin.overview - - · - root +
+ Admin + / + Overview
-

- overview +

+ Overview

-

system state at a glance

+

System state at a glance

{[ - { label: "active users", value: data.stats.activeUsers }, - { label: "sessions", value: data.stats.activeSessions }, - { label: "pending bearers", value: data.stats.pendingBearers }, - { label: "pending activations", value: data.stats.pendingActivations }, + { label: "Active users", value: data.stats.activeUsers }, + { label: "Sessions", value: data.stats.activeSessions }, + { label: "Pending bearers", value: data.stats.pendingBearers }, + { label: "Pending activations", value: data.stats.pendingActivations }, ].map((stat, i) => (
0 ? "border-l border-rule" : ""}`} + className={`px-4 py-5 ${i > 0 ? "border-l border-rule" : ""}`} > -
- {stat.label} -
-
+
{stat.label}
+
{String(stat.value).padStart(2, "0")}
@@ -100,11 +94,11 @@ export default async function AdminPage() {
{data.pendingRequests.length === 0 ? ( - no pending requests + No pending requests ) : ( data.pendingRequests.map(req => (
· id {req.user_id}
- pending + Pending
{req.reason}
-
+
@@ -149,10 +142,9 @@ export default async function AdminPage() {
diff --git a/app/admin/security/page.tsx b/app/admin/security/page.tsx index 357e512..61f991f 100644 --- a/app/admin/security/page.tsx +++ b/app/admin/security/page.tsx @@ -1,6 +1,5 @@ import { Section, Empty } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { Button } from "@/components/Button"; import { searchSecurityEvents } from "@/lib/server/repositories/securityEvents"; import { getCurrentSession } from "@/lib/server/session"; @@ -56,26 +55,21 @@ export default async function AdminSecurityPage({ data-mount-row >
-
- $ - - admin.audit +

+ Admin / Audit + + {events.length} - · - - {String(events.length).padStart(2, "0")} - -

-

- audit console +

+

+ Audit console

- - export csv + Export CSV @@ -84,16 +78,14 @@ export default async function AdminSecurityPage({ data-mount-row > {[ - ["event", "event type"], - ["result", "result"], - ["user", "username"], - ["ip", "ip"], - ["limit", "limit"], + ["event", "Event type"], + ["result", "Result"], + ["user", "Username"], + ["ip", "IP"], + ["limit", "Limit"], ].map(([name, label]) => ( ))}
-
+
{events.length === 0 ? ( - no events + No events ) : ( events.map((ev, i) => (
{ev.event_type}
{ev.result} @@ -126,11 +118,11 @@ export default async function AdminSecurityPage({ {ev.username && ( @{ev.username} )} - {ev.username && ev.ip && } + {ev.username && ev.ip && ·} {ev.ip && {ev.ip}} {Object.keys(ev.metadata || {}).length > 0 && ( <> - + · {JSON.stringify(ev.metadata)} diff --git a/app/admin/users/page.tsx b/app/admin/users/page.tsx index 5d21446..765bf7b 100644 --- a/app/admin/users/page.tsx +++ b/app/admin/users/page.tsx @@ -1,6 +1,5 @@ import { Section, Empty, Row, RowLabel, RowValue } from "@/components/Section"; import { Tag } from "@/components/Tag"; -import { Glyph } from "@/components/Glyph"; import { query } from "@/lib/server/db"; import { getCurrentSession } from "@/lib/server/session"; import { redirect } from "next/navigation"; @@ -71,26 +70,18 @@ export default async function AdminUsersPage({ data-mount-stagger >
-
- $ - - admin.users - - · - - {String(users.length).padStart(2, "0")} - -
-

- users +

+ Admin / Users · {users.length} records +

+

+ Users

-
- $ +
@@ -98,7 +89,7 @@ export default async function AdminUsersPage({
{users.length === 0 ? ( - no users found + No users found ) : ( users.map(u => ( - + @{u.username} - {u.role === "admin" && admin} + {u.role === "admin" && Admin} {u.status} - {u.email} - {u.created_at?.slice(0, 10)} -
+
{u.status !== "banned" ? (
@@ -135,7 +124,7 @@ export default async function AdminUsersPage({ type="submit" className="text-secondary hover:text-danger transition-colors" > - ban + Ban
) : ( @@ -143,9 +132,9 @@ export default async function AdminUsersPage({ )} diff --git a/app/admin/verify/TelegramStepUpWidget.tsx b/app/admin/verify/TelegramStepUpWidget.tsx index 32e696d..be5904f 100644 --- a/app/admin/verify/TelegramStepUpWidget.tsx +++ b/app/admin/verify/TelegramStepUpWidget.tsx @@ -57,8 +57,8 @@ export function TelegramStepUpWidget({ return (
-

- a one-time code will be sent to your linked telegram account. +

+ A one-time code will be sent to your linked Telegram account.

{error && ( @@ -69,12 +69,12 @@ export function TelegramStepUpWidget({ {step === "send" ? ( ) : (
)} diff --git a/app/admin/verify/page.tsx b/app/admin/verify/page.tsx index a2f4278..e6d0426 100644 --- a/app/admin/verify/page.tsx +++ b/app/admin/verify/page.tsx @@ -6,7 +6,6 @@ import { adminStepUpTtlSeconds, } from "@/lib/server/config"; import { Alert } from "@/components/Alert"; -import { Glyph } from "@/components/Glyph"; import { TelegramStepUpWidget } from "./TelegramStepUpWidget"; export const dynamic = "force-dynamic"; @@ -27,27 +26,22 @@ export default async function AdminVerifyPage({ const callbackUrl = `${authBaseUrl()}/api/admin/telegram-step-up`; const errorMessages: Record = { - invalid_payload: "telegram verification failed — try again", + invalid_payload: "Telegram verification failed — try again", identity_mismatch: - "the telegram account does not match your linked identity", + "The Telegram account does not match your linked identity", }; return (
-
- $ - - admin.verify - -
-

- verify identity +

admin / verify

+

+ Verify identity

-

- admin access requires telegram verification every{" "} - +

+ Admin access requires Telegram verification every{" "} + {adminStepUpTtlSeconds / 60} {" "} minutes. @@ -57,26 +51,20 @@ export default async function AdminVerifyPage({ {error && (

- {errorMessages[error] ?? "verification failed — try again"} + {errorMessages[error] ?? "Verification failed — try again"}
)} -
+
{!current.user.telegramId ? ( -
- - - no telegram account is linked — link one from the dashboard first - -
+ + No Telegram account is linked — link one from the dashboard first + ) : !botUsername ? ( -
- - - TELEGRAM_BOT_USERNAME is not configured - -
+ + TELEGRAM_BOT_USERNAME is not configured + ) : (
-
- $ - - admin.webhooks +

+ admin / webhooks + + {deliveries.length} - · - - {String(deliveries.length).padStart(2, "0")} - -

-

- webhook deliveries +

+

+ Webhook deliveries

-