enhanced invitation

This commit is contained in:
ValueOn AG 2026-02-10 10:56:28 +01:00
parent 7077e75fc7
commit 7798463e24
5 changed files with 34 additions and 16 deletions

View file

@ -77,7 +77,6 @@ function App() {
<AuthProvider>
<ToastProvider>
<WorkflowSelectionProvider>
<FileProvider>
<Router>
<Routes>
{/* ================================================== */}
@ -94,7 +93,9 @@ function App() {
{/* ================================================== */}
<Route path="/" element={
<ProtectedRoute>
<FileProvider>
<MainLayout />
</FileProvider>
</ProtectedRoute>
}>
{/* Dashboard (Root) */}
@ -190,7 +191,6 @@ function App() {
} />
</Routes>
</Router>
</FileProvider>
</WorkflowSelectionProvider>
</ToastProvider>
</AuthProvider>

View file

@ -65,6 +65,7 @@ export interface InvitationValidation {
roleIds: string[];
roleLabels?: string[];
targetUsername?: string;
email?: string;
}

View file

@ -24,6 +24,7 @@
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useInvitations, type InvitationValidation } from '../hooks/useInvitations';
import api from '../api';
import { FaCheckCircle, FaTimesCircle, FaSpinner, FaSignInAlt, FaUserPlus } from 'react-icons/fa';
import styles from './InvitePage.module.css';
@ -35,7 +36,7 @@ export const InvitePage: React.FC = () => {
const navigate = useNavigate();
const { validateInvitation, acceptInvitation } = useInvitations();
// Check if user has auth token (simplified check)
// Check if user has an active session
const isAuthenticated = !!sessionStorage.getItem('auth_authority');
// State
@ -68,12 +69,11 @@ export const InvitePage: React.FC = () => {
// Check if the target username already has an account
if (result.targetUsername) {
try {
const resp = await fetch(`/api/local/available?username=${encodeURIComponent(result.targetUsername)}`);
if (resp.ok) {
const data = await resp.json();
// available=true means username is free -> user does NOT exist
setUserExists(!data.available);
}
const resp = await api.get(`/api/local/available`, {
params: { username: result.targetUsername }
});
// available=true means username is free -> user does NOT exist
setUserExists(!resp.data.available);
} catch {
// On error, default to showing both options
setUserExists(null);
@ -104,6 +104,10 @@ export const InvitePage: React.FC = () => {
setTimeout(() => {
navigate('/');
}, 2000);
} else if (result.error?.includes('401') || result.error?.includes('Not authenticated')) {
// Session expired — clear auth and redirect to login
sessionStorage.removeItem('auth_authority');
handleLoginRedirect();
} else {
setError(result.error || 'Fehler beim Annehmen der Einladung');
}
@ -111,20 +115,28 @@ export const InvitePage: React.FC = () => {
setAccepting(false);
};
// Handle redirect to login (stores token first)
// Handle redirect to login (stores token first, passes invitation data for pre-fill)
const handleLoginRedirect = () => {
if (token) {
localStorage.setItem(PENDING_INVITATION_KEY, token);
}
navigate('/login', { state: { from: { pathname: `/invite/${token}` } } });
navigate('/login', { state: {
from: { pathname: `/invite/${token}` },
invitationUsername: validation?.targetUsername || '',
} });
};
// Handle redirect to register (stores token first)
// Handle redirect to register (stores token first, passes invitation data for pre-fill)
const handleRegisterRedirect = () => {
if (token) {
localStorage.setItem(PENDING_INVITATION_KEY, token);
}
navigate('/register', { state: { from: { pathname: `/invite/${token}` }, pendingInvitation: true } });
navigate('/register', { state: {
from: { pathname: `/invite/${token}` },
pendingInvitation: true,
invitationUsername: validation?.targetUsername || '',
invitationEmail: validation?.email || '',
} });
};
// Loading state

View file

@ -12,7 +12,9 @@ import styles from './Login.module.css';
function Login() {
const navigate = useNavigate();
const location = useLocation();
const [username, setUsername] = useState('');
// Pre-fill username from invitation if provided via location.state
const invitationUsername = (location.state as any)?.invitationUsername || '';
const [username, setUsername] = useState(invitationUsername);
const [password, setPassword] = useState('');
const [usernameFocused, setUsernameFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);

View file

@ -19,9 +19,12 @@ function Register() {
const { register, error: registerError, isLoading } = useRegister();
const { error: msalError } = useMsalRegister();
const { checkAvailability, isChecking, error: availabilityError } = useUsernameAvailability();
// Pre-fill from invitation if provided via location.state
const invitationUsername = (location.state as any)?.invitationUsername || '';
const invitationEmail = (location.state as any)?.invitationEmail || '';
const [formData, setFormData] = useState<RegisterFormData>({
username: '',
email: '',
username: invitationUsername,
email: invitationEmail,
fullName: ''
});
const [validationError, setValidationError] = useState<string | null>(null);