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>
|
<AuthProvider>
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<WorkflowSelectionProvider>
|
<WorkflowSelectionProvider>
|
||||||
<FileProvider>
|
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* ================================================== */}
|
{/* ================================================== */}
|
||||||
|
|
@ -94,7 +93,9 @@ function App() {
|
||||||
{/* ================================================== */}
|
{/* ================================================== */}
|
||||||
<Route path="/" element={
|
<Route path="/" element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
|
<FileProvider>
|
||||||
<MainLayout />
|
<MainLayout />
|
||||||
|
</FileProvider>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}>
|
}>
|
||||||
{/* Dashboard (Root) */}
|
{/* Dashboard (Root) */}
|
||||||
|
|
@ -190,7 +191,6 @@ function App() {
|
||||||
} />
|
} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</FileProvider>
|
|
||||||
</WorkflowSelectionProvider>
|
</WorkflowSelectionProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ export interface InvitationValidation {
|
||||||
roleIds: string[];
|
roleIds: string[];
|
||||||
roleLabels?: string[];
|
roleLabels?: string[];
|
||||||
targetUsername?: string;
|
targetUsername?: string;
|
||||||
|
email?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||||
import { useInvitations, type InvitationValidation } from '../hooks/useInvitations';
|
import { useInvitations, type InvitationValidation } from '../hooks/useInvitations';
|
||||||
|
import api from '../api';
|
||||||
import { FaCheckCircle, FaTimesCircle, FaSpinner, FaSignInAlt, FaUserPlus } from 'react-icons/fa';
|
import { FaCheckCircle, FaTimesCircle, FaSpinner, FaSignInAlt, FaUserPlus } from 'react-icons/fa';
|
||||||
import styles from './InvitePage.module.css';
|
import styles from './InvitePage.module.css';
|
||||||
|
|
||||||
|
|
@ -35,7 +36,7 @@ export const InvitePage: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { validateInvitation, acceptInvitation } = useInvitations();
|
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');
|
const isAuthenticated = !!sessionStorage.getItem('auth_authority');
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
@ -68,12 +69,11 @@ export const InvitePage: React.FC = () => {
|
||||||
// Check if the target username already has an account
|
// Check if the target username already has an account
|
||||||
if (result.targetUsername) {
|
if (result.targetUsername) {
|
||||||
try {
|
try {
|
||||||
const resp = await fetch(`/api/local/available?username=${encodeURIComponent(result.targetUsername)}`);
|
const resp = await api.get(`/api/local/available`, {
|
||||||
if (resp.ok) {
|
params: { username: result.targetUsername }
|
||||||
const data = await resp.json();
|
});
|
||||||
// available=true means username is free -> user does NOT exist
|
// available=true means username is free -> user does NOT exist
|
||||||
setUserExists(!data.available);
|
setUserExists(!resp.data.available);
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// On error, default to showing both options
|
// On error, default to showing both options
|
||||||
setUserExists(null);
|
setUserExists(null);
|
||||||
|
|
@ -104,6 +104,10 @@ export const InvitePage: React.FC = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}, 2000);
|
}, 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 {
|
} else {
|
||||||
setError(result.error || 'Fehler beim Annehmen der Einladung');
|
setError(result.error || 'Fehler beim Annehmen der Einladung');
|
||||||
}
|
}
|
||||||
|
|
@ -111,20 +115,28 @@ export const InvitePage: React.FC = () => {
|
||||||
setAccepting(false);
|
setAccepting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle redirect to login (stores token first)
|
// Handle redirect to login (stores token first, passes invitation data for pre-fill)
|
||||||
const handleLoginRedirect = () => {
|
const handleLoginRedirect = () => {
|
||||||
if (token) {
|
if (token) {
|
||||||
localStorage.setItem(PENDING_INVITATION_KEY, 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 = () => {
|
const handleRegisterRedirect = () => {
|
||||||
if (token) {
|
if (token) {
|
||||||
localStorage.setItem(PENDING_INVITATION_KEY, 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
|
// Loading state
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ import styles from './Login.module.css';
|
||||||
function Login() {
|
function Login() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
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 [password, setPassword] = useState('');
|
||||||
const [usernameFocused, setUsernameFocused] = useState(false);
|
const [usernameFocused, setUsernameFocused] = useState(false);
|
||||||
const [passwordFocused, setPasswordFocused] = useState(false);
|
const [passwordFocused, setPasswordFocused] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,12 @@ function Register() {
|
||||||
const { register, error: registerError, isLoading } = useRegister();
|
const { register, error: registerError, isLoading } = useRegister();
|
||||||
const { error: msalError } = useMsalRegister();
|
const { error: msalError } = useMsalRegister();
|
||||||
const { checkAvailability, isChecking, error: availabilityError } = useUsernameAvailability();
|
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>({
|
const [formData, setFormData] = useState<RegisterFormData>({
|
||||||
username: '',
|
username: invitationUsername,
|
||||||
email: '',
|
email: invitationEmail,
|
||||||
fullName: ''
|
fullName: ''
|
||||||
});
|
});
|
||||||
const [validationError, setValidationError] = useState<string | null>(null);
|
const [validationError, setValidationError] = useState<string | null>(null);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue