diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts
new file mode 100644
index 0000000..0382b86
--- /dev/null
+++ b/app/api/auth/login/route.ts
@@ -0,0 +1,71 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getDBConnexion } from '../../utils';
+import { compareSync } from 'bcrypt';
+
+type RequestBody = {
+ username?: string;
+ password?: string;
+};
+
+export const POST = async (req: NextRequest) => {
+ const { username, password }: RequestBody = await req.json();
+
+ if (!username || !password) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Missing username or password field in request body',
+ },
+ {
+ status: 400,
+ }
+ );
+ }
+
+ try {
+ const pool = getDBConnexion();
+
+ const { rows } = await pool.query(
+ `SELECT * FROM "user" WHERE "username"=$1 LIMIT 1;`,
+ [username]
+ );
+
+ await pool.end();
+
+ if (rows.length != 1) {
+ return NextResponse.json({
+ error: true,
+ message: 'User not found',
+ });
+ }
+
+ const { id, password: hashedPassword } = rows[0];
+
+ if (!compareSync(password, hashedPassword)) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Wrong password',
+ },
+ {
+ status: 403,
+ }
+ );
+ }
+
+ return NextResponse.json({
+ token: id,
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Internal error',
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+};
diff --git a/app/api/user/route.ts b/app/api/user/route.ts
new file mode 100644
index 0000000..bcb29b0
--- /dev/null
+++ b/app/api/user/route.ts
@@ -0,0 +1,285 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getDBConnexion } from '../utils';
+import { compareSync, hashSync } from 'bcrypt';
+
+export const GET = async (req: NextRequest) => {
+ const userID = req.headers.get('Authorization');
+
+ if (!userID) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Unauthorized request',
+ },
+ {
+ status: 403,
+ }
+ );
+ }
+
+ try {
+ const pool = getDBConnexion();
+
+ const { rows } = await pool.query(
+ `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`,
+ [userID]
+ );
+
+ if (rows.length != 1) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'User not found',
+ },
+ {
+ status: 404,
+ }
+ );
+ }
+
+ await pool.end();
+
+ return NextResponse.json(rows[0]);
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Internal error',
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+};
+
+type RequestBody = {
+ type?: 'username' | 'password';
+ username?: string;
+ currentPassword?: string;
+ newPassword?: string;
+};
+
+export const PUT = async (req: NextRequest) => {
+ const userID = req.headers.get('Authorization');
+
+ const { type, username, currentPassword, newPassword }: RequestBody =
+ await req.json();
+
+ if (!userID) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Unauthorized request',
+ },
+ {
+ status: 403,
+ }
+ );
+ }
+
+ if (!type) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Missing type field in request body',
+ },
+ {
+ status: 400,
+ }
+ );
+ }
+
+ if (type == 'username') {
+ if (!username) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Missing username field in request body',
+ },
+ {
+ status: 400,
+ }
+ );
+ }
+
+ try {
+ const pool = getDBConnexion();
+
+ const { rowCount } = await pool.query(
+ `SELECT COUNT(*) FROM "user" WHERE "username"=$1 LIMIT 1;`,
+ [username]
+ );
+
+ if (rowCount > 0) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Username already used',
+ },
+ {
+ status: 409,
+ }
+ );
+ }
+
+ const { rows } = await pool.query(
+ `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`,
+ [userID]
+ );
+
+ if (rows.length != 1) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'User not found',
+ },
+ {
+ status: 404,
+ }
+ );
+ }
+
+ await pool.query(`UPDATE "user" SET "username"=$1 WHERE "id"=$2;`, [
+ username,
+ userID,
+ ]);
+
+ await pool.end();
+
+ return NextResponse.json({
+ message: 'Successfully updated username',
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Internal error',
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+ }
+
+ if (type == 'password') {
+ if (!currentPassword || !newPassword) {
+ return NextResponse.json(
+ {
+ error: true,
+ message:
+ 'Missing currentPassword and/or newPassword field(s) in request body',
+ },
+ {
+ status: 400,
+ }
+ );
+ }
+
+ try {
+ const pool = getDBConnexion();
+
+ const { rows } = await pool.query(
+ `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`,
+ [userID]
+ );
+
+ if (rows.length != 1) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'User not found',
+ },
+ {
+ status: 404,
+ }
+ );
+ }
+
+ const { password: currentHashedPassword } = rows[0];
+
+ if (!compareSync(currentPassword, currentHashedPassword)) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Wrong password',
+ },
+ {
+ status: 403,
+ }
+ );
+ }
+
+ const newHashedPassword = hashSync(newPassword, 10);
+
+ await pool.query(`UPDATE "user" SET "password"=$1 WHERE "id"=$2`, [
+ newHashedPassword,
+ userID,
+ ]);
+
+ await pool.end();
+
+ return NextResponse.json({
+ message: 'Successfully updated password',
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Internal error',
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+ }
+};
+
+export const DELETE = async (req: NextRequest) => {
+ const userID = req.headers.get('Authorization');
+
+ try {
+ const pool = getDBConnexion();
+
+ const { rows } = await pool.query(
+ `SELECT * FROM "user" WHERE "id"=$1 LIMIT 1;`,
+ [userID]
+ );
+
+ if (rows.length != 1) {
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'User not found',
+ },
+ {
+ status: 404,
+ }
+ );
+ }
+
+ await pool.query(`DELETE FROM "user" WHERE "id"=$1 LIMIT 1;`, [userID]);
+
+ await pool.end();
+
+ return NextResponse.json({
+ message: 'Successfully delete user',
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ {
+ error: true,
+ message: 'Internal error',
+ },
+ {
+ status: 500,
+ }
+ );
+ }
+};
diff --git a/app/api/utils.ts b/app/api/utils.ts
new file mode 100644
index 0000000..fa8c22c
--- /dev/null
+++ b/app/api/utils.ts
@@ -0,0 +1,9 @@
+import { createPool } from '@vercel/postgres';
+
+export const getDBConnexion = () => {
+ const pool = createPool({
+ connectionString: process.env.POSTGRES_URL,
+ });
+
+ return pool;
+};
diff --git a/app/auth/login/hooks/useActions.js b/app/auth/login/hooks/useActions.js
index f4139ed..998c9e6 100644
--- a/app/auth/login/hooks/useActions.js
+++ b/app/auth/login/hooks/useActions.js
@@ -17,19 +17,16 @@ export const useActions = ({ setError, setIsLoading }) => {
setIsLoading(true);
try {
- const request = await fetch(
- 'https://lovemap-backend.vercel.app/auth/login',
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- username,
- password,
- }),
- }
- );
+ const request = await fetch('/api/auth/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ username,
+ password,
+ }),
+ });
const response = await request.json();
@@ -44,7 +41,6 @@ export const useActions = ({ setError, setIsLoading }) => {
return;
}
if (request.status == 500) {
- console.log('request.status: ', request.status);
setError('Erreur interne. Veuillez réessayer');
setIsLoading(false);
return;
diff --git a/app/auth/login/page.jsx b/app/auth/login/page.jsx
index dd76ab2..2cde739 100644
--- a/app/auth/login/page.jsx
+++ b/app/auth/login/page.jsx
@@ -18,7 +18,7 @@ import { useActions } from './hooks/useActions';
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
import Loader from '@/assets/Loader';
-import '../styles.scss';
+import './styles.scss';
// ---------------------------------------------------------------------------------------------------------------------
const Login = () => {
diff --git a/app/auth/styles.scss b/app/auth/login/styles.scss
similarity index 99%
rename from app/auth/styles.scss
rename to app/auth/login/styles.scss
index 44ff915..6908a00 100644
--- a/app/auth/styles.scss
+++ b/app/auth/login/styles.scss
@@ -39,7 +39,7 @@
display: flex;
flex-direction: column;
align-items: stretch;
- gap: 15px;
+ gap: 20px;
& > .error {
text-align: center;
diff --git a/app/globals.scss b/app/globals.scss
index 9d39b0e..cd25b40 100644
--- a/app/globals.scss
+++ b/app/globals.scss
@@ -18,7 +18,7 @@ body {
flex-direction: column;
width: 100svw;
height: 100svh;
- background-color: #d94253;
+ background-color: #ffffff;
}
a {
diff --git a/app/layout.jsx b/app/layout.jsx
deleted file mode 100644
index e85d807..0000000
--- a/app/layout.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Inter } from 'next/font/google';
-import Provider from './provider';
-import './globals.scss';
-
-const inter = Inter({ subsets: ['latin'] });
-
-export const metadata = {
- title: 'Lovemap ❤️',
-};
-
-const RootLayout = ({ children }) => {
- return (
-
-
- {children}
-
-
- );
-};
-
-export default RootLayout;
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..4c029c3
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,35 @@
+// ---------------------------------------------------------------------------------------------------------------------
+//! Imports
+// ---------------------------------------------------------------------------------------------------------------------
+
+// ------------------------------------------------------ Next ---------------------------------------------------------
+import { Metadata } from 'next';
+// ---------------------------------------------------------------------------------------------------------------------
+
+// --------------------------------------------------- Components ------------------------------------------------------
+import Provider from './provider';
+// ---------------------------------------------------------------------------------------------------------------------
+
+// ------------------------------------------------- Assets & Styles ---------------------------------------------------
+import { Inter } from 'next/font/google';
+import './globals.scss';
+// ---------------------------------------------------------------------------------------------------------------------
+
+const inter = Inter({ subsets: ['latin'] });
+
+export const metadata: Metadata = {
+ title: 'Lovemap ❤️',
+ robots: 'none',
+};
+
+const RootLayout = ({ children }) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default RootLayout;
diff --git a/app/not-found.jsx b/app/not-found.tsx
similarity index 100%
rename from app/not-found.jsx
rename to app/not-found.tsx
diff --git a/app/page.jsx b/app/page.tsx
similarity index 100%
rename from app/page.jsx
rename to app/page.tsx
diff --git a/app/provider.jsx b/app/provider.tsx
similarity index 100%
rename from app/provider.jsx
rename to app/provider.tsx
diff --git a/app/settings/hooks/useActions.ts b/app/settings/hooks/useActions.ts
new file mode 100644
index 0000000..ad182df
--- /dev/null
+++ b/app/settings/hooks/useActions.ts
@@ -0,0 +1,159 @@
+// ---------------------------------------------------------------------------------------------------------------------
+//! Imports
+// ---------------------------------------------------------------------------------------------------------------------
+
+// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
+import { useRouter } from 'next/navigation';
+import { FormEvent } from 'react';
+// ---------------------------------------------------------------------------------------------------------------------
+
+export const useActions = ({
+ userData,
+ setIsPopupDisplayed,
+ setUsernameMessage,
+ setPasswordMessage,
+}) => {
+ const router = useRouter();
+
+ const resetMessage = () => {
+ setUsernameMessage(null);
+ setPasswordMessage(null);
+ };
+
+ const updateUsername = async (value: string) => {
+ resetMessage();
+
+ if (value == userData.username) {
+ return;
+ }
+
+ const token = localStorage.getItem('token');
+
+ try {
+ const request = await fetch('/api/user', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: token,
+ },
+ body: JSON.stringify({
+ type: 'username',
+ username: value,
+ }),
+ });
+
+ if (request.status == 409) {
+ setUsernameMessage("Nom d'utilisateur déjà pris");
+ return;
+ }
+
+ if (request.status != 200) {
+ setUsernameMessage('Erreur interne. Veuillez réessayer');
+ return;
+ }
+ setUsernameMessage("Nom d'utilisateur modifié");
+ } catch (error) {
+ console.error(error);
+
+ setUsernameMessage('Erreur interne. Veuillez réessayer');
+ }
+ };
+
+ const updatePassword = async (ev: FormEvent) => {
+ ev.preventDefault();
+
+ if (!ev.currentTarget.reportValidity()) {
+ return;
+ }
+
+ resetMessage();
+
+ const formData = new FormData(ev.currentTarget);
+ const currentPassword = formData.get('currentPassword');
+ const newPassword = formData.get('newPassword');
+ const confirmNewPassword = formData.get('confirmNewPassword');
+
+ const token = localStorage.getItem('token');
+
+ if (newPassword != confirmNewPassword) {
+ setPasswordMessage('Les mots de passes ne correspondent pas');
+ return;
+ }
+
+ try {
+ const request = await fetch('/api/user', {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: token,
+ },
+ body: JSON.stringify({
+ type: 'password',
+ currentPassword,
+ newPassword,
+ }),
+ });
+
+ if (request.status == 403) {
+ setPasswordMessage('Mauvais mot de passe');
+ return;
+ }
+ if (request.status == 500) {
+ setPasswordMessage('Erreur interne. Veuillez réessayer');
+ return;
+ }
+ setPasswordMessage('Mot de passe modifié');
+ } catch (error) {
+ console.error(error);
+
+ setPasswordMessage('Erreur interne. Veuillez réessayer');
+ }
+ };
+
+ const handleClosePopup = (ev) => {
+ ev.stopPropagation();
+ setIsPopupDisplayed(false);
+ };
+
+ const handleDeleteUser = (ev) => {
+ ev.stopPropagation();
+ resetMessage();
+ setIsPopupDisplayed(true);
+ };
+
+ const deleteUser = async () => {
+ resetMessage();
+ const token = localStorage.getItem('token');
+
+ try {
+ const request = await fetch(
+ 'https://lovemap-backend.vercel.app/user/delete',
+ {
+ method: 'DELETE',
+ headers: {
+ Authorization: token,
+ },
+ }
+ );
+
+ if (request.status != 200) {
+ // setMessage('Erreur interne. Veuillez réessayer');
+ return;
+ }
+
+ localStorage.clear();
+ router.push('/auth/login');
+ } catch (error) {
+ console.error(error);
+ // setMessage('Erreur interne. Veuillez réessayer');
+ }
+ };
+
+ return {
+ updateUsername,
+ updatePassword,
+ handleClosePopup,
+ handleDeleteUser,
+ deleteUser,
+ };
+};
diff --git a/app/settings/hooks/useData.ts b/app/settings/hooks/useData.ts
new file mode 100644
index 0000000..1e89f47
--- /dev/null
+++ b/app/settings/hooks/useData.ts
@@ -0,0 +1,42 @@
+import { useEffect, useState } from 'react';
+
+export const useData = () => {
+ const [userData, setUserData] = useState(null);
+ const [isPopupDisplayed, setIsPopupDisplayed] = useState(false);
+ const [usernameMessage, setUsernameMessage] = useState(null);
+ const [passwordMessage, setPasswordMessage] = useState(null);
+
+ useEffect(() => {
+ (async () => {
+ const token = localStorage.getItem('token');
+
+ const request = await fetch('/api/user', {
+ method: 'GET',
+ headers: {
+ Authorization: token,
+ },
+ });
+
+ const response = await request.json();
+
+ const formattedResponse = {
+ ...response,
+ createdat: new Date(response.createdat).toLocaleDateString(
+ 'fr-FR'
+ ),
+ };
+
+ setUserData(formattedResponse);
+ })();
+ }, []);
+
+ return {
+ userData,
+ isPopupDisplayed,
+ setIsPopupDisplayed,
+ usernameMessage,
+ setUsernameMessage,
+ passwordMessage,
+ setPasswordMessage,
+ };
+};
diff --git a/app/settings/page.jsx b/app/settings/page.jsx
deleted file mode 100644
index d8d1c3b..0000000
--- a/app/settings/page.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-// ---------------------------------------------------------------------------------------------------------------------
-//! Imports
-// ---------------------------------------------------------------------------------------------------------------------
-
-// --------------------------------------------------- Components ------------------------------------------------------
-import Link from 'next/link';
-import Input from '@/components/Input/Input';
-// ---------------------------------------------------------------------------------------------------------------------
-
-// ------------------------------------------------- Assets & Styles ---------------------------------------------------
-import ArrowRightIcon from '@/assets/ArrowRightIcon';
-import './styles.scss';
-// ---------------------------------------------------------------------------------------------------------------------
-
-const Settings = () => {
- return (
-
-
-
-
-
-
Éditer le profil
-
-
-
-
-
-
-
-
- );
-};
-
-export default Settings;
diff --git a/app/settings/page.tsx b/app/settings/page.tsx
new file mode 100644
index 0000000..cd015fd
--- /dev/null
+++ b/app/settings/page.tsx
@@ -0,0 +1,187 @@
+'use client';
+
+// ---------------------------------------------------------------------------------------------------------------------
+//! Imports
+// ---------------------------------------------------------------------------------------------------------------------
+
+// --------------------------------------------------- Components ------------------------------------------------------
+import Link from 'next/link';
+import Input from '@/components/Input/Input';
+import InlineInput from '@/components/InlineInput/InlineInput';
+// ---------------------------------------------------------------------------------------------------------------------
+
+// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
+import { useData } from './hooks/useData';
+import { useActions } from './hooks/useActions';
+// ---------------------------------------------------------------------------------------------------------------------
+
+// ------------------------------------------------- Assets & Styles ---------------------------------------------------
+import ArrowRightIcon from '@/assets/ArrowRightIcon';
+import TrashIcon from '@/assets/TrashIcon';
+import CloseIcon from '@/assets/CloseIcon';
+import CheckIcon from '@/assets/CheckIcon';
+import './styles.scss';
+// ---------------------------------------------------------------------------------------------------------------------
+
+const Settings = () => {
+ const {
+ userData,
+ isPopupDisplayed,
+ setIsPopupDisplayed,
+ usernameMessage,
+ setUsernameMessage,
+ passwordMessage,
+ setPasswordMessage,
+ } = useData();
+
+ const {
+ updateUsername,
+ updatePassword,
+ handleClosePopup,
+ handleDeleteUser,
+ deleteUser,
+ } = useActions({
+ userData,
+ setIsPopupDisplayed,
+ setUsernameMessage,
+ setPasswordMessage,
+ });
+
+ if (!userData) {
+ return;
+ }
+
+ return (
+
+
+
+
+
+
Éditer le profil
+
+
+
+ {usernameMessage && (
+
{usernameMessage}
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {isPopupDisplayed && (
+
+
ev.stopPropagation()}
+ >
+
+
+ Supprimer le compte ?
+
+
+
+
+
+ Êtes-vous sûr de vouloir supprimer votre
+ compte ? Cette action est irréversible.
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default Settings;
diff --git a/app/settings/styles.scss b/app/settings/styles.scss
index 2b22227..ebc37cb 100644
--- a/app/settings/styles.scss
+++ b/app/settings/styles.scss
@@ -4,7 +4,6 @@
flex-direction: column;
gap: 20px;
width: 100%;
- background-color: #d94253;
& > .header {
display: grid;
@@ -12,6 +11,7 @@
align-items: center;
gap: 15px;
padding: 15px;
+ background-color: #d94253;
color: #ffffff;
box-shadow: 0 0 6px rgba(255, 255, 255, 0.2);
@@ -26,68 +26,147 @@
}
}
- & > .form {
- display: grid;
- grid-template-rows: repeat(2, 1fr);
- align-items: center;
+ & > .settingsContainer {
+ flex-grow: 1;
+ position: relative;
+ display: flex;
+ flex-direction: column;
gap: 20px;
- padding: 20px;
- padding-bottom: 0;
+ padding: 0 20px;
- & > .buttonsContainer {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 20px;
+ & .message {
+ padding: 5px;
+ border-radius: 5px;
+ background-color: #d94253;
+ font-size: 1rem;
+ text-align: center;
+ color: #ffffff;
+ }
- & > .submitButton,
- & > .resetButton {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 5px;
- padding: 10px;
- border: 1px solid #ffffff;
+ & > .separator {
+ display: block;
+ margin: 20px auto;
+ width: 90%;
+ height: 1px;
+ background-color: #d94253;
+ background: linear-gradient(
+ 90deg,
+ rgba(#d94253, 0) 0%,
+ rgba(#d94253, 1) 10%,
+ rgba(#d94253, 1) 90%,
+ rgba(#d94253, 0) 100%
+ );
+ }
+
+ & > .passwordForm {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+
+ & > .submitButton {
+ padding: 7px 21px;
+ border: none;
border-radius: 5px;
- background-color: #ffffff;
+ background-color: #d94253;
font-size: 1rem;
- color: #d94253;
+ color: #ffffff;
cursor: pointer;
}
+ }
- & > .resetButton {
- background-color: #d94253;
- color: #ffffff;
+ & > .deleteButton {
+ display: grid;
+ grid-template-columns: 20px max-content;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+ padding: 7px 21px;
+ border: 1px solid #d94253;
+ border-radius: 5px;
+ background-color: #d94253;
+ font-size: 1rem;
+ color: #ffffff;
+ cursor: pointer;
+ }
+
+ & > .popupOverlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(#ffffff, 0.5);
+ backdrop-filter: blur(5px);
+
+ & > .popup {
+ z-index: 1;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ margin: 10px;
+ background-color: #ffffff;
+ border: 1px solid #d94253;
+ border-radius: 5px;
+
+ & > .popupHeader {
+ display: grid;
+ grid-template-columns: 1fr 20px;
+ align-items: center;
+ gap: 20px;
+ padding: 10px;
+
+ & > .popupTitle {
+ font-size: 1.2rem;
+ color: #d94253;
+ }
+
+ & > .popupCloseButton {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: none;
+ border-radius: 5px;
+ background-color: transparent;
+ color: #d94253;
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: rgba(#d94253, 0.2);
+ }
+ }
+ }
+
+ & > .popupContent {
+ padding: 10px;
+ color: #d94253;
+ }
+
+ & > .popupButtonsContainer {
+ display: flex;
+ flex-wrap: nowrap;
+ justify-content: space-evenly;
+ align-items: center;
+ padding: 10px;
+
+ & > .popupButton {
+ display: grid;
+ grid-template-columns: 20px 1fr;
+ align-items: center;
+ gap: 5px;
+ padding: 5px 15px;
+ border: none;
+ border-radius: 5px;
+ background-color: #d94253;
+ font-size: 1rem;
+ color: #ffffff;
+ cursor: pointer;
+ }
+ }
}
}
}
-
- & > .separator {
- display: block;
- margin: 20px auto;
- width: 90%;
- height: 1px;
- background-color: rgb(254, 254, 250);
- background: linear-gradient(
- 90deg,
- rgba(254, 254, 250, 0) 0%,
- rgba(254, 254, 250, 1) 10%,
- rgba(254, 254, 250, 1) 90%,
- rgba(254, 254, 250, 0) 100%
- );
- }
-
- & > .deleteButton {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 5px;
- margin: 0 20px;
- padding: 10px;
- border: 1px solid #d80135;
- border-radius: 5px;
- background-color: #d80135;
- font-size: 1rem;
- color: #ffffff;
- cursor: pointer;
- }
}
diff --git a/app/statistics/hooks/useData.js b/app/statistics/hooks/useData.js
index 638e12c..da26de8 100644
--- a/app/statistics/hooks/useData.js
+++ b/app/statistics/hooks/useData.js
@@ -7,21 +7,18 @@ export const useData = () => {
(async () => {
const token = localStorage.getItem('token');
- const request = await fetch(
- 'https://lovemap-backend.vercel.app/user',
- {
- method: 'GET',
- headers: {
- Authorization: token,
- },
- }
- );
+ const request = await fetch('/api/user', {
+ method: 'GET',
+ headers: {
+ Authorization: token,
+ },
+ });
const response = await request.json();
const formattedResponse = {
- ...response.user,
- createdat: new Date(response.user.createdat).toLocaleDateString(
+ ...response,
+ createdat: new Date(response.createdat).toLocaleDateString(
'fr-FR'
),
};
diff --git a/app/statistics/page.jsx b/app/statistics/page.tsx
similarity index 82%
rename from app/statistics/page.jsx
rename to app/statistics/page.tsx
index 3636e8e..cfcfc73 100644
--- a/app/statistics/page.jsx
+++ b/app/statistics/page.tsx
@@ -10,21 +10,29 @@ import SlotCounter from 'react-slot-counter';
// ---------------------------------------------------------------------------------------------------------------------
// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
+import { useRouter } from 'next/navigation';
import { useData } from './hooks/useData';
// ---------------------------------------------------------------------------------------------------------------------
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
import SettingsIcon from '@/assets/SettingsIcon';
+import { Logout } from '@/assets/Logout';
import './styles.scss';
// ---------------------------------------------------------------------------------------------------------------------
const Statistics = () => {
+ const router = useRouter();
const { userData } = useData();
if (!userData) {
return;
}
+ const logout = () => {
+ localStorage.clear();
+ router.push('/auth/login');
+ };
+
return (
@@ -33,12 +41,21 @@ const Statistics = () => {
Créé le: {userData.createdat}
-
-
-
+
+
+
+
+
+
@@ -48,7 +65,7 @@ const Statistics = () => {
startValue={'00'}
startValueOnce
dummyCharacterCount={10}
- direction='top-bottom'
+ direction='top-down'
containerClassName='slotCounter'
/>
Loves par semaine
@@ -59,7 +76,7 @@ const Statistics = () => {
startValue={'00'}
startValueOnce
dummyCharacterCount={10}
- direction='top-bottom'
+ direction='top-down'
containerClassName='slotCounter'
/>
Loves par mois
@@ -70,7 +87,7 @@ const Statistics = () => {
startValue={'00'}
startValueOnce
dummyCharacterCount={10}
- direction='top-bottom'
+ direction='top-down'
containerClassName='slotCounter'
/>
Loves par an
@@ -81,7 +98,7 @@ const Statistics = () => {
startValue={'00'}
startValueOnce
dummyCharacterCount={10}
- direction='top-bottom'
+ direction='top-down'
containerClassName='slotCounter'
/>
Loves total
diff --git a/app/statistics/styles.scss b/app/statistics/styles.scss
index 3a26263..f191827 100644
--- a/app/statistics/styles.scss
+++ b/app/statistics/styles.scss
@@ -21,21 +21,31 @@
& > .username {
font-size: 1.1rem;
- color: #ffffff;
+ color: #d94253;
}
& > .sinceDate {
font-size: 0.9rem;
font-weight: 300;
- color: #eee;
+ color: #d94253;
}
}
- & > .settingsButton {
- $size: 25px;
- width: $size;
- height: $size;
- color: #ffffff;
+ & > .buttonsContainer {
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ gap: 10px;
+
+ & > .button {
+ $size: 25px;
+ width: $size;
+ height: $size;
+ border: none;
+ background-color: transparent;
+ color: #d94253;
+ cursor: pointer;
+ }
}
}
@@ -43,13 +53,13 @@
display: block;
width: 90%;
height: 1px;
- background-color: rgb(254, 254, 250);
+ background-color: #d94253;
background: linear-gradient(
90deg,
- rgba(254, 254, 250, 0) 0%,
- rgba(254, 254, 250, 1) 10%,
- rgba(254, 254, 250, 1) 90%,
- rgba(254, 254, 250, 0) 100%
+ rgba(#d94253, 0) 0%,
+ rgba(#d94253, 1) 10%,
+ rgba(#d94253, 1) 90%,
+ rgba(#d94253, 0) 100%
);
}
@@ -68,45 +78,14 @@
flex-direction: column;
align-items: center;
font-weight: 300;
- color: #ffffff;
+ color: #d94253;
& > .slotCounter {
font-size: 3rem;
}
& > .statName {
- color: #eee;
- }
- }
- }
-
- & > .buttonsContainer {
- display: flex;
- flex-direction: column;
- align-items: stretch;
- gap: 2px;
- width: 100%;
- box-shadow: 0 0 6px rgba(255, 255, 255, 0.2);
-
- & > .button {
- display: flex;
- flex-wrap: nowrap;
- justify-content: space-between;
- gap: 20px;
- padding: 15px 20px;
- border: none;
- background-color: #d94253;
- font-size: 1rem;
- color: #ffffff;
- filter: brightness(1.1);
- transition: filter 0.3s ease;
-
- &:hover {
- filter: brightness(1.3);
- }
-
- & > svg {
- width: 1.2em;
+ color: #d94253;
}
}
}
diff --git a/assets/ArrowRightIcon.jsx b/assets/ArrowRightIcon.tsx
similarity index 76%
rename from assets/ArrowRightIcon.jsx
rename to assets/ArrowRightIcon.tsx
index df46e8c..a153108 100644
--- a/assets/ArrowRightIcon.jsx
+++ b/assets/ArrowRightIcon.tsx
@@ -1,4 +1,6 @@
-const ArrowRightIcon = (props) => {
+import { SVGProps } from 'react';
+
+const ArrowRightIcon = (props?: SVGProps
) => {
return (