"use client"; import { useQuery } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; import { AlertCircle, ArrowRight, CheckCircle2, LogIn, Shield, Sparkles, Users, XCircle, } from "lucide-react"; import { motion } from "motion/react"; import Image from "next/image"; import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; import { use, useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; import { acceptInviteMutationAtom } from "@/atoms/invites/invites-mutation.atoms"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; import type { AcceptInviteResponse } from "@/contracts/types/invites.types"; import { invitesApiService } from "@/lib/apis/invites-api.service"; import { getBearerToken } from "@/lib/auth-utils"; import { trackSearchSpaceInviteAccepted, trackSearchSpaceInviteDeclined, trackSearchSpaceUserAdded, } from "@/lib/posthog/events"; import { cacheKeys } from "@/lib/query-client/cache-keys"; export default function InviteAcceptPage() { const params = useParams(); const router = useRouter(); const inviteCode = params.invite_code as string; const { data: inviteInfo = null, isLoading: loading } = useQuery({ queryKey: cacheKeys.invites.info(inviteCode), enabled: !!inviteCode, staleTime: 5 * 60 * 1000, queryFn: async () => { if (!inviteCode) return null; return invitesApiService.getInviteInfo({ invite_code: inviteCode, }); }, }); const { mutateAsync: acceptInviteMutation } = useAtomValue(acceptInviteMutationAtom); const acceptInvite = useCallback(async () => { if (!inviteCode) { toast.error("No invite code provided"); return null; } try { const result = await acceptInviteMutation({ invite_code: inviteCode }); return result; } catch (err: any) { toast.error(err.message || "Failed to accept invite"); throw err; } }, [inviteCode, acceptInviteMutation]); const [accepting, setAccepting] = useState(false); const [accepted, setAccepted] = useState(false); const [acceptedData, setAcceptedData] = useState(null); const [error, setError] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(null); // Check if user is logged in useEffect(() => { if (typeof window !== "undefined") { const token = getBearerToken(); setIsLoggedIn(!!token); } }, []); const handleAccept = async () => { setAccepting(true); setError(null); try { const result = await acceptInvite(); if (result) { setAccepted(true); setAcceptedData(result); // Track invite accepted and user added events trackSearchSpaceInviteAccepted( result.search_space_id, result.search_space_name, result.role_name ); trackSearchSpaceUserAdded( result.search_space_id, result.search_space_name, result.role_name ); } } catch (err: any) { setError(err.message || "Failed to accept invite"); } finally { setAccepting(false); } }; const handleDecline = () => { // Track invite declined event trackSearchSpaceInviteDeclined(inviteInfo?.search_space_name); router.push("/dashboard"); }; const handleLoginRedirect = () => { // Store the invite code to redirect back after login localStorage.setItem("pending_invite_code", inviteCode); // Save the current invite page URL so we can return after authentication localStorage.setItem("surfsense_redirect_path", `/invite/${inviteCode}`); // Redirect to login (we manually set the path above since invite pages need special handling) window.location.href = "/login"; }; // Check for pending invite after login useEffect(() => { if (isLoggedIn && typeof window !== "undefined") { const pendingInvite = localStorage.getItem("pending_invite_code"); if (pendingInvite === inviteCode) { localStorage.removeItem("pending_invite_code"); // Auto-accept the invite after redirect handleAccept(); } } }, [isLoggedIn, inviteCode]); return (
{/* Background decoration */}
{loading || isLoggedIn === null ? (

Loading invite details...

) : accepted && acceptedData ? ( <> Welcome to the team! You've successfully joined {acceptedData.search_space_name}

{acceptedData.search_space_name}

Search Space

{acceptedData.role_name}

Your Role

) : !inviteInfo?.is_valid ? ( <> Invalid Invite {inviteInfo?.message || "This invite link is no longer valid"}

The invite may have expired, reached its maximum uses, or been revoked by the owner.

) : !isLoggedIn ? ( <> You're Invited! Sign in to join {inviteInfo?.search_space_name || "this search space"}

{inviteInfo?.search_space_name}

Search Space

{inviteInfo?.role_name && (

{inviteInfo.role_name}

Role you'll receive

)}
) : ( <> You're Invited! Accept this invite to join {inviteInfo?.search_space_name || "this search space"}

{inviteInfo?.search_space_name}

Search Space

{inviteInfo?.role_name && (

{inviteInfo.role_name}

Role you'll receive

)}
{error && ( {error} )}
)}
{/* Branding */} SurfSense SurfSense
); }