enhanced invitation
This commit is contained in:
parent
7077e75fc7
commit
7798463e24
5 changed files with 34 additions and 16 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export interface InvitationValidation {
|
|||
roleIds: string[];
|
||||
roleLabels?: string[];
|
||||
targetUsername?: string;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue