Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ pip-delete-this-directory.txt

# macOS
.DS_Store

# Saved design reference pages (contain third-party account PII; never commit)
reference/
63 changes: 31 additions & 32 deletions app/activate/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -49,16 +48,16 @@ export default async function ActivatePage({
<AuthShell tag="auth/authorized" account={{ username: current.user.username }}>
<div className="flex items-start gap-4 mb-7">
<div
className="h-12 w-12 border border-accent flex items-center justify-center text-accent text-meta uppercase tracking-wider shrink-0"
className="h-12 w-12 border border-accent rounded-md flex items-center justify-center text-accent-strong text-[13px] font-medium shrink-0"
aria-hidden
>
{activation.app.name.slice(0, 2)}
</div>
<div className="min-w-0 flex-1">
<div className="text-meta uppercase tracking-wider text-muted mb-1">
authorized
<div className="text-[12px] text-muted mb-1">
Authorized
</div>
<h1 className="text-[26px] tracking-tightest text-fg truncate leading-none">
<h1 className="text-[26px] text-fg truncate leading-none">
{activation.app.name}
</h1>
</div>
Expand All @@ -71,7 +70,7 @@ export default async function ActivatePage({
</div>
<Link href="/">
<Button variant="secondary" type="button">
return to dashboard
Return to dashboard
</Button>
</Link>
</AuthShell>
Expand Down Expand Up @@ -104,44 +103,44 @@ export default async function ActivatePage({
<AuthShell tag="auth/authorize" account={{ username: current.user.username }}>
<div className="flex items-start gap-4 mb-7">
<div
className="h-12 w-12 border border-accent flex items-center justify-center text-accent text-meta uppercase tracking-wider shrink-0"
className="h-12 w-12 border border-accent rounded-md flex items-center justify-center text-accent-strong text-[13px] font-medium shrink-0"
aria-hidden
>
{activation.app.name.slice(0, 2)}
</div>
<div className="min-w-0 flex-1">
<div className="text-meta uppercase tracking-wider text-muted mb-1">
authorize app
<div className="text-[12px] text-muted mb-1">
Authorize app
</div>
<h1 className="text-[26px] tracking-tightest text-fg truncate leading-none">
<h1 className="text-[26px] text-fg truncate leading-none">
{activation.app.name}
</h1>
<div className="mt-2 flex items-center gap-2">
{expired ? (
<Tag tone="danger">expired</Tag>
<Tag tone="danger">Expired</Tag>
) : (
<span className="text-meta text-muted">
expires {activation.expiresAt.slice(0, 16).replace("T", " ")}
<span className="text-[12px] text-muted">
Expires {activation.expiresAt.slice(0, 16).replace("T", " ")}
</span>
)}
</div>
</div>
</div>

<div className="border-t border-rule mb-5">
<DetailRow label="signed in" value={current.user.username} />
<DetailRow label="email" value={current.user.email} />
<DetailRow label="Signed in" value={current.user.username} />
<DetailRow label="Email" value={current.user.email} />
<DetailRow
label="origin"
label="Origin"
value={`${activation.userAgent || "unknown device"} · ${activation.ip || "unknown ip"}`}
/>
{requiredProduct && (
<DetailRow
label="requires"
label="Requires"
value={requiredProduct}
right={
<Tag tone={blocked ? "warning" : "success"}>
{blocked ? "missing" : "active"}
{blocked ? "Missing" : "Active"}
</Tag>
}
/>
Expand All @@ -150,8 +149,8 @@ export default async function ActivatePage({
</div>

<div className="mb-5">
<div className="text-meta uppercase tracking-wider text-muted mb-2">
will share
<div className="text-[12px] text-muted mb-2">
Will share
</div>
<div className="border-t border-rule">
{standardScopes.map((scope) => {
Expand All @@ -161,8 +160,8 @@ export default async function ActivatePage({
key={scope}
className="flex items-baseline gap-3 py-2.5 border-b border-rule"
>
<Glyph kind="ok" />
<span className="text-meta text-fg flex-1">{item.label}</span>
<span className="w-2 h-2 rounded-full bg-accent shrink-0 translate-y-0.5" aria-hidden />
<span className="text-[13px] text-fg flex-1">{item.label}</span>
<input
type="hidden"
name="scopes"
Expand All @@ -185,13 +184,13 @@ export default async function ActivatePage({
value={scope}
defaultChecked
form="approve-form"
className="appearance-none w-4 h-4 border border-rule bg-transparent checked:bg-accent checked:border-accent transition-colors shrink-0 translate-y-0.5"
className="appearance-none w-4 h-4 border border-rule rounded bg-transparent checked:bg-accent checked:border-accent transition-colors shrink-0 translate-y-0.5"
/>
<span className="text-meta text-fg flex-1 group-hover:text-accent transition-colors">
<span className="text-[13px] text-fg flex-1 group-hover:text-accent-strong transition-colors">
{item.label}
</span>
<span className="text-micro uppercase tracking-wider text-accent">
optional
<span className="text-[12px] text-accent-strong">
Optional
</span>
</label>
);
Expand All @@ -202,15 +201,15 @@ export default async function ActivatePage({
{blocked && !expired && requiredProduct && (
<div className="mb-5">
<Alert tone="warning">
active {requiredProduct} subscription required to approve
Active {requiredProduct} subscription required to approve
</Alert>
</div>
)}

{error === "csrf" && (
<div className="mb-5">
<Alert tone="warning">
your confirmation expired. review and approve again.
Your confirmation expired. Review and approve again.
</Alert>
</div>
)}
Expand All @@ -223,7 +222,7 @@ export default async function ActivatePage({
<input type="hidden" name="csrf_token" value={csrfToken} />
<input type="hidden" name="token" value={token} />
<Button variant="ghost" type="submit" disabled={expired}>
deny
Deny
</Button>
</form>
<form
Expand All @@ -234,7 +233,7 @@ export default async function ActivatePage({
<input type="hidden" name="csrf_token" value={csrfToken} />
<input type="hidden" name="token" value={token} />
<Button type="submit" disabled={expired || blocked}>
approve
Approve
</Button>
</form>
</div>
Expand All @@ -253,10 +252,10 @@ function DetailRow({
}) {
return (
<div className="flex items-baseline justify-between gap-4 py-2 border-b border-rule last:border-b-0">
<span className="text-meta uppercase tracking-wider text-muted shrink-0">
<span className="text-[12px] text-muted shrink-0">
{label}
</span>
<span className="text-meta text-right truncate flex-1 flex items-center justify-end gap-2">
<span className="text-[13px] text-right truncate flex-1 flex items-center justify-end gap-2">
<span className="truncate text-fg">{value}</span>
{right}
</span>
Expand Down
30 changes: 12 additions & 18 deletions app/admin/activation-requests/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -51,35 +50,31 @@ export default async function AdminActivationRequestsPage() {
data-mount-stagger
>
<header className="mb-10" data-mount-row>
<div className="flex items-baseline gap-2 mb-2 text-meta">
<span className="text-danger">$</span>
<span className="uppercase tracking-wider text-muted">
admin.activations
</span>
<span className="text-faint">·</span>
<span className="text-meta text-faint tabular-nums">
{String(requests.length).padStart(2, "0")}
<div className="flex items-baseline gap-2 mb-2">
<span className="text-[13px] text-muted">admin.activations</span>
<span className="text-[13px] text-faint tabular-nums">
{requests.length}
</span>
</div>
<h1 className="text-[32px] tracking-tightest text-fg leading-none">
activation requests
<h1 className="text-[32px] text-fg leading-none">
Activation requests
</h1>
</header>

<div data-mount-row>
<Section
index="1.0"
title="recent activations"
title="Recent activations"
hint={`last ${requests.length}`}
>
{requests.length === 0 ? (
<Empty>no activation requests</Empty>
<Empty>No activation requests</Empty>
) : (
requests.map(req => (
<Row key={req.public_id}>
<RowLabel>
<span className="flex items-baseline gap-2">
<span className="text-fg normal-case tracking-normal truncate">
<span className="text-fg truncate">
{req.app_name}
</span>
<Tag tone={statusTone[req.status] ?? "neutral"}>
Expand All @@ -90,24 +85,23 @@ export default async function AdminActivationRequestsPage() {
<RowValue>
{req.approved_username && (
<>
<Glyph kind="ok" />
<span className="text-muted truncate">
@{req.approved_username}
</span>
<Glyph kind="dot" />
<span className="text-faint">·</span>
</>
)}
{req.requested_subject && (
<>
<span className="text-faint truncate">
sub:{req.requested_subject}
</span>
<Glyph kind="dot" />
<span className="text-faint">·</span>
</>
)}
<span className="text-faint truncate">{req.public_id}</span>
</RowValue>
<span className="text-meta text-faint tabular-nums">
<span className="text-[13px] text-faint tabular-nums">
{req.created_at?.slice(0, 10)}
</span>
</Row>
Expand Down
36 changes: 14 additions & 22 deletions app/admin/bans/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -41,25 +40,20 @@ export default async function AdminBansPage() {
data-mount-stagger
>
<header className="mb-10" data-mount-row>
<div className="flex items-baseline gap-2 mb-2 text-meta">
<span className="text-danger">$</span>
<span className="uppercase tracking-wider text-muted">
admin.bans
</span>
<span className="text-faint">·</span>
<span className="text-meta text-faint tabular-nums">
{String(active.length).padStart(2, "0")} active
<div className="flex items-baseline gap-2 mb-2">
<span className="text-[13px] text-muted tabular-nums">
{active.length} active
</span>
</div>
<h1 className="text-[32px] tracking-tightest text-fg leading-none">
bans
<h1 className="text-[32px] tracking-tight text-fg leading-none">
Bans
</h1>
</header>

<div data-mount-row>
<Section index="1.0" title="active bans" hint="enforced now">
<Section index="1.0" title="Active bans" hint="Enforced now">
{active.length === 0 ? (
<Empty>no active bans</Empty>
<Empty>No active bans</Empty>
) : (
active.map(ban => (
<div
Expand All @@ -76,13 +70,11 @@ export default async function AdminBansPage() {
{ban.reason && (
<div className="text-muted mt-1">{ban.reason}</div>
)}
<div className="text-faint mt-1 flex items-baseline gap-2">
<span className="tabular-nums">
{ban.created_at?.slice(0, 10)}
</span>
<div className="text-faint mt-1 flex items-baseline gap-2 tabular-nums">
<span>{ban.created_at?.slice(0, 10)}</span>
{ban.expires_at && (
<>
<Glyph kind="dot" />
<span aria-hidden>·</span>
<span>expires {ban.expires_at.slice(0, 10)}</span>
</>
)}
Expand All @@ -92,9 +84,9 @@ export default async function AdminBansPage() {
<input type="hidden" name="banId" value={ban.id} />
<button
type="submit"
className="text-meta uppercase tracking-wider text-secondary hover:text-accent transition-colors"
className="text-[13px] text-secondary hover:text-accent-strong transition-colors"
>
revoke
Revoke
</button>
</form>
</div>
Expand All @@ -105,7 +97,7 @@ export default async function AdminBansPage() {

{revoked.length > 0 && (
<div data-mount-row>
<Section index="2.0" title="revoked bans" hint="historical">
<Section index="2.0" title="Revoked bans" hint="Historical">
{revoked.map(ban => (
<div
key={ban.id}
Expand All @@ -123,7 +115,7 @@ export default async function AdminBansPage() {
)}
<div className="text-faint mt-1 flex items-baseline gap-2 tabular-nums">
<span>{ban.created_at?.slice(0, 10)}</span>
<Glyph kind="dot" />
<span aria-hidden>·</span>
<span>revoked {ban.revoked_at?.slice(0, 10)}</span>
</div>
</div>
Expand Down
Loading