import { redirect } from "next/navigation"; import { AuthTokenType, UserStatus } from "@prisma/client"; import { Button, PageHeader, SectionCard } from "@/components/ui"; import { consumeAuthToken } from "@/lib/auth-tokens"; import { hashPassword } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import { getRequestAuditContext, writeAuditTrail } from "@/lib/audit"; async function acceptInvite(formData: FormData) { "use server"; const requestContext = await getRequestAuditContext(); const tokenRaw = formData.get("token"); const token = typeof tokenRaw === "string" ? tokenRaw.trim() : ""; const fullNameRaw = formData.get("fullName"); const fullName = typeof fullNameRaw === "string" ? fullNameRaw.trim() : ""; const passwordRaw = formData.get("password"); const password = typeof passwordRaw === "string" ? passwordRaw : ""; const confirmRaw = formData.get("confirmPassword"); const confirmPassword = typeof confirmRaw === "string" ? confirmRaw : ""; if (!token || !fullName || !password || !confirmPassword) { redirect(`/invite/${encodeURIComponent(token)}?error=missing_fields`); } if (password !== confirmPassword) { redirect(`/invite/${encodeURIComponent(token)}?error=password_mismatch`); } const resolvedToken = await consumeAuthToken(token, AuthTokenType.INVITE_ACCEPTANCE); if (!resolvedToken.valid) { redirect( `/invite/${encodeURIComponent(token)}?error=${resolvedToken.reason === "expired" ? "expired_token" : "invalid_token"}` ); } const user = await prisma.user.findUnique({ where: { id: resolvedToken.token.userId } }); if (!user || user.status !== UserStatus.INVITED) { redirect(`/invite/${encodeURIComponent(token)}?error=invalid_token`); } await prisma.$transaction([ prisma.user.update({ where: { id: user.id }, data: { fullName, passwordHash: await hashPassword(password), status: UserStatus.ACTIVE } }), prisma.authToken.update({ where: { id: resolvedToken.token.id }, data: { consumedAt: new Date() } }) ]); await writeAuditTrail({ tenantId: user.tenantId, actorUserId: user.id, entityType: "user", entityId: user.id, action: "user_invite_accepted", metadata: { fullName, email: user.email }, ipAddress: requestContext.ipAddress, userAgent: requestContext.userAgent }); redirect("/login?success=invite_accepted"); } export default async function InvitationPage({ params, searchParams }: { params: Promise<{ token: string }>; searchParams?: Promise<{ error?: string }>; }) { const { token } = await params; const paramsData = await (searchParams ?? Promise.resolve({ error: undefined })); const error = paramsData?.error; const resolvedToken = await consumeAuthToken(token, AuthTokenType.INVITE_ACCEPTANCE); const isTokenValid = resolvedToken.valid; const tokenInvalidMessage = error === "expired_token" ? "Token undangan sudah kedaluwarsa." : error === "invalid_token" ? "Link undangan tidak valid." : error === "missing_fields" ? "Lengkapi data nama dan password." : error === "password_mismatch" ? "Password tidak cocok." : null; return (
{!isTokenValid || tokenInvalidMessage ? (

{tokenInvalidMessage || "Link undangan tidak valid atau sudah kedaluwarsa."}

) : null} {isTokenValid ? (
) : ( )}
); }