import { useQuery } from "@tanstack/react-query"; import { Link, createFileRoute, notFound } from "@tanstack/react-router"; import { useState } from "react"; import { AuthPageCard, AuthPageShell } from "@/client/features/auth/AuthPage"; import { captureClientEvent } from "@/client/lib/posthog"; import { authClient, signOutAndRedirect, useSession } from "@/lib/auth-client"; import { isHostedClientAuthMode } from "@/lib/auth-mode"; export const Route = createFileRoute("/accept-invitation/$id")({ beforeLoad: () => { if (!isHostedClientAuthMode()) { throw notFound(); } }, component: AcceptInvitationPage, }); function AcceptInvitationPage() { const { id } = Route.useParams(); const { data: session, isPending: isSessionPending } = useSession(); return ( {isSessionPending ? null : session?.user ? ( ) : ( )} ); } // getInvitation requires a session matching the invited email, so a // logged-out visitor gets a generic shell — no invitation details are // exposed pre-auth by design. function SignedOutInvitationCard({ invitationId }: { invitationId: string }) { const redirect = `/accept-invitation/${invitationId}`; return (

You’ve been invited to join an organization on CrawlerX. Sign in with the email address that received the invitation to accept it.

Create account Sign in
); } function InvitationCard({ invitationId, userEmail, }: { invitationId: string; userEmail: string; }) { const [isSubmitting, setIsSubmitting] = useState(false); const [actionError, setActionError] = useState(null); const [declined, setDeclined] = useState(false); const invitationQuery = useQuery({ queryKey: ["invitation", invitationId], queryFn: async () => { const result = await authClient.organization.getInvitation({ query: { id: invitationId }, }); if (result.error) { throw new Error(result.error.message || "Invitation not found"); } return result.data; }, retry: false, }); async function handleAccept() { setActionError(null); setIsSubmitting(true); try { const accepted = await authClient.organization.acceptInvitation({ invitationId, }); if (accepted.error) { setActionError( accepted.error.message || "We couldn't accept the invitation.", ); setIsSubmitting(false); return; } // Accepting updates the session row but not the session cookie cache; // setActive refreshes the cookie so the app opens in the joined org // immediately instead of after the cache expires. await authClient.organization.setActive({ organizationId: accepted.data.invitation.organizationId, }); captureClientEvent("team:invitation_accept"); // Full navigation: every cached query in this tab belongs to the old // workspace. window.location.assign("/"); } catch { setActionError("We couldn't accept the invitation. Please try again."); setIsSubmitting(false); } } async function handleDecline() { setActionError(null); setIsSubmitting(true); try { const result = await authClient.organization.rejectInvitation({ invitationId, }); if (result.error) { setActionError( result.error.message || "We couldn't decline the invitation.", ); setIsSubmitting(false); return; } captureClientEvent("team:invitation_decline"); setDeclined(true); } catch { setActionError("We couldn't decline the invitation. Please try again."); setIsSubmitting(false); } } if (invitationQuery.isPending) { return (
); } if (invitationQuery.isError) { return (

This invitation may have expired, been canceled, or belong to a different email address. You’re signed in as{" "} {userEmail} .

If the invitation was sent to another address, sign out and sign back in with that email. Otherwise ask your teammate to send a new invite.

Go to dashboard
); } if (declined) { return (

You declined the invitation to join{" "} {invitationQuery.data.organizationName} .

Go to dashboard
); } return (

{invitationQuery.data.inviterEmail} {" "} invited you to join{" "} {invitationQuery.data.organizationName} {" "} on CrawlerX.

{actionError ?

{actionError}

: null}
); }