+ style={{ backgroundImage: "url('img/bg_26.png')" }}>
-
- Définir votre mot de passe
-
+
Définir votre mot de passe
{error && (
@@ -80,7 +77,9 @@ export const RegistrationForm = () => {
-
-
);
-}
+};
diff --git a/frontend/src/components/auth/resetPasswordForm.tsx b/frontend/src/components/auth/resetPasswordForm.tsx
index bc4d03a..f70b829 100644
--- a/frontend/src/components/auth/resetPasswordForm.tsx
+++ b/frontend/src/components/auth/resetPasswordForm.tsx
@@ -10,9 +10,9 @@ export const ResetPasswordForm = () => {
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
- const tokenFromUrl = urlParams.get("token");
+ const tokenFromUrl = urlParams.get('token');
if (!tokenFromUrl) {
- alert("Token invalide ou manquant.");
+ alert('Token invalide ou manquant.');
}
setToken(tokenFromUrl);
}, []);
@@ -20,17 +20,17 @@ export const ResetPasswordForm = () => {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!token) {
- alert("Lien invalide ou expiré.");
+ alert('Lien invalide ou expiré.');
return;
}
if (password.length < 6) {
- alert("Le mot de passe doit contenir au moins 6 caractères.");
+ alert('Le mot de passe doit contenir au moins 6 caractères.');
return;
}
if (password !== confirmPassword) {
- alert("Les mots de passe ne correspondent pas.");
+ alert('Les mots de passe ne correspondent pas.');
return;
}
@@ -38,11 +38,11 @@ export const ResetPasswordForm = () => {
try {
const response = await resetPasswordUser(token, password);
if (response) {
- alert("Mot de passe modifié avec succès.");
- window.location.href = "/";
+ alert('Mot de passe modifié avec succès.');
+ window.location.href = '/';
}
} catch {
- alert("Erreur de connexion au serveur.");
+ alert('Erreur de connexion au serveur.');
} finally {
setLoading(false);
}
@@ -51,8 +51,7 @@ export const ResetPasswordForm = () => {
return (
+ style={{ backgroundImage: "url('img/bg_26.png')" }}>
diff --git a/frontend/src/components/home/emergencyModal.tsx b/frontend/src/components/home/emergencyModal.tsx
index 5413d05..fbfc161 100644
--- a/frontend/src/components/home/emergencyModal.tsx
+++ b/frontend/src/components/home/emergencyModal.tsx
@@ -1,8 +1,9 @@
import { useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
+import { useOnboarding } from '../../contexts/onboarding';
import { decodeToken, getToken } from '../../services/requests/auth.service';
-import { createUserContactInformation, getCurrentUserOnboardingStatus } from '../../services/requests/user.service';
+import { createUserContactInformation } from '../../services/requests/user.service';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import Modal from '../ui/modal';
@@ -72,6 +73,8 @@ function EmergencyModal() {
const isLogin = searchParams.get('login') === 'true';
const isNew = roles.includes('Nouveau');
+ const { status: onboardingStatus, loading: onboardingLoading } = useOnboarding();
+
useEffect(() => {
if (!isLogin) {
setFlowStep('idle');
@@ -80,48 +83,27 @@ function EmergencyModal() {
setLanguage('fr');
return;
}
-
- let cancelled = false;
-
- const loadOnboardingStatus = async () => {
+ if (!isNew) return;
+ if (onboardingLoading) {
setFlowStep('loading');
- setError(null);
-
- try {
- const status = await getCurrentUserOnboardingStatus();
-
- if (cancelled) {
- return;
- }
-
- if (!status.hasemergencyContactInformation) {
- setFlowStep('emergency');
- return;
- }
-
- if (status.needsVssForm) {
- setFlowStep('vss');
- return;
- }
-
- setFlowStep('idle');
- setSearchParams({});
- } catch {
- if (!cancelled) {
- setFlowStep('emergency');
- setError(copy[language].statusError);
- }
- }
- };
-
- if (isNew) {
- loadOnboardingStatus();
+ return;
}
-
- return () => {
- cancelled = true;
- };
- }, [isLogin, isNew, setSearchParams]);
+ if (!onboardingStatus) {
+ setFlowStep('emergency');
+ setError(copy[language].statusError);
+ return;
+ }
+ if (!onboardingStatus.hasemergencyContactInformation) {
+ setFlowStep('emergency');
+ return;
+ }
+ if (onboardingStatus.needsVssForm) {
+ setFlowStep('vss');
+ return;
+ }
+ setFlowStep('idle');
+ setSearchParams({});
+ }, [isLogin, isNew, onboardingLoading, onboardingStatus, language, setSearchParams]);
const closeFlow = () => {
setSearchParams({});
diff --git a/frontend/src/components/home/teamSection.tsx b/frontend/src/components/home/teamSection.tsx
index 031e9db..d825d35 100644
--- a/frontend/src/components/home/teamSection.tsx
+++ b/frontend/src/components/home/teamSection.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
+import { useUser } from '../../contexts/user';
import { type TeamDisplayInfos } from '../../interfaces/team.interface';
import { getUserTeam } from '../../services/requests/team.service';
import { Button } from '../ui/button';
@@ -9,16 +10,38 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
export const Team = () => {
const [teamInfos, setTeamInfos] = useState
(null);
const [loading, setLoading] = useState(true);
+ const { user, loading: userLoading } = useUser();
useEffect(() => {
- fetchTeam();
- }, []);
+ const fetchTeam = async () => {
+ const team = await getUserTeam();
+ setTeamInfos(team);
+ setLoading(false);
+ };
- const fetchTeam = async () => {
- const team = await getUserTeam();
- setTeamInfos(team);
- setLoading(false);
- };
+ if (user?.permission === 'Nouveau') {
+ void fetchTeam();
+ } else {
+ // if not a new user, we don't need to load team
+ setLoading(false);
+ }
+ }, [user]);
+
+ if (userLoading) {
+ return (
+
+
+
+ Chargement de ton équipe...
+
+
+
+ );
+ }
+
+ if (user?.permission !== 'Nouveau') {
+ return null;
+ }
if (loading) {
return (
diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx
index b109bd6..9d5e8cf 100644
--- a/frontend/src/components/navbar.tsx
+++ b/frontend/src/components/navbar.tsx
@@ -4,8 +4,8 @@ import { AnimatePresence, motion } from 'framer-motion';
import { Fragment, useEffect, useState } from 'react';
import { NavLink, useLocation, useSearchParams } from 'react-router-dom';
+import { useOnboarding } from '../contexts/onboarding';
import { decodeToken, getToken } from '../services/requests/auth.service';
-import { getCurrentUserOnboardingStatus } from '../services/requests/user.service';
import { Button } from './ui/button';
interface NavItem {
@@ -31,6 +31,7 @@ export const Navbar = () => {
const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[];
const [hasContactInformation, setHasContactInformation] = useState(false);
const [needsVssForm, setNeedsVssForm] = useState(false);
+ const { status: onboardingStatus, loading: onboardingLoading } = useOnboarding();
const [, setSearchParams] = useSearchParams();
const handleLogout = () => {
@@ -41,31 +42,16 @@ export const Navbar = () => {
useEffect(() => {
setMenuOpen(false);
- const fetchOnboardingStatus = async () => {
- try {
- const onboardingStatus = await getCurrentUserOnboardingStatus();
- setHasContactInformation(onboardingStatus.hasemergencyContactInformation);
- setNeedsVssForm(onboardingStatus.needsVssForm);
- } catch (error) {
- console.error("Erreur lors de la récupération du statut d'onboarding :", error);
- }
- };
+ if (!isAuthenticated) return;
- if (isAuthenticated) {
- fetchOnboardingStatus();
+ if (!onboardingLoading && onboardingStatus) {
+ setHasContactInformation(onboardingStatus.hasemergencyContactInformation);
+ setNeedsVssForm(onboardingStatus.needsVssForm);
}
- const handleOnboardingUpdate = () => {
- if (isAuthenticated) {
- fetchOnboardingStatus();
- }
- };
-
- window.addEventListener('user-onboarding-updated', handleOnboardingUpdate);
-
- return () => {
- window.removeEventListener('user-onboarding-updated', handleOnboardingUpdate);
- };
+ // OnboardingProvider refreshes on 'user-onboarding-updated' event globally.
+ // No local event listeners required here.
+ return undefined;
}, [isAuthenticated, pathname]);
const navItems: NavItem[] = [
diff --git a/frontend/src/components/profil/profilForm.tsx b/frontend/src/components/profil/profilForm.tsx
index 6eba1b0..1209b90 100644
--- a/frontend/src/components/profil/profilForm.tsx
+++ b/frontend/src/components/profil/profilForm.tsx
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react';
// import { FaDiscord } from 'react-icons/fa';
import Select from 'react-select';
-import { type User } from '../../interfaces/user.interface';
-import { getCurrentUser, updateCurrentUser } from '../../services/requests/user.service';
+import { useUser } from '../../contexts/user';
+import { updateCurrentUser } from '../../services/requests/user.service';
import { Button } from '../ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Input } from '../ui/input';
@@ -26,32 +26,31 @@ const branchOptions = [
];
export const ProfilForm = () => {
- const [user, setUser] = useState(null);
+ const { user, loading: userLoading, refreshUser } = useUser();
const [branch, setBranch] = useState('');
const [contact, setContact] = useState('');
const [loading, setLoading] = useState(false);
useEffect(() => {
- const fetchUser = async () => {
- try {
- const me = await getCurrentUser();
- setUser(me);
- setBranch(me.branch || '');
- setContact(me.contact || '');
- } catch (err) {
- console.error("Erreur lors de la récupération de l'utilisateur", err);
- }
- };
- fetchUser();
- }, []);
+ if (user) {
+ setBranch(user.branch || '');
+ setContact(user.contact || '');
+ }
+ }, [user]);
const handleSubmit = async () => {
setLoading(true);
const response = await updateCurrentUser({ branch: branch, contact: contact });
- alert(response.message);
+ // refresh cached user data after update
+ try {
+ await refreshUser();
+ } catch {
+ alert(response.message);
+ }
setLoading(false);
};
+ if (userLoading || loading) return null;
if (!user) return null;
return (
diff --git a/frontend/src/components/tent/tentSection.tsx b/frontend/src/components/tent/tentSection.tsx
index ba9e27f..1e1d576 100644
--- a/frontend/src/components/tent/tentSection.tsx
+++ b/frontend/src/components/tent/tentSection.tsx
@@ -2,12 +2,13 @@ import { useEffect, useState } from 'react';
import Select from 'react-select';
import Swal from 'sweetalert2';
+import { useOnboarding } from '../../contexts/onboarding';
import { type Tent } from '../../interfaces/tent.interface';
import { type User } from '../../interfaces/user.interface';
import { decodeToken, getToken } from '../../services/requests/auth.service';
import { checkWEIStatus } from '../../services/requests/event.service';
import { cancelTent, createTent, getUserTent } from '../../services/requests/tent.service';
-import { getCurrentUserOnboardingStatus, getUsers } from '../../services/requests/user.service';
+import { getUsers } from '../../services/requests/user.service';
import { Button } from '../ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
@@ -23,6 +24,8 @@ export const TentPublic = () => {
: { userPermission: undefined, userRoles: [] };
const roles = [userPermission, ...userRoles.map((r) => r.roleName)].filter(Boolean) as string[];
+ const { status: onboardingStatus, loading: onboardingLoading } = useOnboarding();
+
useEffect(() => {
const fetchUsers = async () => {
try {
@@ -56,19 +59,13 @@ export const TentPublic = () => {
fetchUsers();
fetchTent();
fetchWEIStatus();
- if (roles.includes('Nouveau')) {
- fetchOnboardingStatus();
- }
}, [roles]);
- const fetchOnboardingStatus = async () => {
- try {
- const onboardingStatus = await getCurrentUserOnboardingStatus();
- setCanBook(onboardingStatus.vss_form == 'validated');
- } catch (error) {
- console.error("Erreur lors de la récupération du statut d'onboarding :", error);
- }
- };
+ useEffect(() => {
+ if (!roles.includes('Nouveau')) return;
+ if (onboardingLoading) return;
+ setCanBook(onboardingStatus ? onboardingStatus.vss_form == 'validated' : false);
+ }, [roles, onboardingLoading, onboardingStatus]);
if (!token) return null;
const { userId } = decodeToken(token);
diff --git a/frontend/src/components/utils/privateroute.tsx b/frontend/src/components/utils/privateroute.tsx
index f09a752..a4945bb 100644
--- a/frontend/src/components/utils/privateroute.tsx
+++ b/frontend/src/components/utils/privateroute.tsx
@@ -10,11 +10,7 @@ interface PrivateRouteProps {
children: React.ReactNode;
}
-const PrivateRoute: React.FC = ({
- permissionRequired,
- roleRequired,
- children,
-}) => {
+const PrivateRoute: React.FC = ({ permissionRequired, roleRequired, children }) => {
const token = getToken();
if (!token) {
@@ -28,20 +24,20 @@ const PrivateRoute: React.FC = ({
return ;
}
- const isAdmin = decoded.userPermission === "Admin";
+ const isAdmin = decoded.userPermission === 'Admin';
- const hasPermission =
- !permissionRequired || decoded.userPermission === permissionRequired;
+ const hasPermission = !permissionRequired || decoded.userPermission === permissionRequired;
- const hasRole =
- !roleRequired ||
- decoded.userRoles?.some((role) => role.roleName === roleRequired);
+ const hasRole = !roleRequired || decoded.userRoles?.some((role) => role.roleName === roleRequired);
if (!isAdmin && !(hasPermission || hasRole)) {
return ;
}
+ // PrivateRoute only handles authz checks; user data is provided by `UserProvider` at app root.
return <>{children}>;
};
export default PrivateRoute;
+
+// UserProvider and useUser moved to src/context/user.tsx
diff --git a/frontend/src/contexts/onboarding.tsx b/frontend/src/contexts/onboarding.tsx
new file mode 100644
index 0000000..5552f5a
--- /dev/null
+++ b/frontend/src/contexts/onboarding.tsx
@@ -0,0 +1,76 @@
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+import type { UserOnboardingStatus } from '../interfaces/user.interface';
+import { getToken } from '../services/requests/auth.service';
+import { getCurrentUserOnboardingStatus } from '../services/requests/user.service';
+
+type OnboardingContextType = {
+ status: UserOnboardingStatus | null;
+ loading: boolean;
+ refreshOnboarding: () => Promise;
+};
+
+const OnboardingContext = createContext(undefined);
+
+let cachedOnboarding: UserOnboardingStatus | null = null;
+
+export const useOnboarding = (): OnboardingContextType => {
+ const ctx = useContext(OnboardingContext);
+ if (!ctx) throw new Error('useOnboarding must be used within an OnboardingProvider');
+ return ctx;
+};
+
+export const OnboardingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const tokenPresent = Boolean(getToken());
+ const [status, setStatus] = useState(cachedOnboarding);
+ const [loading, setLoading] = useState(() => cachedOnboarding === null && tokenPresent);
+
+ const fetchStatus = async () => {
+ const token = getToken();
+ if (!token) {
+ setStatus(null);
+ setLoading(false);
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const s = await getCurrentUserOnboardingStatus();
+ setStatus(s ?? null);
+ cachedOnboarding = s ?? null;
+ } catch {
+ setStatus(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (!tokenPresent && !cachedOnboarding) {
+ // nothing to do when not authenticated
+ return;
+ }
+
+ if (!cachedOnboarding) void fetchStatus();
+
+ const handler = () => {
+ void fetchStatus();
+ };
+
+ window.addEventListener('user-onboarding-updated', handler);
+ return () => window.removeEventListener('user-onboarding-updated', handler);
+ }, []);
+
+ const refreshOnboarding = async () => {
+ cachedOnboarding = null;
+ await fetchStatus();
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default OnboardingProvider;
diff --git a/frontend/src/contexts/user.tsx b/frontend/src/contexts/user.tsx
new file mode 100644
index 0000000..d9d7453
--- /dev/null
+++ b/frontend/src/contexts/user.tsx
@@ -0,0 +1,66 @@
+import React, { createContext, useContext, useEffect, useState } from 'react';
+
+import type { User } from '../interfaces/user.interface';
+import { getToken } from '../services/requests/auth.service';
+import { getCurrentUser } from '../services/requests/user.service';
+
+type UserContextType = {
+ user: User | null;
+ loading: boolean;
+ refreshUser: () => Promise;
+};
+
+const UserContext = createContext(undefined);
+
+let cachedUser: User | null = null;
+
+export const useUser = (): UserContextType => {
+ const ctx = useContext(UserContext);
+ if (!ctx) {
+ throw new Error('useUser must be used within a UserProvider');
+ }
+ return ctx;
+};
+
+export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const tokenPresent = Boolean(getToken());
+ const [user, setUser] = useState(cachedUser);
+ const [loading, setLoading] = useState(() => cachedUser === null && tokenPresent);
+
+ const fetchCurrentUser = async () => {
+ const token = getToken();
+ if (!token) {
+ setUser(null);
+ setLoading(false);
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const current = await getCurrentUser();
+ setUser(current ?? null);
+ cachedUser = current ?? null;
+ } catch {
+ setUser(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (!tokenPresent && !cachedUser) return;
+ if (!cachedUser) {
+ void fetchCurrentUser();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const refreshUser = async () => {
+ cachedUser = null;
+ await fetchCurrentUser();
+ };
+
+ return {children};
+};
+
+export default UserProvider;
diff --git a/frontend/src/pages/admin.tsx b/frontend/src/pages/admin.tsx
deleted file mode 100644
index 433ab0f..0000000
--- a/frontend/src/pages/admin.tsx
+++ /dev/null
@@ -1,367 +0,0 @@
-//--------------Challenge Import--------------//
-import { useEffect, useRef, useState } from 'react';
-
-import { AdminBannedAddEmail, AdminBannedList } from '../components/Admin/adminBanned';
-import { AdminBusTools } from '../components/Admin/adminBus';
-import AdminChallengeList from '../components/Admin/AdminChallenge/adminChalengeList';
-import { AdminChallengeAddPointsForm } from '../components/Admin/AdminChallenge/adminChallengeAddPointsForm';
-import ChallengeEditor from '../components/Admin/AdminChallenge/adminChallengeEditor';
-import { AdminValidatedChallengesList } from '../components/Admin/AdminChallenge/adminChallengeValidatedList';
-import { AdminEmail } from '../components/Admin/adminEmail';
-import { AdminEvents } from '../components/Admin/adminEvent';
-import {
- AdminExportConnect,
- AdminImportFoodMenu,
- AdminImportNotebooks,
- AdminImportPlannings,
-} from '../components/Admin/adminExportImport';
-import { AdminFactionManagement } from '../components/Admin/adminFaction';
-import { AdminRolePointsManager } from '../components/Admin/adminGames';
-import { AdminLayout } from '../components/Admin/adminLayout';
-import { AdminNews } from '../components/Admin/adminNews';
-//--------------Perm Import--------------//
-import PermanenceActions from '../components/Admin/AdminPerm/adminPermAction';
-import PermanenceForm from '../components/Admin/AdminPerm/adminPermForm';
-import { ImportPermCSV } from '../components/Admin/AdminPerm/adminPermImport';
-import PermanenceList from '../components/Admin/AdminPerm/adminPermList';
-import { AdminRoleManagement, AdminRolePreferences } from '../components/Admin/adminRole';
-import { AdminShotgunRanking } from '../components/Admin/adminShotgun';
-import { AdminTeamManagement, DistributeTeam } from '../components/Admin/adminTeam';
-import { TentAdmin } from '../components/Admin/adminTent';
-import { AdminRegisterNewStudent, AdminSyncNewStudent, AdminUser } from '../components/Admin/adminUser';
-import { RevealSection } from '../components/ui/revealSection';
-import { type Banned } from '../interfaces/banned.interface';
-import { type Challenge, type ValidatedChallenge } from '../interfaces/challenge.interface';
-import { type Faction } from '../interfaces/faction.interface';
-import { type Permanence } from '../interfaces/permanence.interface';
-import { type Team } from '../interfaces/team.interface';
-import { type User } from '../interfaces/user.interface';
-import { getAllChallenges, getAllChallengesValidates } from '../services/requests/challenge.service';
-import { getAllFactionsUser } from '../services/requests/faction.service';
-import { getAllPermanences } from '../services/requests/permanence.service';
-import { getAllTeams } from '../services/requests/team.service';
-import { getUsers, getUsersAdmin } from '../services/requests/user.service';
-
-export const AdminPageTeam: React.FC = () => (
-
-
-
-);
-
-export const AdminPageFaction: React.FC = () => (
-
-
-
-);
-
-export const AdminPageRole: React.FC = () => (
-
-
-
-);
-
-export const AdminPageEvents: React.FC = () => (
-
-
-
-);
-
-export const AdminPageShotgun: React.FC = () => (
-
-
-
-);
-
-export const AdminPageExport: React.FC = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-);
-
-export const AdminPagePerm: React.FC = () => {
- const [permanences, setPermanences] = useState([]);
- const [users, setUsers] = useState([]);
- const [editMode, setEditMode] = useState(false);
- const [editPermanence, setEditPermanence] = useState(null);
-
- const editorRef = useRef(null);
-
- useEffect(() => {
- void fetchPermanences();
- void fetchUsers();
- }, []);
-
- const fetchPermanences = async () => {
- const res = await getAllPermanences();
- setPermanences(res.data as Permanence[]);
- };
-
- const fetchUsers = async () => {
- const res = await getUsersAdmin();
- setUsers(res as User[]);
- };
-
- return (
-
-
- {/* Formulaire (créer/éditer) */}
-
- {
- setEditMode(false);
- setEditPermanence(null);
- }}
- />
-
-
- {/* Liste des permanences */}
-
- {
- setEditMode(true);
- setEditPermanence(perm);
- setTimeout(() => {
- editorRef.current?.scrollIntoView({ behavior: 'smooth' });
- }, 100);
- }}
- />
-
-
- {/* Actions globales */}
-
-
-
-
- {/* Import CSV (si dispo) */}
-
-
-
-
-
- );
-};
-
-export const AdminPageChall: React.FC = () => {
- const [challenges, setChallenges] = useState([]);
- const [validatedChallenges, setValidatedChallenges] = useState([]);
- const [users, setUsers] = useState([]);
- const [teams, setTeams] = useState([]);
- const [factions, setFactions] = useState([]);
- const [editingChallenge, setEditingChallenge] = useState(null);
- const editorRef = useRef(null);
-
- const fetchChallengesUsersTeamsFactions = async () => {
- try {
- const challsRes = await getAllChallenges();
- const usersRes = await getUsers();
- const teamsRes = await getAllTeams();
- const factionsRes = await getAllFactionsUser();
-
- const challsResFiltered = challsRes.filter((c: Challenge) => c.category != 'Free');
- setChallenges(challsResFiltered);
- setUsers(usersRes);
- setTeams(teamsRes);
- setFactions(factionsRes);
- } catch (err) {
- console.error('Erreur chargement challenges', err);
- }
- };
-
- const fetchValidatedChallenges = async () => {
- try {
- const res = await getAllChallengesValidates();
- setValidatedChallenges(res);
- } catch (err) {
- console.error('Erreur chargement challenges validés', err);
- }
- };
-
- useEffect(() => {
- fetchChallengesUsersTeamsFactions();
- fetchValidatedChallenges();
- }, []);
-
- const handleEdit = (challenge: Challenge) => {
- setEditingChallenge(challenge);
- editorRef.current?.scrollIntoView({ behavior: 'smooth' });
- };
-
- return (
-
-
-
- {/* Formulaire création / édition */}
-
-
-
-
- {/* Liste des challenges */}
- {
- fetchChallengesUsersTeamsFactions();
- fetchValidatedChallenges();
- }}
- onEdit={handleEdit}
- users={users}
- teams={teams}
- factions={factions}
- />
-
-
-
- {/* Ajout de points */}
-
-
-
-
- {/* Liste des challenges validés */}
-
-
-
-
- );
-};
-
-export const AdminPageEmail: React.FC = () => (
-
-
-
-);
-
-export const AdminPageUser: React.FC = () => (
-
-
-
-);
-
-export const AdminPageNews: React.FC = () => (
-
-
-
-);
-
-export const AdminPageTent: React.FC = () => (
-
-
-
-
-
-
-
-);
-
-export const AdminPageBus: React.FC = () => (
-
-
-
-);
-
-export const AdminPageGames: React.FC = () => (
-
-
-
-);
-
-export const AdminBanned: React.FC = () => {
- const [bannedList, setBannedList] = useState([]);
-
- return (
-
-
-
- );
-};
diff --git a/frontend/src/pages/admin/adminBanned.tsx b/frontend/src/pages/admin/adminBanned.tsx
new file mode 100644
index 0000000..7385a62
--- /dev/null
+++ b/frontend/src/pages/admin/adminBanned.tsx
@@ -0,0 +1,26 @@
+import { useState } from 'react';
+
+import { AdminBannedAddEmail, AdminBannedList } from '../../components/Admin/adminBanned';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+import { type Banned } from '../../interfaces/banned.interface';
+
+const AdminPageBanned: React.FC = () => {
+ const [bannedList, setBannedList] = useState([]);
+
+ return (
+
+
+
+ );
+};
+
+export default AdminPageBanned;
diff --git a/frontend/src/pages/admin/adminBus.tsx b/frontend/src/pages/admin/adminBus.tsx
new file mode 100644
index 0000000..1981724
--- /dev/null
+++ b/frontend/src/pages/admin/adminBus.tsx
@@ -0,0 +1,15 @@
+import { AdminBusTools } from '../../components/Admin/adminBus';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageBus: React.FC = () => (
+
+
+
+);
+
+export default AdminPageBus;
diff --git a/frontend/src/pages/admin/adminChallenges.tsx b/frontend/src/pages/admin/adminChallenges.tsx
new file mode 100644
index 0000000..53dbfc7
--- /dev/null
+++ b/frontend/src/pages/admin/adminChallenges.tsx
@@ -0,0 +1,107 @@
+import { useEffect, useRef, useState } from 'react';
+
+import AdminChallengeList from '../../components/Admin/AdminChallenge/adminChalengeList';
+import { AdminChallengeAddPointsForm } from '../../components/Admin/AdminChallenge/adminChallengeAddPointsForm';
+import ChallengeEditor from '../../components/Admin/AdminChallenge/adminChallengeEditor';
+import { AdminValidatedChallengesList } from '../../components/Admin/AdminChallenge/adminChallengeValidatedList';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+import { type Challenge, type ValidatedChallenge } from '../../interfaces/challenge.interface';
+import { type Faction } from '../../interfaces/faction.interface';
+import { type Team } from '../../interfaces/team.interface';
+import { type User } from '../../interfaces/user.interface';
+import { getAllChallenges, getAllChallengesValidates } from '../../services/requests/challenge.service';
+import { getAllFactionsUser } from '../../services/requests/faction.service';
+import { getAllTeams } from '../../services/requests/team.service';
+import { getUsers } from '../../services/requests/user.service';
+
+const AdminPageChallenges: React.FC = () => {
+ const [challenges, setChallenges] = useState([]);
+ const [validatedChallenges, setValidatedChallenges] = useState([]);
+ const [users, setUsers] = useState([]);
+ const [teams, setTeams] = useState([]);
+ const [factions, setFactions] = useState([]);
+ const [editingChallenge, setEditingChallenge] = useState(null);
+ const editorRef = useRef(null);
+
+ const fetchChallengesUsersTeamsFactions = async () => {
+ try {
+ const challsRes = await getAllChallenges();
+ const usersRes = await getUsers();
+ const teamsRes = await getAllTeams();
+ const factionsRes = await getAllFactionsUser();
+
+ const challsResFiltered = challsRes.filter((c: Challenge) => c.category != 'Free');
+ setChallenges(challsResFiltered);
+ setUsers(usersRes);
+ setTeams(teamsRes);
+ setFactions(factionsRes);
+ } catch (err) {
+ console.error('Erreur chargement challenges', err);
+ }
+ };
+
+ const fetchValidatedChallenges = async () => {
+ try {
+ const res = await getAllChallengesValidates();
+ setValidatedChallenges(res);
+ } catch (err) {
+ console.error('Erreur chargement challenges validés', err);
+ }
+ };
+
+ useEffect(() => {
+ fetchChallengesUsersTeamsFactions();
+ fetchValidatedChallenges();
+ }, []);
+
+ const handleEdit = (challenge: Challenge) => {
+ setEditingChallenge(challenge);
+ editorRef.current?.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ return (
+
+
+
+ {/* Formulaire création / édition */}
+
+
+
+
+ {/* Liste des challenges */}
+ {
+ fetchChallengesUsersTeamsFactions();
+ fetchValidatedChallenges();
+ }}
+ onEdit={handleEdit}
+ users={users}
+ teams={teams}
+ factions={factions}
+ />
+
+
+
+ {/* Ajout de points */}
+
+
+
+
+ {/* Liste des challenges validés */}
+
+
+
+
+ );
+};
+
+export default AdminPageChallenges;
diff --git a/frontend/src/pages/admin/adminEmail.tsx b/frontend/src/pages/admin/adminEmail.tsx
new file mode 100644
index 0000000..e30db64
--- /dev/null
+++ b/frontend/src/pages/admin/adminEmail.tsx
@@ -0,0 +1,15 @@
+import { AdminEmail } from '../../components/Admin/adminEmail';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageEmail: React.FC = () => (
+
+
+
+);
+
+export default AdminPageEmail;
diff --git a/frontend/src/pages/admin/adminEvents.tsx b/frontend/src/pages/admin/adminEvents.tsx
new file mode 100644
index 0000000..27b200f
--- /dev/null
+++ b/frontend/src/pages/admin/adminEvents.tsx
@@ -0,0 +1,15 @@
+import { AdminEvents } from '../../components/Admin/adminEvent';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageEvents: React.FC = () => (
+
+
+
+);
+
+export default AdminPageEvents;
diff --git a/frontend/src/pages/admin/adminExport.tsx b/frontend/src/pages/admin/adminExport.tsx
new file mode 100644
index 0000000..a44d62c
--- /dev/null
+++ b/frontend/src/pages/admin/adminExport.tsx
@@ -0,0 +1,32 @@
+import {
+ AdminExportConnect,
+ AdminImportFoodMenu,
+ AdminImportNotebooks,
+ AdminImportPlannings,
+} from '../../components/Admin/adminExportImport';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageExport: React.FC = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default AdminPageExport;
diff --git a/frontend/src/pages/admin/adminFaction.tsx b/frontend/src/pages/admin/adminFaction.tsx
new file mode 100644
index 0000000..4c707b6
--- /dev/null
+++ b/frontend/src/pages/admin/adminFaction.tsx
@@ -0,0 +1,15 @@
+import { AdminFactionManagement } from '../../components/Admin/adminFaction';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageFaction: React.FC = () => (
+
+
+
+);
+
+export default AdminPageFaction;
diff --git a/frontend/src/pages/admin/adminGames.tsx b/frontend/src/pages/admin/adminGames.tsx
new file mode 100644
index 0000000..bb5a67e
--- /dev/null
+++ b/frontend/src/pages/admin/adminGames.tsx
@@ -0,0 +1,15 @@
+import { AdminRolePointsManager } from '../../components/Admin/adminGames';
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageGames: React.FC = () => (
+
+
+
+);
+
+export default AdminPageGames;
diff --git a/frontend/src/pages/admin/adminNews.tsx b/frontend/src/pages/admin/adminNews.tsx
new file mode 100644
index 0000000..2d38311
--- /dev/null
+++ b/frontend/src/pages/admin/adminNews.tsx
@@ -0,0 +1,15 @@
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { AdminNews } from '../../components/Admin/adminNews';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageNews: React.FC = () => (
+
+
+
+);
+
+export default AdminPageNews;
diff --git a/frontend/src/pages/admin/adminPerm.tsx b/frontend/src/pages/admin/adminPerm.tsx
new file mode 100644
index 0000000..d36c929
--- /dev/null
+++ b/frontend/src/pages/admin/adminPerm.tsx
@@ -0,0 +1,83 @@
+import { useEffect, useRef, useState } from 'react';
+
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import PermanenceActions from '../../components/Admin/AdminPerm/adminPermAction';
+import PermanenceForm from '../../components/Admin/AdminPerm/adminPermForm';
+import { ImportPermCSV } from '../../components/Admin/AdminPerm/adminPermImport';
+import PermanenceList from '../../components/Admin/AdminPerm/adminPermList';
+import { RevealSection } from '../../components/ui/revealSection';
+import { type Permanence } from '../../interfaces/permanence.interface';
+import { type User } from '../../interfaces/user.interface';
+import { getAllPermanences } from '../../services/requests/permanence.service';
+import { getUsersAdmin } from '../../services/requests/user.service';
+
+const AdminPagePerm: React.FC = () => {
+ const [permanences, setPermanences] = useState([]);
+ const [users, setUsers] = useState([]);
+ const [editMode, setEditMode] = useState(false);
+ const [editPermanence, setEditPermanence] = useState(null);
+
+ const editorRef = useRef(null);
+
+ useEffect(() => {
+ void fetchPermanences();
+ void fetchUsers();
+ }, []);
+
+ const fetchPermanences = async () => {
+ const res = await getAllPermanences();
+ setPermanences(res.data as Permanence[]);
+ };
+
+ const fetchUsers = async () => {
+ const res = await getUsersAdmin();
+ setUsers(res as User[]);
+ };
+
+ return (
+
+
+ {/* Formulaire (créer/éditer) */}
+
+ {
+ setEditMode(false);
+ setEditPermanence(null);
+ }}
+ />
+
+
+ {/* Liste des permanences */}
+
+ {
+ setEditMode(true);
+ setEditPermanence(perm);
+ setTimeout(() => {
+ editorRef.current?.scrollIntoView({ behavior: 'smooth' });
+ }, 100);
+ }}
+ />
+
+
+ {/* Actions globales */}
+
+
+
+
+ {/* Import CSV (si dispo) */}
+
+
+
+
+
+ );
+};
+
+export default AdminPagePerm;
diff --git a/frontend/src/pages/admin/adminRole.tsx b/frontend/src/pages/admin/adminRole.tsx
new file mode 100644
index 0000000..d5ee50b
--- /dev/null
+++ b/frontend/src/pages/admin/adminRole.tsx
@@ -0,0 +1,19 @@
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { AdminRoleManagement, AdminRolePreferences } from '../../components/Admin/adminRole';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageRole: React.FC = () => (
+
+
+
+);
+
+export default AdminPageRole;
diff --git a/frontend/src/pages/admin/adminShotgun.tsx b/frontend/src/pages/admin/adminShotgun.tsx
new file mode 100644
index 0000000..b7ada63
--- /dev/null
+++ b/frontend/src/pages/admin/adminShotgun.tsx
@@ -0,0 +1,14 @@
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { AdminShotgunRanking } from '../../components/Admin/adminShotgun';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageShotgun: React.FC = () => (
+
+
+
+);
+export default AdminPageShotgun;
diff --git a/frontend/src/pages/admin/adminTeam.tsx b/frontend/src/pages/admin/adminTeam.tsx
new file mode 100644
index 0000000..cf3b6de
--- /dev/null
+++ b/frontend/src/pages/admin/adminTeam.tsx
@@ -0,0 +1,19 @@
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { AdminTeamManagement, DistributeTeam } from '../../components/Admin/adminTeam';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageTeam: React.FC = () => (
+
+
+
+);
+
+export default AdminPageTeam;
diff --git a/frontend/src/pages/admin/adminTent.tsx b/frontend/src/pages/admin/adminTent.tsx
new file mode 100644
index 0000000..090461b
--- /dev/null
+++ b/frontend/src/pages/admin/adminTent.tsx
@@ -0,0 +1,15 @@
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { TentAdmin } from '../../components/Admin/adminTent';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageTent: React.FC = () => (
+
+
+
+
+
+
+
+);
+
+export default AdminPageTent;
diff --git a/frontend/src/pages/admin/adminUser.tsx b/frontend/src/pages/admin/adminUser.tsx
new file mode 100644
index 0000000..3057e21
--- /dev/null
+++ b/frontend/src/pages/admin/adminUser.tsx
@@ -0,0 +1,23 @@
+import { AdminLayout } from '../../components/Admin/adminLayout';
+import { AdminRegisterNewStudent, AdminSyncNewStudent, AdminUser } from '../../components/Admin/adminUser';
+import { RevealSection } from '../../components/ui/revealSection';
+
+const AdminPageUser: React.FC = () => (
+
+
+
+);
+
+export default AdminPageUser;
diff --git a/frontend/src/pages/notFound.tsx b/frontend/src/pages/notFound.tsx
index 072a11b..d5f7462 100644
--- a/frontend/src/pages/notFound.tsx
+++ b/frontend/src/pages/notFound.tsx
@@ -17,7 +17,7 @@ const NotFoundPage = () => {
+ style={{ backgroundImage: "url('img/bg_26.png')" }}>
diff --git a/frontend/src/pages/perm.tsx b/frontend/src/pages/perm.tsx
deleted file mode 100644
index c93f633..0000000
--- a/frontend/src/pages/perm.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import { useEffect, useState } from 'react';
-import { Navigate } from 'react-router-dom';
-import Swal from 'sweetalert2';
-
-import { Footer } from '../components/footer';
-import EmergencyModal from '../components/home/emergencyModal';
-import { Navbar } from '../components/navbar';
-import { RespoPresenceManagement } from '../components/permanence/appealPerm';
-import { AvailablePermanencesList } from '../components/permanence/permList';
-import { MyPermanencesList } from '../components/permanence/permUser';
-import { type Permanence } from '../interfaces/permanence.interface';
-import { type DecodedToken } from '../interfaces/token.interfaces';
-import { decodeToken, getToken } from '../services/requests/auth.service';
-import {
- applyToPermanence,
- cancelPermanence,
- getMyPermanences,
- getOpenPermanences,
- isUserRespo,
-} from '../services/requests/permanence.service';
-
-export const AvailablePermanencesPage: React.FC = () => {
- const [permanences, setPermanences] = useState([]);
- const [myPermanences, setMyPermanences] = useState([]);
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- useEffect(() => {
- fetchData();
- }, []);
-
- const fetchData = async () => {
- try {
- const [openPerms, myPerms] = await Promise.all([getOpenPermanences(), getMyPermanences()]);
- setPermanences(openPerms);
- setMyPermanences(myPerms);
- } catch (err) {
- console.error('Erreur lors du chargement des permanences', err);
- Swal.fire('Erreur', 'Impossible de charger les permanences.', 'error');
- }
- };
-
- const handleApplyToPermanence = async (permId: number) => {
- if (isSubmitting) return;
- setIsSubmitting(true);
-
- try {
- const response = await applyToPermanence(permId);
- await Swal.fire('Succès ✅', response.message, 'success');
- fetchData();
- } catch (err) {
- console.error("Erreur lors de l'inscription", err);
- Swal.fire('Erreur', "Impossible de s'inscrire à la permanence.", 'error');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
-
- );
-};
-
-export const MyPermanencesPage: React.FC = () => {
- const [myPermanences, setMyPermanences] = useState([]);
-
- useEffect(() => {
- fetchMyPermanences();
- }, []);
-
- const fetchMyPermanences = async () => {
- try {
- const perms = await getMyPermanences();
- setMyPermanences(perms);
- } catch (err) {
- console.error('Erreur lors du chargement', err);
- Swal.fire('Erreur', 'Impossible de charger vos permanences.', 'error');
- }
- };
-
- const handleCancelPermanence = async (permId: number) => {
- const selectedPermanence = myPermanences.find((perm) => perm.id === permId);
- if (!selectedPermanence) {
- Swal.fire('Erreur', 'Permanence non trouvée.', 'error');
- return;
- }
-
- const now = new Date();
- const deadline = new Date(selectedPermanence.start_at);
- deadline.setDate(deadline.getDate() - 1);
-
- if (now > deadline) {
- Swal.fire('Info', "La désinscription n'est plus possible.", 'info');
- return;
- }
-
- const result = await Swal.fire({
- title: 'Êtes-vous sûr ?',
- text: 'Vous ne pourrez plus revenir en arrière.',
- icon: 'warning',
- showCancelButton: true,
- confirmButtonText: 'Oui, me désinscrire',
- cancelButtonText: 'Annuler',
- confirmButtonColor: '#d33',
- cancelButtonColor: '#3085d6',
- });
-
- if (!result.isConfirmed) return;
-
- try {
- const response = await cancelPermanence(permId);
- Swal.fire('Succès ✅', response.message, 'success');
- fetchMyPermanences();
- } catch (err) {
- console.error("Erreur lors de l'annulation", err);
- Swal.fire('Erreur', 'Impossible de vous désinscrire.', 'error');
- }
- };
-
- return (
-
- );
-};
-
-export const RespoCallPage = () => {
- const [isRespo, setIsRespo] = useState(null);
-
- useEffect(() => {
- const checkRespoStatus = async () => {
- try {
- const token = getToken();
-
- if (!token) {
- return ;
- }
-
- let decoded: DecodedToken;
- try {
- decoded = decodeToken(token);
- } catch {
- return ;
- }
-
- const result = await isUserRespo(Number(decoded.userId));
- setIsRespo(result.data === true);
- } catch (error) {
- console.error('Erreur lors de la vérification du rôle respo', error);
- setIsRespo(false);
- }
- };
-
- checkRespoStatus();
- }, []);
-
- return (
-
-
-
-
-
- {isRespo === null ? (
-
Chargement...
- ) : isRespo === false ? (
-
- ❌ Accès refusé : vous n'êtes pas responsable d'une permanence.
-
- ) : (
-
- )}
-
-
-
-
- );
-};
diff --git a/frontend/src/pages/perm/permAvailable.tsx b/frontend/src/pages/perm/permAvailable.tsx
new file mode 100644
index 0000000..9e3d3bd
--- /dev/null
+++ b/frontend/src/pages/perm/permAvailable.tsx
@@ -0,0 +1,64 @@
+import { useEffect, useState } from 'react';
+import Swal from 'sweetalert2';
+
+import EmergencyModal from '../../components/home/emergencyModal';
+import { Navbar } from '../../components/navbar';
+import { AvailablePermanencesList } from '../../components/permanence/permList';
+import { type Permanence } from '../../interfaces/permanence.interface';
+import { applyToPermanence, getMyPermanences, getOpenPermanences } from '../../services/requests/permanence.service';
+
+const PermanencesPageAvailable: React.FC = () => {
+ const [permanences, setPermanences] = useState([]);
+ const [myPermanences, setMyPermanences] = useState([]);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ const fetchData = async () => {
+ try {
+ const [openPerms, myPerms] = await Promise.all([getOpenPermanences(), getMyPermanences()]);
+ setPermanences(openPerms);
+ setMyPermanences(myPerms);
+ } catch (err) {
+ console.error('Erreur lors du chargement des permanences', err);
+ Swal.fire('Erreur', 'Impossible de charger les permanences.', 'error');
+ }
+ };
+
+ const handleApplyToPermanence = async (permId: number) => {
+ if (isSubmitting) return;
+ setIsSubmitting(true);
+
+ try {
+ const response = await applyToPermanence(permId);
+ await Swal.fire('Succès ✅', response.message, 'success');
+ fetchData();
+ } catch (err) {
+ console.error("Erreur lors de l'inscription", err);
+ Swal.fire('Erreur', "Impossible de s'inscrire à la permanence.", 'error');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default PermanencesPageAvailable;
diff --git a/frontend/src/pages/perm/permMy.tsx b/frontend/src/pages/perm/permMy.tsx
new file mode 100644
index 0000000..4665ce3
--- /dev/null
+++ b/frontend/src/pages/perm/permMy.tsx
@@ -0,0 +1,79 @@
+import { useEffect, useState } from 'react';
+import Swal from 'sweetalert2';
+
+import EmergencyModal from '../../components/home/emergencyModal';
+import { Navbar } from '../../components/navbar';
+import { MyPermanencesList } from '../../components/permanence/permUser';
+import { type Permanence } from '../../interfaces/permanence.interface';
+import { cancelPermanence, getMyPermanences } from '../../services/requests/permanence.service';
+
+const PermanencesPageMy: React.FC = () => {
+ const [myPermanences, setMyPermanences] = useState([]);
+
+ useEffect(() => {
+ fetchMyPermanences();
+ }, []);
+
+ const fetchMyPermanences = async () => {
+ try {
+ const perms = await getMyPermanences();
+ setMyPermanences(perms);
+ } catch (err) {
+ console.error('Erreur lors du chargement', err);
+ Swal.fire('Erreur', 'Impossible de charger vos permanences.', 'error');
+ }
+ };
+
+ const handleCancelPermanence = async (permId: number) => {
+ const selectedPermanence = myPermanences.find((perm) => perm.id === permId);
+ if (!selectedPermanence) {
+ Swal.fire('Erreur', 'Permanence non trouvée.', 'error');
+ return;
+ }
+
+ const now = new Date();
+ const deadline = new Date(selectedPermanence.start_at);
+ deadline.setDate(deadline.getDate() - 1);
+
+ if (now > deadline) {
+ Swal.fire('Info', "La désinscription n'est plus possible.", 'info');
+ return;
+ }
+
+ const result = await Swal.fire({
+ title: 'Êtes-vous sûr ?',
+ text: 'Vous ne pourrez plus revenir en arrière.',
+ icon: 'warning',
+ showCancelButton: true,
+ confirmButtonText: 'Oui, me désinscrire',
+ cancelButtonText: 'Annuler',
+ confirmButtonColor: '#d33',
+ cancelButtonColor: '#3085d6',
+ });
+
+ if (!result.isConfirmed) return;
+
+ try {
+ const response = await cancelPermanence(permId);
+ Swal.fire('Succès ✅', response.message, 'success');
+ fetchMyPermanences();
+ } catch (err) {
+ console.error("Erreur lors de l'annulation", err);
+ Swal.fire('Erreur', 'Impossible de vous désinscrire.', 'error');
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default PermanencesPageMy;
diff --git a/frontend/src/pages/perm/permRespoCall.tsx b/frontend/src/pages/perm/permRespoCall.tsx
new file mode 100644
index 0000000..7b7d225
--- /dev/null
+++ b/frontend/src/pages/perm/permRespoCall.tsx
@@ -0,0 +1,64 @@
+import { useEffect, useState } from 'react';
+import { Navigate } from 'react-router-dom';
+
+import { Footer } from '../../components/footer';
+import EmergencyModal from '../../components/home/emergencyModal';
+import { Navbar } from '../../components/navbar';
+import { RespoPresenceManagement } from '../../components/permanence/appealPerm';
+import { type DecodedToken } from '../../interfaces/token.interfaces';
+import { decodeToken, getToken } from '../../services/requests/auth.service';
+import { isUserRespo } from '../../services/requests/permanence.service';
+
+const PermanencesPageRespoCall = () => {
+ const [isRespo, setIsRespo] = useState(null);
+
+ useEffect(() => {
+ const checkRespoStatus = async () => {
+ try {
+ const token = getToken();
+
+ if (!token) {
+ return ;
+ }
+
+ let decoded: DecodedToken;
+ try {
+ decoded = decodeToken(token);
+ } catch {
+ return ;
+ }
+
+ const result = await isUserRespo(Number(decoded.userId));
+ setIsRespo(result.data === true);
+ } catch (error) {
+ console.error('Erreur lors de la vérification du rôle respo', error);
+ setIsRespo(false);
+ }
+ };
+
+ checkRespoStatus();
+ }, []);
+
+ return (
+
+
+
+
+
+ {isRespo === null ? (
+
Chargement...
+ ) : isRespo === false ? (
+
+ ❌ Accès refusé : vous n'êtes pas responsable d'une permanence.
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+export default PermanencesPageRespoCall;
diff --git a/frontend/src/pages/roadbook.tsx b/frontend/src/pages/roadbook.tsx
index de80e67..0ea7a98 100644
--- a/frontend/src/pages/roadbook.tsx
+++ b/frontend/src/pages/roadbook.tsx
@@ -5,7 +5,7 @@ import EmergencyModal from '../components/home/emergencyModal';
import { Navbar } from '../components/navbar';
import { RoadBookCard } from '../components/roadbook/roadbookCard';
-const Roadbook: React.FC = () => (
+const RoadbookPage: React.FC = () => (
@@ -18,4 +18,4 @@ const Roadbook: React.FC = () => (
);
-export default Roadbook;
+export default RoadbookPage;
diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts
index 693559d..6b7f453 100644
--- a/frontend/src/services/api.ts
+++ b/frontend/src/services/api.ts
@@ -24,7 +24,12 @@ api.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
- console.warn('🔑 Token expiré ou invalide, redirection...');
+ console.warn('🔑 Token expiré ou invalide — suppression du token et redirection...');
+ try {
+ localStorage.removeItem('authToken');
+ } catch {
+ console.error('❌ Impossible de supprimer le token du localStorage.');
+ }
window.location.replace('/');
}
return Promise.reject(error);
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
index 8829ce4..ab1fc16 100644
--- a/frontend/tsconfig.app.json
+++ b/frontend/tsconfig.app.json
@@ -1,32 +1,30 @@
{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2020",
- "useDefineForClassFields": true,
- "lib": ["ES2020", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "skipLibCheck": true,
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "isolatedModules": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src"],
- "baseUrl": ".",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"],
+ "baseUrl": ".",
"paths": {
- "@/*": [
- "./src/*"
- ]
+ "@/*": ["./src/*"]
}
}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 20ff94b..d82983e 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -1,19 +1,17 @@
{
- "files": [],
- "references": [
- {
- "path": "./tsconfig.app.json"
- },
- {
- "path": "./tsconfig.node.json"
+ "files": [],
+ "references": [
+ {
+ "path": "./tsconfig.app.json"
+ },
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
}
- ],
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "@/*": [
- "./src/*"
- ]
- }
- }
-}
\ No newline at end of file
+}
diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json
index 87cfa44..1bca458 100644
--- a/frontend/tsconfig.node.json
+++ b/frontend/tsconfig.node.json
@@ -1,26 +1,22 @@
{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2022",
- "lib": [
- "ES2023"
- ],
- "module": "ESNext",
- "skipLibCheck": true,
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "isolatedModules": true,
- "moduleDetection": "force",
- "noEmit": true,
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": [
- "vite.config.ts"
- ]
-}
\ No newline at end of file
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 7929520..e2137e4 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,21 +1,29 @@
-import tailwindcss from '@tailwindcss/vite'
-import react from '@vitejs/plugin-react'
-import path from "path"
-import { defineConfig } from 'vite'
+import tailwindcss from '@tailwindcss/vite';
+import react from '@vitejs/plugin-react';
+import path from 'path';
+import cleanup from 'rollup-plugin-cleanup';
+import { defineConfig } from 'vite';
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
- "@": path.resolve(__dirname, "./src"),
+ '@': path.resolve(__dirname, './src'),
},
},
server: {
port: 4000,
- allowedHosts: true
+ allowedHosts: true,
},
build: {
outDir: 'dist',
- }
-})
+ rollupOptions: {
+ plugins: [
+ cleanup({
+ comments: 'none',
+ }),
+ ],
+ },
+ },
+});