diff --git a/Front-End/src/app/[locale]/admin/layout.js b/Front-End/src/app/[locale]/admin/layout.js index 223dc03..3798cf0 100644 --- a/Front-End/src/app/[locale]/admin/layout.js +++ b/Front-End/src/app/[locale]/admin/layout.js @@ -15,6 +15,7 @@ import { } from 'lucide-react'; import DropdownMenu from '@/components/DropdownMenu'; import Logo from '@/components/Logo'; +import Popup from '@/components/Popup'; import { FE_ADMIN_HOME_URL, FE_ADMIN_SUBSCRIPTIONS_URL, @@ -45,6 +46,7 @@ export default function Layout({ const [establishment, setEstablishment] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [isPopupVisible, setIsPopupVisible] = useState(false); const pathname = usePathname(); const currentPage = pathname.split('/').pop(); @@ -54,11 +56,19 @@ export default function Layout({ const softwareName = "N3WT School"; const softwareVersion = `v${process.env.NEXT_PUBLIC_APP_VERSION}`; + const handleDisconnect = () => { + setIsPopupVisible(true); + }; + + const confirmDisconnect = () => { + setIsPopupVisible(false); + disconnect(); + }; const dropdownItems = [ { label: 'Déconnexion', - onClick: disconnect, + onClick: handleDisconnect, icon: LogOut, }, ]; @@ -74,46 +84,48 @@ export default function Layout({ }, []); return ( - <> - - - {!isLoading && ( -
- -
- {/* Header - h-16 = 64px */} -
-
{headerTitle}
- } - items={dropdownItems} - buttonClassName="" - menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded shadow-lg" - /> -
- {/* Main Content */} -
- {/* Content avec scroll si nécessaire */} -
- {children} -
- {/* Footer - h-16 = 64px */} -
-
- © {new Date().getFullYear()} N3WT-INNOV Tous droits réservés. -
{softwareName} - {softwareVersion}
+ + {!isLoading && ( +
+ +
+ {/* Header - h-16 = 64px */} +
+
{headerTitle}
+ } + items={dropdownItems} + buttonClassName="" + menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded shadow-lg" + /> +
+ {/* Main Content */} +
+ {/* Content avec scroll si nécessaire */} +
+ {children}
- -
+ {/* Footer - h-16 = 64px */} +
+
+ © {new Date().getFullYear()} N3WT-INNOV Tous droits réservés. +
{softwareName} - {softwareVersion}
+
+ +
+
- - )} - -
+ )} + setIsPopupVisible(false)} + /> +
- ); } diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 9e0b486..d5cc9db 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -44,6 +44,7 @@ import { import DjangoCSRFToken from '@/components/DjangoCSRFToken' import { useCsrfToken } from '@/context/CsrfContext'; import { fetchRegistrationFileGroups } from '@/app/lib/registerFileGroupAction'; +import { ESTABLISHMENT_ID } from '@/utils/Url'; const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; @@ -371,7 +372,8 @@ useEffect(()=>{ idGuardians: selectedGuardiansIds, fees: allFeesIds, discounts: allDiscountsds, - fileGroup: selectedFileGroup + fileGroup: selectedFileGroup, + establishment: ESTABLISHMENT_ID }; createRegisterForm(data, csrfToken) @@ -413,7 +415,8 @@ useEffect(()=>{ sibling: [] }, fees: allFeesIds, - discounts: allDiscountsds + discounts: allDiscountsds, + establishment: ESTABLISHMENT_ID }; createRegisterForm(data, csrfToken) diff --git a/Front-End/src/app/[locale]/parents/layout.js b/Front-End/src/app/[locale]/parents/layout.js index b7f2f1a..41cb7d0 100644 --- a/Front-End/src/app/[locale]/parents/layout.js +++ b/Front-End/src/app/[locale]/parents/layout.js @@ -9,6 +9,9 @@ import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL import useLocalStorage from '@/hooks/useLocalStorage'; import { fetchMessages } from '@/app/lib/messagerieAction'; import ProtectedRoute from '@/components/ProtectedRoute'; +import { SessionProvider } from 'next-auth/react'; +import { disconnect } from '@/app/lib/authAction'; +import Popup from '@/components/Popup'; export default function Layout({ children, @@ -18,6 +21,16 @@ export default function Layout({ const [messages, setMessages] = useState([]); const [userId, setUserId] = useLocalStorage("userId", '') ; const [isLoading, setIsLoading] = useState(true); + const [isPopupVisible, setIsPopupVisible] = useState(false); + + const handleDisconnect = () => { + setIsPopupVisible(true); + }; + + const confirmDisconnect = () => { + setIsPopupVisible(false); + disconnect(); + }; useEffect(() => { setIsLoading(true); @@ -42,7 +55,8 @@ export default function Layout({ } return ( - + +
{/* Entête */}
@@ -75,7 +89,7 @@ export default function Layout({ } items={[ - { label: 'Se déconnecter', icon: LogOut, onClick: () => {} }, + { label: 'Se déconnecter', icon: LogOut, onClick: handleDisconnect }, { label: 'Settings', icon: Settings , onClick: () => { router.push(FE_PARENTS_SETTINGS_URL); } } ]} buttonClassName="p-2 rounded-full hover:bg-gray-200" @@ -90,7 +104,14 @@ export default function Layout({ {children}
-
+ setIsPopupVisible(false)} + /> +
+ ); } diff --git a/Front-End/src/app/[locale]/users/login/page.js b/Front-End/src/app/[locale]/users/login/page.js index 64d31a5..e2c37b0 100644 --- a/Front-End/src/app/[locale]/users/login/page.js +++ b/Front-End/src/app/[locale]/users/login/page.js @@ -36,7 +36,7 @@ export default function Page() { return data.errorMessage === "" } - async function handleFormLogin(formData) { + /*async function handleFormLogin(formData) { setIsLoading(true); try { @@ -76,7 +76,52 @@ export default function Page() { setIsLoading(false); setErrorMessage('An error occurred during sign in.'); } - } + }*/ + + function handleFormLogin(formData) { + setIsLoading(true); + + signIn('credentials', { + redirect: false, + email: formData.get('login'), + password: formData.get('password'), + }).then(result => { + console.log('Sign In Result', result); + setIsLoading(false); + + if (result.error) { + setErrorMessage(result.error); + } else { + getSession().then(session => { + if (!session || !session.user) { + throw new Error('Session not found'); + } + const user = session.user; + console.log('User Session:', user); + localStorage.setItem('userId', user.id); // Stocker l'identifiant de l'utilisateur + if (user.droit === 0) { + // Vue ECOLE + } else if (user.droit === 1) { + // Vue ADMIN + router.push(FE_ADMIN_SUBSCRIPTIONS_URL); + } else if (user.droit === 2) { + // Vue PARENT + router.push(FE_PARENTS_HOME_URL); + } else { + // Cas anormal + } + }).catch(error => { + console.error('Error during session retrieval:', error); + setIsLoading(false); + setErrorMessage('An error occurred during session retrieval.'); + }); + } + }).catch(error => { + console.error('Error during sign in:', error); + setIsLoading(false); + setErrorMessage('An error occurred during sign in.'); + }); + } if (isLoading === true) { return // Affichez le composant Loader diff --git a/Front-End/src/app/lib/authAction.js b/Front-End/src/app/lib/authAction.js index 099a178..14701d8 100644 --- a/Front-End/src/app/lib/authAction.js +++ b/Front-End/src/app/lib/authAction.js @@ -1,156 +1,155 @@ - +import { signOut } from 'next-auth/react'; import { BE_AUTH_LOGIN_URL, BE_AUTH_REGISTER_URL, BE_AUTH_PROFILES_URL, BE_AUTH_RESET_PASSWORD_URL, BE_AUTH_NEW_PASSWORD_URL, - FE_USERS_LOGIN_URL , + FE_USERS_LOGIN_URL, } from '@/utils/Url'; -import {mockUser} from "@/data/mockUsersData"; - -const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true'; - - const requestResponseHandler = async (response) => { - const body = await response.json(); if (response.ok) { return body; } - // Throw an error with the JSON body containing the form errors const error = new Error('Form submission error'); error.details = body; throw error; -} +}; + +/*export const login = (data, csrfToken) => { + const request = new Request( + `${BE_AUTH_LOGIN_URL}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + body: JSON.stringify(data), + credentials: 'include', + } + ); + return fetch(request).then(requestResponseHandler); +};*/ export const login = (data, csrfToken) => { - console.log('data', data); - const request = new Request( - `${BE_AUTH_LOGIN_URL}`, - { - method:'POST', - headers: { - 'Content-Type':'application/json', - 'X-CSRFToken': csrfToken - }, - body: JSON.stringify(data), - credentials: 'include', + const request = new Promise((resolve, reject) => { + signIn('credentials', { + redirect: false, + email: data.email, + password: data.password, + }).then(result => { + if (result.error) { + reject(new Error(result.error)); + } else { + resolve(result); } - ); - return fetch(request).then(requestResponseHandler) -} + }).catch(reject); + }); + + return request.then(requestResponseHandler); +}; /** * Disconnects the user after confirming the action. * If `NEXT_PUBLIC_USE_FAKE_DATA` environment variable is set to 'true', it will log a fake disconnect and redirect to the login URL. - * Otherwise, it will send a PUT request to the backend to update the user profile and then redirect to the login URL. + * Otherwise, it will call `signOut` from NextAuth.js to handle the logout. * * @function * @name disconnect * @returns {void} */ -export const disconnect = () => { - if (confirm("\nÊtes-vous sûr(e) de vouloir vous déconnecter ?")) { +export const disconnect = () => { + signOut({ callbackUrl: FE_USERS_LOGIN_URL }); +}; - if (useFakeData) { - console.log('Fake disconnect:', mockUser); - router.push(`${FE_USERS_LOGIN_URL}`); - } else { - console.log('Fake disconnect:', mockUser); - router.push(`${FE_USERS_LOGIN_URL}`); - } +export const createProfile = (data, csrfToken) => { + const request = new Request( + `${BE_AUTH_PROFILES_URL}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + credentials: 'include', + body: JSON.stringify(data), } - }; - - -export const createProfile = (data,csrfToken) => { -const request = new Request( - `${BE_AUTH_PROFILES_URL}`, - { - method:'POST', - headers: { - 'Content-Type':'application/json', - 'X-CSRFToken': csrfToken - }, - credentials: 'include', - body: JSON.stringify(data), - } - ); - return fetch(request).then(requestResponseHandler) -} + ); + return fetch(request).then(requestResponseHandler); +}; export const updateProfile = (id, data, csrfToken) => { - const request = new Request( + const request = new Request( `${BE_AUTH_PROFILES_URL}/${id}`, { - method:'PUT', - headers: { - 'Content-Type':'application/json', - 'X-CSRFToken': csrfToken - }, - credentials: 'include', - body: JSON.stringify(data), - } - ); - return fetch(request).then(requestResponseHandler) -} + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + credentials: 'include', + body: JSON.stringify(data), + } + ); + return fetch(request).then(requestResponseHandler); +}; export const sendNewPassword = (data, csrfToken) => { - const request = new Request( - `${BE_AUTH_NEW_PASSWORD_URL}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrfToken - }, - credentials: 'include', - body: JSON.stringify(data), - } - ); - return fetch(request).then(requestResponseHandler) -} + `${BE_AUTH_NEW_PASSWORD_URL}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + credentials: 'include', + body: JSON.stringify(data), + } + ); + return fetch(request).then(requestResponseHandler); +}; -export const subscribe = (data,csrfToken) =>{ +export const subscribe = (data, csrfToken) => { const request = new Request( - `${BE_AUTH_REGISTER_URL}`, - { - method:'POST', - headers: { - 'Content-Type':'application/json', - 'X-CSRFToken': csrfToken - }, - credentials: 'include', - body: JSON.stringify( data), - } - ); - return fetch(request).then(requestResponseHandler) -} + `${BE_AUTH_REGISTER_URL}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + credentials: 'include', + body: JSON.stringify(data), + } + ); + return fetch(request).then(requestResponseHandler); +}; export const resetPassword = (uuid, data, csrfToken) => { const request = new Request( - `${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`, - { - method:'POST', - headers: { - 'Content-Type':'application/json', - 'X-CSRFToken': csrfToken - }, - credentials: 'include', - body: JSON.stringify(data), - } + `${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken + }, + credentials: 'include', + body: JSON.stringify(data), + } ); - return fetch(request).then(requestResponseHandler) -} + return fetch(request).then(requestResponseHandler); +}; export const getResetPassword = (uuid) => { - const url= `${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`; + const url = `${BE_AUTH_RESET_PASSWORD_URL}/${uuid}`; return fetch(url, { - headers: { - 'Content-Type': 'application/json', - }, - }).then(requestResponseHandler) -} \ No newline at end of file + headers: { + 'Content-Type': 'application/json', + }, + }).then(requestResponseHandler); +}; \ No newline at end of file diff --git a/Front-End/src/pages/api/auth/[...nextauth].js b/Front-End/src/pages/api/auth/[...nextauth].js index 51b604f..c0391eb 100644 --- a/Front-End/src/pages/api/auth/[...nextauth].js +++ b/Front-End/src/pages/api/auth/[...nextauth].js @@ -40,7 +40,12 @@ const options = { }) ], session: { - jwt: true + jwt: true, + maxAge: 24 * 60 * 60, // 1 day in seconds + updateAge: 24 * 60 * 60 // Update session every day + }, + jwt: { + maxAge: 24 * 60 * 60 // 1 day in seconds }, callbacks: { async jwt({ token, user }) { diff --git a/Front-End/src/pages/api/auth/signout.js b/Front-End/src/pages/api/auth/signout.js deleted file mode 100644 index 97af273..0000000 --- a/Front-End/src/pages/api/auth/signout.js +++ /dev/null @@ -1,9 +0,0 @@ -import { signOut } from 'next-auth/react'; - -export default function SignOut() { - return ( - - ); -} \ No newline at end of file