Upgrade design & moved backend here & login and settings work with api

This commit is contained in:
2024-09-29 18:04:41 +02:00
parent 42d3212624
commit f2d1eb4289
43 changed files with 1881 additions and 359 deletions
+159
View File
@@ -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<HTMLFormElement>) => {
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,
};
};
+42
View File
@@ -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,
};
};
-73
View File
@@ -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 (
<div className='settings'>
<div className='header'>
<Link
href={'/statistics'}
className='backButton'
>
<ArrowRightIcon
style={{
rotate: '180deg',
}}
/>
</Link>
<p className='title'>Éditer le profil</p>
</div>
<form className='form'>
<Input
label="Nom d'utilisateur"
type='text'
name='username'
defaultValue='Un Épicier'
autoComplete='off'
required
/>
<Input
label='Mot de passe'
type='password'
name='password'
defaultValue='********'
autoComplete='off'
required
/>
<div className='buttonsContainer'>
<button
type='reset'
className='resetButton'
>
Annuler
</button>
<button
type='submit'
className='submitButton'
>
Appliquer
</button>
</div>
</form>
<div className='separator' />
<button className='deleteButton'>Supprimer mon compte</button>
</div>
);
};
export default Settings;
+187
View File
@@ -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 (
<div className='settings'>
<div className='header'>
<Link
href={'/statistics'}
className='backButton'
>
<ArrowRightIcon
style={{
rotate: '180deg',
}}
/>
</Link>
<p className='title'>Éditer le profil</p>
</div>
<div className='settingsContainer'>
{usernameMessage && (
<p className='message'>{usernameMessage}</p>
)}
<InlineInput
label="Nom d'utilisateur"
type='text'
name='username'
placeholder="Nom d'utilisateur"
defaultValue={userData.username}
autoComplete='username'
onSubmit={updateUsername}
/>
<div className='separator' />
<form
className='passwordForm'
onSubmit={updatePassword}
>
{passwordMessage && (
<p className='message'>{passwordMessage}</p>
)}
<Input
type='password'
name='currentPassword'
placeholder='Mot de passe actuel'
autoComplete='password'
required
/>
<Input
type='password'
name='newPassword'
placeholder='Nouveau mot de passe'
autoComplete='off'
required
/>
<Input
type='password'
name='confirmNewPassword'
placeholder='Confirmer le nouveau mot de passe'
autoComplete='off'
required
/>
<button
type='submit'
className='submitButton'
>
Appliquer
</button>
</form>
<div className='separator'></div>
<button
className='deleteButton'
onClick={handleDeleteUser}
>
<TrashIcon />
Supprimer mon compte
</button>
{isPopupDisplayed && (
<div
className='popupOverlay'
onClick={handleClosePopup}
>
<div
className='popup'
onClick={(ev) => ev.stopPropagation()}
>
<div className='popupHeader'>
<p className='popupTitle'>
Supprimer le compte ?
</p>
<button
className='popupCloseButton'
onClick={handleClosePopup}
>
<CloseIcon />
</button>
</div>
<div className='popupContent'>
<p>
Êtes-vous sûr de vouloir supprimer votre
compte ? Cette action est irréversible.
</p>
</div>
<div className='popupButtonsContainer'>
<button
type='button'
className='popupButton'
onClick={handleClosePopup}
>
<CloseIcon />
Non
</button>
<button
type='button'
className='popupButton'
onClick={deleteUser}
>
<CheckIcon />
Oui
</button>
</div>
</div>
</div>
)}
</div>
</div>
);
};
export default Settings;
+133 -54
View File
@@ -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;
}
}