import { redirect } from "next/navigation"; import { auth } from "@/auth"; import { prisma } from "@/lib/db"; import type { BlockedRequesterDTO, LinkedAccountDTO, PendingLinkRequestDTO, ProfileDTO, } from "@/types/profile"; import { ProfileForm } from "@/components/profile/profile-form"; import { RequestLinkForm } from "@/components/profile/request-link-form"; import { PendingLinkRequests } from "@/components/profile/pending-link-requests"; import { LinkedAccounts } from "@/components/profile/linked-accounts"; import { BlockedLinkRequests } from "@/components/profile/blocked-link-requests"; const LINK_ACCOUNT_SELECT = { id: true, email: true, name: true, firstName: true, lastName: true, avatar: true, } as const; type LinkAccountRow = { id: string; email: string; name: string | null; firstName: string | null; lastName: string | null; avatar: string | null; }; /** Display name for another account: profile first/last name when set, * falling back to the legacy sign-up name. */ function identity(row: LinkAccountRow) { const full = [row.firstName, row.lastName].filter(Boolean).join(" ").trim(); return { id: row.id, email: row.email, name: full || row.name?.trim() || null, avatar: row.avatar, }; } function dateLabel(date: Date) { return date.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); } export default async function ProfilePage() { const session = await auth(); // Defense in depth: proxy.ts already redirects unauthenticated requests, // but every protected data boundary should check for itself too. if (!session?.user) redirect("/login"); const userId = session.user.id; const [user, pendingRequests, links, blocks] = await Promise.all([ prisma.user.findUnique({ where: { id: userId }, select: { name: true, firstName: true, lastName: true, avatar: true }, }), // Requests addressed to this account -- the ones it can act on. prisma.accountLinkRequest.findMany({ where: { toUserId: userId }, orderBy: { createdAt: "desc" }, include: { fromUser: { select: LINK_ACCOUNT_SELECT } }, }), // Confirmed links in either direction; the other side of the pair is // whichever isn't this account. prisma.accountLink.findMany({ where: { OR: [{ userId }, { linkedUserId: userId }] }, include: { user: { select: LINK_ACCOUNT_SELECT }, linkedUser: { select: LINK_ACCOUNT_SELECT }, }, }), // Blocks this account issued: who is barred from requesting it. prisma.accountLinkBlock.findMany({ where: { toUserId: userId }, orderBy: { createdAt: "desc" }, include: { fromUser: { select: LINK_ACCOUNT_SELECT } }, }), ]); if (!user) redirect("/login"); const profile: ProfileDTO = { firstName: user.firstName, lastName: user.lastName, avatar: user.avatar, }; const pending: PendingLinkRequestDTO[] = pendingRequests.map((request) => ({ requestId: request.id, requestedAtLabel: dateLabel(request.createdAt), ...identity(request.fromUser), })); const linked: LinkedAccountDTO[] = links.map((link) => ({ linkId: link.id, ...identity(link.userId === userId ? link.linkedUser : link.user), })); const blocked: BlockedRequesterDTO[] = blocks.map((block) => ({ blockId: block.id, blockedAtLabel: dateLabel(block.createdAt), ...identity(block.fromUser), })); return (
Your name and photo, shown in the menu on the left — and your links to other accounts on this server.