/** * InvitePage * * Public page for accepting invitations via email link. * URL: /invite/:token * * This page is primarily used for NEW users who receive an invitation email * and need to register first. * * Flow for NEW users (via email link): * 1. User opens email link → lands here * 2. Token is validated and stored in localStorage * 3. User clicks "Registrieren" → redirects to /register * 4. After registration, user logs in * 5. Login page checks localStorage, redirects back here * 6. User clicks "Einladung annehmen" * * For EXISTING users: * The in-app notification system handles invitations directly. * When an invitation is created for an existing user, a notification * appears in their notification bell with accept/decline buttons. */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import { useInvitations, type InvitationValidation } from '../hooks/useInvitations'; import { FaCheckCircle, FaTimesCircle, FaSpinner, FaSignInAlt, FaUserPlus } from 'react-icons/fa'; import styles from './InvitePage.module.css'; // Key for storing pending invitation token export const PENDING_INVITATION_KEY = 'pendingInvitationToken'; export const InvitePage: React.FC = () => { const { token } = useParams<{ token: string }>(); const navigate = useNavigate(); const { validateInvitation, acceptInvitation } = useInvitations(); // Check if user has auth token (simplified check) const isAuthenticated = !!sessionStorage.getItem('auth_authority'); // State const [validation, setValidation] = useState(null); const [validating, setValidating] = useState(true); const [accepting, setAccepting] = useState(false); const [success, setSuccess] = useState(false); const [error, setError] = useState(null); // Validate token on mount useEffect(() => { const validate = async () => { if (!token) { setError('Kein Einladungs-Token angegeben'); setValidating(false); return; } const result = await validateInvitation(token); setValidation(result); setValidating(false); // If invitation is valid but user is not authenticated, // store the token for later use after login/registration // Use localStorage instead of sessionStorage to persist across tabs // (e.g., when user opens password reset email in a new tab) if (result.valid && !isAuthenticated) { localStorage.setItem(PENDING_INVITATION_KEY, token); } }; validate(); }, [token, validateInvitation, isAuthenticated]); // Accept invitation (for authenticated users) const handleAccept = async () => { if (!token) return; setAccepting(true); setError(null); const result = await acceptInvitation(token); if (result.success) { // Clear pending invitation token localStorage.removeItem(PENDING_INVITATION_KEY); setSuccess(true); // Redirect to dashboard after 2 seconds setTimeout(() => { navigate('/'); }, 2000); } else { setError(result.error || 'Fehler beim Annehmen der Einladung'); } setAccepting(false); }; // Handle redirect to login (stores token first) const handleLoginRedirect = () => { if (token) { localStorage.setItem(PENDING_INVITATION_KEY, token); } navigate('/login', { state: { from: { pathname: `/invite/${token}` } } }); }; // Handle redirect to register (stores token first) const handleRegisterRedirect = () => { if (token) { localStorage.setItem(PENDING_INVITATION_KEY, token); } navigate('/register', { state: { from: { pathname: `/invite/${token}` }, pendingInvitation: true } }); }; // Loading state if (validating) { return (

Einladung wird überprüft...

); } // Invalid invitation if (!validation?.valid) { return (

Ungültige Einladung

{validation?.reason || 'Diese Einladung ist nicht gültig.'}

Zur Anmeldung
); } // Success state if (success) { return (

Erfolgreich!

Sie wurden erfolgreich zum Mandanten hinzugefügt.

Sie werden weitergeleitet...

); } // Already authenticated - show accept button if (isAuthenticated) { return (

Einladung annehmen

Sie wurden eingeladen, einem Mandanten beizutreten.

{validation.targetUsername && (
Eingeladen: {validation.targetUsername}
)} {validation.mandateName && (
Mandant: {validation.mandateName}
)}
Status: Angemeldet
{validation.roleLabels && validation.roleLabels.length > 0 && (
Zugewiesene Rollen: {validation.roleLabels.join(', ')}
)}
{error && (
{error}
)}
Abbrechen
); } // Not authenticated - show login/register options (NO inline registration form) return (

Einladung annehmen

Sie wurden eingeladen, einem Mandanten beizutreten.

{validation.targetUsername && (
Eingeladen: {validation.targetUsername}
)} {validation.mandateName && (
Mandant: {validation.mandateName}
)} {validation.roleLabels && validation.roleLabels.length > 0 && (
Zugewiesene Rollen: {validation.roleLabels.join(', ')}
)}

{validation.targetUsername ? `Bitte melden Sie sich als "${validation.targetUsername}" an, um die Einladung anzunehmen.` : 'Bitte melden Sie sich an, um die Einladung anzunehmen.'}

{error && (
{error}
)}
oder

Sie können sich mit Ihrem bestehenden Konto anmelden oder ein neues erstellen. Die Einladung wird automatisch nach der Anmeldung akzeptiert.

); }; export default InvitePage;