/** * Standalone invite acceptance page (not wrapped in admin Shell). * Validates an invite token, then registers a passkey to complete signup. */ import { Button, Input, Loader } from "@cloudflare/kumo"; import { Link, useSearch } from "@tanstack/react-router"; import * as React from "react"; import { validateInviteToken, type InviteVerifyResult } from "../lib/api"; import { PasskeyRegistration } from "./auth/PasskeyRegistration"; import { LogoLockup } from "./Logo.js"; type InviteStep = "verify" | "register" | "error"; interface RegisterStepProps { inviteData: InviteVerifyResult; token: string; } function handleInviteSuccess() { window.location.href = "/_emdash/admin"; } function RegisterStep({ inviteData, token }: RegisterStepProps) { const [name, setName] = React.useState(""); return (

You've been invited!

You'll be joining as{" "} {inviteData.roleName}

setName(e.target.value)} placeholder="Jane Doe" autoComplete="name" autoFocus />

Create your passkey

Passkeys are a secure, passwordless way to sign in using your device's biometrics, PIN, or security key.

); } interface ErrorStepProps { message: string; code?: string; } function ErrorStep({ message, code }: ErrorStepProps) { return (

{code === "TOKEN_EXPIRED" ? "Invite expired" : code === "INVALID_TOKEN" ? "Invalid invite link" : code === "USER_EXISTS" ? "Account already exists" : "Something went wrong"}

{message}

{code === "USER_EXISTS" ? ( ) : ( <>

Please ask your administrator to send a new invite.

)}
); } export function InviteAcceptPage() { const { token: urlToken } = useSearch({ strict: false }); const [step, setStep] = React.useState("verify"); const [error, setError] = React.useState(); const [errorCode, setErrorCode] = React.useState(); const [isLoading, setIsLoading] = React.useState(true); const [inviteData, setInviteData] = React.useState(null); const [token, setToken] = React.useState(null); React.useEffect(() => { if (!urlToken) { setError("No invite token provided"); setStep("error"); setIsLoading(false); return; } setToken(urlToken); void verifyToken(urlToken); }, [urlToken]); const verifyToken = async (tokenToVerify: string) => { setIsLoading(true); setError(undefined); setErrorCode(undefined); try { const result = await validateInviteToken(tokenToVerify); setInviteData(result); setStep("register"); } catch (err) { const verifyError = err instanceof Error ? err : new Error(String(err)); const errorWithCode = verifyError as Error & { code?: string }; setError(verifyError.message); setErrorCode(typeof errorWithCode.code === "string" ? errorWithCode.code : undefined); setStep("error"); } finally { setIsLoading(false); } }; if (isLoading) { return (

Verifying your invite...

); } return (

{step === "register" && "Accept Invite"} {step === "error" && "Invite Error"}

{step === "register" && inviteData && token && ( )} {step === "error" && ( )}
); } export default InviteAcceptPage;