diff --git a/next.config.mjs b/next.config.mjs
index 4678774e6d606704bce1897a5dab960cd798bf66..c9a3c0c4982148fee841236eca5f36fe1a572ab2 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,4 +1,8 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {};
+const nextConfig = {
+ images: {
+ domains: ['https://res.cloudinary.com'],
+ }
+};
export default nextConfig;
diff --git a/src/app/auth/change-password/page.jsx b/src/app/auth/change-password/page.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a89f4840e8ec4f373062d0090f2ca5b16243e275
--- /dev/null
+++ b/src/app/auth/change-password/page.jsx
@@ -0,0 +1,120 @@
+"use client"
+import fetchRequest from '@/app/lib/fetchRequest'
+import Image from 'next/image'
+import Link from 'next/link'
+import React, { useEffect, useState } from 'react'
+import { useSearchParams } from 'next/navigation'
+import Loader from '@/components/Loader/Loader'
+import { PASSWORD_REGEX } from '@/app/lib/constants'
+
+const ChangePassword = () => {
+ const [isSuccess, setIsSuccess] = useState(false)
+ const [formErrors, setFormErrors] = useState([])
+ const [password, setPassword] = useState("")
+ const [confirmPassword, setConfirmPassword] = useState("")
+ const [isLoading, setIsLoading] = useState(false)
+ const params = useSearchParams();
+ const handleChangePassword = async (event) => {
+ event.preventDefault()
+ setIsLoading(true)
+ const { isSuccess, data, errors } = await fetchRequest(`/password_reset/confirm/?token=${params.get("token")}`, {
+ method: "POST",
+ body: JSON.stringify({
+ password,
+ token: params.get("token")
+ })
+ })
+ if (isSuccess) {
+ setIsSuccess(true)
+ }
+ else {
+ console.log(errors)
+ setIsLoading(false)
+ if (errors.type === "Validation Error") {
+ if (errors.detail.token) {
+ setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est invalide"])
+ }
+ else {
+ setFormErrors(["Le Mot de passe est invalide"])
+ }
+ } else if (errors?.detail?.startsWith("The OTP password entered is not valid")) {
+ setFormErrors(["Le lien que vous avez utilisé pour réinitialiser votre mot de passe est déja utilisé"])
+ }
+ else {
+ console.log("Internal Server Error")
+ }
+ }
+ }
+ const isEmptyFields = !password && !confirmPassword
+ useEffect(() => {
+ var currentErrors = []
+ if (password !== confirmPassword) {
+ currentErrors.push("Les mots de passe ne sont pas identiques.")
+ }
+ if (password.length < 8) {
+ currentErrors.push("Le mot de passe doit comporter au moins 8 caractères.")
+ }
+ if (!PASSWORD_REGEX.test(password)) {
+ currentErrors.push("Le mot de passe doit contenir au moins une lettre, un chiffre et un caractère spécial.")
+ }
+ setFormErrors(currentErrors)
+ }, [password, confirmPassword])
+ return (
+
{
{
/>
-
-
Mot de passe oublié?
+
+
Mot de passe oublié?
{messages && (
@@ -124,7 +86,7 @@ const LoginPage = () => {
)}
diff --git a/src/app/auth/verif/page.jsx b/src/app/auth/verif/page.jsx
index cfea4df1253203ecab55cdbcb6d5644d8980cac5..15d3d4083be7e343e184e71bde68c51c9ab26e99 100644
--- a/src/app/auth/verif/page.jsx
+++ b/src/app/auth/verif/page.jsx
@@ -5,7 +5,7 @@ const Verif = () => {
// fetch data from the server /example
const fetchExampleData = async () => {
try {
- const data = await fetchRequest('http://localhost:8000/example/',
+ const data = await fetchRequest(`/example/`,
{method: 'POST',
body: JSON.stringify({
"name": "jhon",
@@ -20,18 +20,18 @@ const Verif = () => {
}
};
const isAuth = async () => {
- isAuthenticated();
+ await isAuthenticated();
}
return (
- <>
+
user is redirected to this page after successful login
{/*test fetchExampleData */}
- >
+
);
}
export default Verif;
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
index 6c139df80533021db2ee61f9f06c23be2eb0a57f..b6ba7d37334c09788aa0026af48f37b048eed1d8 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -2,29 +2,6 @@
@tailwind components;
@tailwind utilities;
-:root {
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
- }
-}
-
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
-}
@layer utilities {
.text-balance {
@@ -34,18 +11,23 @@ body {
/* Customize the scrollbar */
::-webkit-scrollbar {
- width: 6px; /* Set the width of the scrollbar */
+ width: 6px;
+ /* Set the width of the scrollbar */
}
::-webkit-scrollbar-thumb {
- background-color: rgba(0, 0, 0, 0.3); /* Color of the thumb */
- border-radius: 3px; /* Rounded corners of the thumb */
+ background-color: rgba(0, 0, 0, 0.3);
+ /* Color of the thumb */
+ border-radius: 3px;
+ /* Rounded corners of the thumb */
}
::-webkit-scrollbar-thumb:hover {
- background-color: rgba(0, 0, 0, 0.5); /* Color of the thumb on hover */
+ background-color: rgba(0, 0, 0, 0.5);
+ /* Color of the thumb on hover */
}
::-webkit-scrollbar-track {
- background-color: rgba(0, 0, 0, 0.1); /* Color of the track */
-}
+ background-color: rgba(0, 0, 0, 0.1);
+ /* Color of the track */
+}
\ No newline at end of file
diff --git a/src/app/layout.js b/src/app/layout.js
index 9aef1df7d6c3d50515ab62f578c5cc963a1243a7..3cf1c509714b5ff4b72ca6c66ac3cbaaed88e86a 100644
--- a/src/app/layout.js
+++ b/src/app/layout.js
@@ -1,5 +1,6 @@
import { Inter } from "next/font/google";
import "./globals.css";
+import Header from "@/app/ui/Header";
const inter = Inter({ subsets: ["latin"] });
@@ -11,7 +12,10 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
- {children}
+
+
+ {children}
+
);
}
diff --git a/src/app/lib/constants.js b/src/app/lib/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4da663555ffe54dfea91d044d2b58ec3c463486
--- /dev/null
+++ b/src/app/lib/constants.js
@@ -0,0 +1 @@
+export const PASSWORD_REGEX = /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).*$/;
\ No newline at end of file
diff --git a/src/app/lib/dal.js b/src/app/lib/dal.js
deleted file mode 100644
index 74c6293b78f5f50dac5fc7d8a9b11ed8f1b15fbe..0000000000000000000000000000000000000000
--- a/src/app/lib/dal.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import 'server-only'
-
-import Cookies from 'js-cookie';
-import { decrypt } from '@/app/lib/session'
-import {redirect} from "next/navigation";
-
-export const verifySession = cache(async () => {
- const cookie = Cookies.get('session').value
- const session = await decrypt(cookie)
-
- if (!session.token) {
- redirect('/login')
- }
-
- return { isAuth: true, userName: session.username }
-})
\ No newline at end of file
diff --git a/src/app/lib/fetchRequest.js b/src/app/lib/fetchRequest.js
index 3afb1c3072b662f2ba0461e36390f1f9e0700eed..9effc1e3b2c63c8d919ab26c92990e5ca65e98ba 100644
--- a/src/app/lib/fetchRequest.js
+++ b/src/app/lib/fetchRequest.js
@@ -1,29 +1,35 @@
//custom fetch tha have the token in header
import Cookies from 'js-cookie';
-import {decrypt} from "@/app/lib/session";
-
+import { decrypt } from "@/app/lib/session";
+const BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"
const fetchRequest = async (url, options = {}) => {
- const token = Cookies.get('session');
- console.log('token', token)
- const jwtDecrypted = decrypt(token);
+ const jwtCookie = Cookies.get('session');
+ console.log('jwtCookie', jwtCookie)
+ const jwtDecrypted = await decrypt(jwtCookie);
console.log('jwtDecrypted', jwtDecrypted)
const headers = {
...options.headers,
- 'Authorization': `Token ${token}`,
+ // add authorization header with token if there is jwtDecrypted.sessionData.token
+ Authorization: jwtDecrypted?.sessionData.token ? `Bearer ${jwtDecrypted.sessionData.token}` : undefined,
'Content-Type': 'application/json',
};
- const response = await fetch(url, {
+ const response = await fetch(`${BASE_URL}${url}`, {
...options,
headers,
});
if (!response.ok) {
- throw new Error('Network response was not ok');
+ try {
+ const errorData = await response.json();
+ return { isSuccess: false, errors: errorData, data: null }
+ } catch (error) {
+ return { isSuccess: false, errors: error, data: null }
+ }
}
-
- return response.json();
+ const data = await response.json()
+ return { isSuccess: true, errors: null, data: data };
};
export default fetchRequest;
diff --git a/src/app/lib/isAuthenticated.js b/src/app/lib/isAuthenticated.js
index 0a880acf5d8ce15ae58da3d0295208ff7a4ca3f6..7c77a5e9fe0c6cb8dff56ea1052bd1b27e4b6f6b 100644
--- a/src/app/lib/isAuthenticated.js
+++ b/src/app/lib/isAuthenticated.js
@@ -1,16 +1,20 @@
//verify if the user is authenticated
import Cookies from "js-cookie";
+import {decrypt} from "@/app/lib/session";
-export default function isAuthenticated() {
+export default async function isAuthenticated() {
if (!Cookies.get('session')) {
console.log('user is not authenticated');
// redirect to auth/login page
- window.location.href = '/auth/login';
-
+ // window.location.href = '/auth/login';
return false;
}
- else console.log('user is authenticated');
- return true;
+
+ console.log('user is authenticated');
+ const session = Cookies.get('session')
+ const cookieDecoded = await decrypt(session)
+ return { isAuth: true, sessionData: cookieDecoded.sessionData }
+
}
diff --git a/src/app/lib/isAuthenticatedSSR.js b/src/app/lib/isAuthenticatedSSR.js
new file mode 100644
index 0000000000000000000000000000000000000000..09a8fd77ead1a9e744b9e263ec5ddf81362ab5f1
--- /dev/null
+++ b/src/app/lib/isAuthenticatedSSR.js
@@ -0,0 +1,21 @@
+//verify if the user is authenticated
+
+import { cookies } from 'next/headers'
+
+import {decrypt} from "@/app/lib/session";
+
+export default async function isAuthenticatedSSR() {
+ if (!cookies().get('session')) {
+ console.log('user is not authenticated');
+ // redirect to auth/login page
+ // window.location.href = '/auth/login';
+ return false;
+ }
+
+ console.log('user is authenticated');
+ const cookie = cookies().get('session')?.value
+ const session = await decrypt(cookie)
+ return { isAuth: true, sessionData: session.sessionData }
+
+}
+
diff --git a/src/app/lib/session.js b/src/app/lib/session.js
index 850ed748db48de63bae014a7fe245ff3a84f3278..1eb509a2e30326ef4e345033f9811c8dc0688513 100644
--- a/src/app/lib/session.js
+++ b/src/app/lib/session.js
@@ -2,7 +2,8 @@
import Cookies from 'js-cookie';
import { SignJWT, jwtVerify } from 'jose'
-const secretKey = "password1234"
+const secretKey = process.env.NEXT_PUBLIC_SESSION_SECRET;
+console.log(secretKey);
const encodedKey = new TextEncoder().encode(secretKey)
export async function encrypt(payload) {
diff --git a/src/app/page.js b/src/app/page.js
index ecfca6f8e5034eb7f60a623d3db202433f6b96b9..87ce7aa74a17bf1ba51f0e9979b2d955634bf8ca 100644
--- a/src/app/page.js
+++ b/src/app/page.js
@@ -1,149 +1,8 @@
-import Image from "next/image";
export default function Home() {
return (
-
-
- Get started by editing
- src/app/page.js
-
-
-
-
-
-
-
-
-
+ Home Page
);
}
diff --git a/src/app/ui/Header.jsx b/src/app/ui/Header.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b9d5df239d8cd894961ef8108103ed744d0617b5
--- /dev/null
+++ b/src/app/ui/Header.jsx
@@ -0,0 +1,54 @@
+// 'use client';
+
+// import { useState, useEffect } from 'react';
+import Link from 'next/link';
+// import isAuthenticated from "@/app/lib/isAuthenticated";
+import isAuthenticatedSSR from "@/app/lib/isAuthenticatedSSR";
+import LogoutButton from "@/app/ui/LogoutButton";
+// import fetchRequest from "@/app/lib/fetchRequest";
+// import Cookies from "js-cookie";
+
+const Header = async () => {
+
+ const {isAuth, sessionData} = await isAuthenticatedSSR()
+ console.log('isAuth', isAuth)
+ console.log('sessionData', sessionData)
+ return (
+
+
+
+
+ );
+};
+
+export default Header;
\ No newline at end of file
diff --git a/src/app/ui/LogoutButton.js b/src/app/ui/LogoutButton.js
new file mode 100644
index 0000000000000000000000000000000000000000..b219fc4381e545d6cd4e3facfe37c20696fbe65b
--- /dev/null
+++ b/src/app/ui/LogoutButton.js
@@ -0,0 +1,26 @@
+'use client';
+
+import Cookies from 'js-cookie';
+import fetchRequest from "@/app/lib/fetchRequest";
+
+const LogoutButton = () => {
+ const logout = async () => {
+ const response = await fetchRequest(`/logout`, {
+ method: 'GET'});
+ console.log(response);
+ if (response.isSuccess) {
+ console.log('logout successful');
+ Cookies.remove('session');
+ window.location.href = '/';
+ }
+
+ };
+
+ return (
+
+ );
+};
+
+export default LogoutButton;
diff --git a/src/components/Loader/Loader.css b/src/components/Loader/Loader.css
new file mode 100644
index 0000000000000000000000000000000000000000..1e1ca65608312aa3c0c9ecfbed194e5cee0cbf75
--- /dev/null
+++ b/src/components/Loader/Loader.css
@@ -0,0 +1,46 @@
+@keyframes ldio-gcsicpsikdq {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.ldio-gcsicpsikdq div {
+ width: 50px;
+ height: 50px;
+ border-top-color: transparent;
+ border: 5px solid transparent;
+ border-radius: 50%;
+}
+
+.ldio-gcsicpsikdq div {
+ animation: ldio-gcsicpsikdq 1s linear infinite;
+}
+
+.loadingio-spinner-rolling-daoiuzlm498 {
+ width: min-content;
+ height: 70px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ background: transparent;
+}
+
+.ldio-gcsicpsikdq {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ transform: translateZ(0) scale(1);
+ backface-visibility: hidden;
+ transform-origin: 0 0;
+ /* see note above */
+}
+
+.ldio-gcsicpsikdq div {
+ box-sizing: content-box;
+}
\ No newline at end of file
diff --git a/src/components/Loader/Loader.jsx b/src/components/Loader/Loader.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2430f19a7bd30aade3569d38228bdc38b43b5288
--- /dev/null
+++ b/src/components/Loader/Loader.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import "./Loader.css"
+const Loader = ({ size, border, className, color }) => {
+ return (
+
+ )
+}
+
+export default Loader
\ No newline at end of file
diff --git a/src/middleware.js b/src/middleware.js
index 1308f827736c79a423734acb70d0051515d7d5b1..62024a0b3f987cb5b71312e1770bb7245c57fed8 100644
--- a/src/middleware.js
+++ b/src/middleware.js
@@ -1,12 +1,10 @@
-"use client";
-
import { NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
-import Cookies from 'js-cookie';
+import { cookies } from 'next/headers'
// 1. Specify protected and public routes
-const protectedRoutes = ['/dashboard']
-const publicRoutes = ['/login', '/signup', '/']
+const protectedRoutes = ['/dashboard', '/auth/verif']
+const publicRoutes = ['/auth/login', '/auth/signup']
export default async function middleware(req) {
// 2. Check if the current route is protected or public
@@ -15,22 +13,21 @@ export default async function middleware(req) {
const isPublicRoute = publicRoutes.includes(path)
// 3. Decrypt the session from the cookie
- const cookie = Cookies.get('session');
- console.log('cookie', cookie)
+ const cookie = cookies().get('session')?.value
const session = await decrypt(cookie)
-
+ console.log('session dfdf', session)
// 5. Redirect to /login if the user is not authenticated
- if (isProtectedRoute && !session?.token) {
- return NextResponse.redirect(new URL('/login', req.nextUrl))
+ if (isProtectedRoute && !session?.sessionData.token) {
+ return NextResponse.redirect(new URL('/auth/login', req.nextUrl))
}
// 6. Redirect to /dashboard if the user is authenticated
if (
isPublicRoute &&
- session?.token &&
- !req.nextUrl.pathname.startsWith('/auth')
+ session?.sessionData.token &&
+ !req.nextUrl.pathname.startsWith('/auth/verif')
) {
- return NextResponse.redirect(new URL('/auth', req.nextUrl))
+ return NextResponse.redirect(new URL('/auth/verif', req.nextUrl))
}
return NextResponse.next()