"use client"; import * as React from "react"; import { useSession } from "next-auth/react"; import { Loader2, Mail, ShieldCheck, ShieldAlert, UserCircle2 } from "lucide-react"; import { api } from "@/lib/trpc"; import { Skeleton } from "@/components/ui/skeleton"; const SOURCE_LABELS: Record = { primary: "Primary", "oauth:github": "GitHub", "oauth:google": "Google", "oauth:authentik": "Authentik", manual: "Manually verified", }; function sourceLabel(source: string): string { return SOURCE_LABELS[source] ?? source; } /** * Best-effort "X ago" without pulling in a date library. The "Linked emails" * row only needs coarse buckets ("just now", "3d ago", "2mo ago") — we never * surface the raw timestamp on this page, so jitter is fine. */ function relativeTime(when: Date | string | null | undefined): string { if (!when) return "never"; const then = when instanceof Date ? when.getTime() : new Date(when).getTime(); const diffMs = Date.now() - then; if (Number.isNaN(diffMs) || diffMs < 0) return "just now"; const sec = Math.floor(diffMs / 1000); if (sec < 60) return "just now"; const min = Math.floor(sec / 60); if (min < 60) return `${min}m ago`; const hr = Math.floor(min / 60); if (hr < 24) return `${hr}h ago`; const day = Math.floor(hr / 24); if (day < 30) return `${day}d ago`; const mo = Math.floor(day / 30); if (mo < 12) return `${mo}mo ago`; const yr = Math.floor(day / 365); return `${yr}y ago`; } export default function ProfileSettingsPage() { const { data: session } = useSession(); const identitiesQuery = api.identity.listMine.useQuery(); return (

Profile

Personal account settings. These apply to you across every workspace.

Linked emails

Workspace invites sent to any of these addresses will be accepted under your account. Verified emails are confirmed by the provider that supplied them — we do not trust an unverified claim.

{session?.user?.email ? (
Signed in as {session.user.email}
) : null}
{identitiesQuery.isLoading ? (
    {[0, 1].map((i) => (
  • ))}
) : identitiesQuery.isError ? (

Could not load your linked emails. Refresh to retry.

) : (identitiesQuery.data?.length ?? 0) === 0 ? ( // Should never happen for an authenticated user (the migration backfills // a primary identity for every existing users row) but cheaper to render // a friendly empty state than to throw on the page.

No emails linked. This is unexpected — please contact support.

) : (
    {identitiesQuery.data!.map((identity) => (
  • {identity.email} {sourceLabel(identity.source)}

    Last used {relativeTime(identity.lastUsedAt ?? identity.createdAt)}

    {identity.verified ? (
    Verified
    ) : (
    Pending
    )}
  • ))}
)}
); }