Display & Delete in love list

This commit is contained in:
2024-10-11 19:08:52 +02:00
parent b702bc629c
commit 1077400260
23 changed files with 644 additions and 523 deletions
@@ -19,11 +19,13 @@ import { useActions } from './hooks/useActions';
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
import Loader from '@/assets/Loader';
import './styles.scss';
import Input from '@/components/Input/Input';
// ---------------------------------------------------------------------------------------------------------------------
const Login = () => {
const [error, setError] = useState(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const { handleSubmit } = useActions({ setError, setIsLoading });
return (
@@ -37,36 +39,29 @@ const Login = () => {
priority
/>
<form
className='formContainer'
onSubmit={handleSubmit}
>
<div className='formContainer'>
<h1 className='formTitle'>Connexion</h1>
<div className='form'>
<form
className='form'
onSubmit={handleSubmit}
>
{error && <p className='error'>{error}</p>}
<div className='inputContainer'>
<input
type='text'
name='username'
className='input'
required
/>
<label className='inputLabel'>
Nom d&apos;utilisateur
</label>
</div>
<div className='inputContainer'>
<input
type='password'
name='password'
className='input'
required
/>
<label className='inputLabel'>Mot de passe</label>
</div>
</div>
<div className='buttonsContainer'>
<Input
name='username'
type='text'
placeholder="Nom d'utilisateur"
autoComplete='username'
required
/>
<Input
type='password'
name='password'
placeholder='Mot de passe'
autoComplete='password'
required
/>
<button
type='submit'
className='submitButton'
@@ -75,8 +70,8 @@ const Login = () => {
Se connecter
{isLoading && <Loader />}
</button>
</div>
</form>
</form>
</div>
</div>
);
};
+18 -55
View File
@@ -20,69 +20,31 @@
flex-direction: column;
align-items: stretch;
gap: 10px;
padding: 20px 30px;
width: 95svw;
max-width: 450px;
border: 2px solid #fefefa;
border-radius: 7px;
background-color: #d94253;
& > .formTitle {
margin-bottom: 20px;
font-size: 2rem;
font-weight: 300;
text-align: center;
color: #fefefa;
color: #d94253;
}
& > .form {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 20px;
gap: 15px;
padding: 20px;
border: 1px solid #d94253;
border-radius: 10px;
background-color: #f9e2e5;
& > .error {
text-align: center;
color: #fefefa;
}
& > .inputContainer {
position: relative;
display: grid;
& > .inputLabel {
position: absolute;
top: 50%;
left: 15px;
transform: translateY(-50%);
padding: 5px;
background-color: #d94253;
color: #fefefa;
user-select: none;
pointer-events: none;
transition: top 0.3s ease, left 0.3s ease,
font-size 0.3s ease, transform 0.3s ease;
}
& > .input {
padding: 10px;
border: 1px solid #fefefa;
border-radius: 5px;
background-color: #d94253;
outline: none;
font-size: 1em;
color: #fefefa;
&:focus + .inputLabel,
&:not(:placeholder-shown) + .inputLabel {
top: 0;
left: 10px;
transform: translateY(-50%);
font-size: 0.9em;
}
}
}
& > .forgetLink {
position: relative;
width: max-content;
@@ -106,28 +68,29 @@
width: 100%;
}
}
}
& > .buttonsContainer {
display: flex;
justify-content: center;
align-items: center;
gap: 30px;
margin-top: 15px;
& > .submitButton {
align-self: center;
position: relative;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 7px 42px;
border: 1px solid #fefefa;
border: 1px solid #e1e1e1;
border-radius: 5px;
background-color: #fefefa;
font-size: 0.9rem;
background-color: #ffffff;
font-size: 1rem;
color: #d94253;
cursor: pointer;
transition: border-color 0.3s ease, background-color 0.3s ease,
color 0.3s ease;
&:hover {
border-color: #d94253;
background-color: #d94253;
color: #ffffff;
}
& > svg {
position: absolute;
+1 -1
View File
@@ -18,7 +18,7 @@ body {
flex-direction: column;
width: 100svw;
height: 100svh;
background-color: #ffffff;
background-color: #fbeced;
overflow: hidden;
}
+30 -35
View File
@@ -1,12 +1,25 @@
// ---------------------------------------------------------------------------------------------------------------------
//! Imports
// ---------------------------------------------------------------------------------------------------------------------
// -------------------------------------------------- React & Next -----------------------------------------------------
import { useEffect, useRef, useState } from 'react';
// ---------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------- Components ------------------------------------------------------
import { IconLayer, ScatterplotLayer } from 'deck.gl';
// ---------------------------------------------------------------------------------------------------------------------
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
import { StringifiedPinIcon } from '@/assets/PinIcon';
import { getDataset } from '../request';
// ---------------------------------------------------------------------------------------------------------------------
export const useData = () => {
const deckRef = useRef(null);
const [canUseGeolocation, setCanUseGeolocation] = useState(false);
const [geoError, setGeoError] = useState(null);
const [canUseGeolocation, setCanUseGeolocation] = useState<boolean>(false);
const [geoError, setGeoError] = useState<string | null>(null);
const [userLocation, setUserLocation] = useState<{ [key: string]: number }>(
{}
);
@@ -14,9 +27,9 @@ export const useData = () => {
const [source, setSource] = useState([]);
const [layers, setLayers] = useState([]);
const [isAddLoveOpened, setIsAddLoveOpened] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [addLoveMessage, setAddLoveMessage] = useState(null);
const [isAddLoveOpened, setIsAddLoveOpened] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [addLoveMessage, setAddLoveMessage] = useState<string | null>(null);
const [viewState, setViewState] = useState<any>({
longitude: 2.209666999999996,
@@ -61,7 +74,18 @@ export const useData = () => {
(async () => {
const dataset = await getDataset();
setSource(dataset);
const formattedDataset = dataset.map((point) => {
const properties = { ...point };
delete properties.geopoint;
return {
type: 'Feature',
geometry: point.geopoint,
properties: properties,
};
});
setSource(formattedDataset);
})();
}, []);
@@ -127,32 +151,3 @@ export const useData = () => {
setAddLoveMessage,
};
};
const getDataset = async () => {
const token = localStorage.getItem('token');
const request = await fetch('/api/point', {
method: 'GET',
headers: {
Authorization: token,
},
});
const response = await request.json();
if (response) {
const formattedData = response.map((point) => {
const properties = { ...point };
delete properties.geopoint;
return {
type: 'Feature',
geometry: point.geopoint,
properties: properties,
};
});
return formattedData;
}
return [];
};
+54
View File
@@ -0,0 +1,54 @@
export const useActions = ({
loveToDelete,
setLoveToDelete,
setDeleteMessage,
refreshDataset,
}) => {
const handleEdit = (id: string) => {
alert('Fonctionnalité en cours de développement');
};
const openPopup = (id: string) => {
setDeleteMessage(null);
setLoveToDelete(id);
};
const closePopup = () => {
setDeleteMessage(null);
setLoveToDelete(null);
};
const deleteLove = async () => {
const token = localStorage.getItem('token');
try {
const response = await fetch('/api/point', {
method: 'DELETE',
headers: {
Authorization: token,
'Content-Type': 'application/json',
},
body: JSON.stringify({
pointId: loveToDelete,
}),
});
if (response.ok) {
await refreshDataset();
setLoveToDelete(null);
return;
}
} catch (error) {
console.error(error);
}
setDeleteMessage("Une erreur s'est produite. Veuillez réessayer");
};
return {
handleEdit,
openPopup,
closePopup,
deleteLove,
};
};
+45
View File
@@ -0,0 +1,45 @@
// ---------------------------------------------------------------------------------------------------------------------
//! Imports
// ---------------------------------------------------------------------------------------------------------------------
// -------------------------------------------------- React & Next -----------------------------------------------------
import { useEffect, useState } from 'react';
// ---------------------------------------------------------------------------------------------------------------------
// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
import { getDataset } from '@/app/request';
// ---------------------------------------------------------------------------------------------------------------------
export const useData = () => {
const [dataset, setDataset] = useState([]);
const [loveToDelete, setLoveToDelete] = useState<string | null>(null);
const [deleteMessage, setDeleteMessage] = useState<string | null>(null);
useEffect(() => {
refreshDataset();
}, []);
const refreshDataset = async () => {
const data = await getDataset();
const formattedDataset = data.map((d) => {
const formattedDate = new Date(d.createdat).toLocaleString('fr-FR');
return {
...d,
createdat: formattedDate,
};
});
setDataset(formattedDataset);
};
return {
dataset,
loveToDelete,
setLoveToDelete,
deleteMessage,
setDeleteMessage,
refreshDataset,
};
};
-35
View File
@@ -1,35 +0,0 @@
// ---------------------------------------------------------------------------------------------------------------------
//! Imports
// ---------------------------------------------------------------------------------------------------------------------
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
import './styles.scss';
// ---------------------------------------------------------------------------------------------------------------------
const MyLoves = () => {
const data = [];
return (
<div className='myLovesContainer'>
<h1 className='title'>Mes loves</h1>
<div className='myLovesList'>
{data.map((love, index) => (
<div
key={love.id || index}
className='loveItem'
>
<p className='date'>
Le: {love.createdAt || '20/09/2024'}
</p>
<p className='description'>
{love.description || 'Description'}
</p>
</div>
))}
</div>
</div>
);
};
export default MyLoves;
+90
View File
@@ -0,0 +1,90 @@
'use client';
import TrashIcon from '@/assets/TrashIcon';
// ---------------------------------------------------------------------------------------------------------------------
//! Imports
// ---------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------- Components ------------------------------------------------------
import ConfirmPopup from '@/components/ConfirmPopup/ConfirmPopup';
// ---------------------------------------------------------------------------------------------------------------------
// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
import { useData } from './hooks/useData';
import { useActions } from './hooks/useActions';
// ---------------------------------------------------------------------------------------------------------------------
// ------------------------------------------------- Assets & Styles ---------------------------------------------------
import PenIcon from '@/assets/PenIcon';
import './styles.scss';
// ---------------------------------------------------------------------------------------------------------------------
const MyLoves = () => {
const {
dataset,
loveToDelete,
setLoveToDelete,
deleteMessage,
setDeleteMessage,
refreshDataset,
} = useData();
const { handleEdit, openPopup, closePopup, deleteLove } = useActions({
loveToDelete,
setLoveToDelete,
setDeleteMessage,
refreshDataset,
});
return (
<div className='myLovesContainer'>
<div className='header'>
<p className='title'>Mes loves</p>
</div>
<div className='myLovesList'>
{dataset.map((love) => (
<div
key={love.id}
className='loveItem'
>
<div className='infos'>
<p className='location'>{love.location}</p>
<p className='comment'>{love.comment || '-'}</p>
<p className='date'>Le: {love.createdat}</p>
</div>
<div className='actionButtons'>
<button
type='button'
className='actionButton'
onClick={() => handleEdit(love.id)}
>
<PenIcon />
</button>
<button
type='button'
className='actionButton'
onClick={() => openPopup(love.id)}
>
<TrashIcon />
</button>
</div>
</div>
))}
</div>
{loveToDelete && (
<ConfirmPopup
title='Supprimer ce love'
content='Es-tu sûr de vouloir supprimer ce love ?'
onClose={closePopup}
message={deleteMessage}
onAccept={deleteLove}
/>
)}
</div>
);
};
export default MyLoves;
+61 -15
View File
@@ -2,8 +2,21 @@
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
& > .header {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 20px;
padding: 20px;
width: 100%;
background-color: #d94253;
color: #ffffff;
& > .title {
font-size: 1.2rem;
}
}
& > .title {
font-size: 1.5rem;
@@ -16,24 +29,57 @@
flex-direction: column;
& > .loveItem {
display: flex;
flex-direction: column;
gap: 5px;
$buttonSize: 30px;
display: grid;
grid-template-columns: 1fr $buttonSize;
padding: 10px;
background-color: #d94253;
border-bottom: 1px solid #d94253;
background-color: #f9e2e5;
&:not(:last-child) {
border-bottom: 2px solid #d80135;
& > .infos {
display: flex;
flex-direction: column;
gap: 5px;
& > .location {
font-size: 1.1rem;
color: #d94253;
}
& > .comment {
color: #d94253;
}
& > .date {
font-size: 0.9rem;
font-weight: 300;
color: #d94253;
}
}
& > .date {
font-size: 1.1rem;
color: #ffffff;
}
& > .actionButtons {
display: flex;
flex-direction: column;
gap: 10px;
& > .description {
font-weight: 300;
color: #ffffff;
& > .actionButton {
display: flex;
justify-content: center;
align-items: center;
padding: 5px;
width: $buttonSize;
height: $buttonSize;
border: none;
border-radius: 50%;
background-color: transparent;
color: #d94253;
transition: background-color 0.3s ease, color 0.3s ease;
&:hover {
background-color: #d94253;
color: #ffffff;
}
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
export const getDataset = async () => {
const token = localStorage.getItem('token');
const request = await fetch('/api/point', {
method: 'GET',
headers: {
Authorization: token,
},
});
const response = await request.json();
if (response) {
return response;
}
return [];
};
+10 -2
View File
@@ -20,10 +20,18 @@ export const useActions = ({
setPasswordMessage(null);
};
const updateUsername = async (value: string) => {
const updateUsername = async (ev: FormEvent<HTMLFormElement>) => {
resetMessage();
if (value == userData.username) {
if (!ev.currentTarget.reportValidity()) {
return;
}
const formData = new FormData(ev.currentTarget);
const value = formData.get('username').toString();
if (value == userData.username || value.length <= 1) {
return;
}
+57 -85
View File
@@ -7,7 +7,7 @@
// --------------------------------------------------- Components ------------------------------------------------------
import Link from 'next/link';
import Input from '@/components/Input/Input';
import InlineInput from '@/components/InlineInput/InlineInput';
import ConfirmPopup from '@/components/ConfirmPopup/ConfirmPopup';
// ---------------------------------------------------------------------------------------------------------------------
// -------------------------------------------------- Hooks & Utils ----------------------------------------------------
@@ -18,8 +18,6 @@ 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';
// ---------------------------------------------------------------------------------------------------------------------
@@ -72,44 +70,18 @@ const Settings = () => {
<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}
className='card'
onSubmit={updateUsername}
>
{passwordMessage && (
<p className='message'>{passwordMessage}</p>
)}
<p className='cardTitle'>Nom d&apos;utilisateur</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'
type='text'
name='username'
placeholder="Nom d'utilisateur"
defaultValue={userData.username}
autoComplete='username'
required
/>
@@ -121,7 +93,47 @@ const Settings = () => {
</button>
</form>
<div className='separator'></div>
<form
className='card'
onSubmit={updatePassword}
>
<p className='cardTitle'>Modifier le mot de passe</p>
{passwordMessage && (
<p className='message'>{passwordMessage}</p>
)}
<div className='inputsContainer'>
<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
/>
</div>
<button
type='submit'
className='submitButton'
>
Appliquer
</button>
</form>
<button
className='deleteButton'
@@ -132,52 +144,12 @@ const Settings = () => {
</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>
<ConfirmPopup
title='Supprimer le compte ?'
content='Es-tu sûr de vouloir supprimer ton compte ? Cette action est irréversible.'
onClose={handleClosePopup}
onAccept={deleteUser}
/>
)}
</div>
</div>
+34 -107
View File
@@ -9,11 +9,11 @@
display: grid;
grid-template-columns: 25px 1fr;
align-items: center;
gap: 15px;
gap: 10px;
padding: 15px;
width: 100%;
background-color: #d94253;
color: #ffffff;
box-shadow: 0 0 6px rgba(255, 255, 255, 0.2);
& > .backButton {
display: flex;
@@ -27,8 +27,6 @@
}
& > .settingsContainer {
flex-grow: 1;
position: relative;
display: flex;
flex-direction: column;
gap: 20px;
@@ -43,34 +41,43 @@
color: #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 {
& > .card {
display: flex;
flex-direction: column;
gap: 10px;
gap: 5px;
padding: 10px;
border: 1px solid #d94253;
border-radius: 10px;
background-color: #f9e2e5;
& > .cardTitle {
font-size: 1rem;
color: #d94253;
}
& > .inputsContainer {
display: flex;
flex-direction: column;
gap: 5px;
padding: 15px 0;
}
& > .submitButton {
padding: 7px 21px;
border: none;
border: 1px solid #e1e1e1;
border-radius: 5px;
background-color: #d94253;
background-color: #ffffff;
font-size: 1rem;
color: #ffffff;
color: #d94253;
cursor: pointer;
transition: border-color 0.3s ease, background-color 0.3s ease,
color 0.3s ease;
&:hover {
border-color: #d94253;
background-color: #d94253;
color: #ffffff;
}
}
}
@@ -81,92 +88,12 @@
align-items: center;
gap: 10px;
padding: 7px 21px;
border: 1px solid #d94253;
border: none;
border-radius: 5px;
background-color: #d94253;
background-color: #f9e2e5;
font-size: 1rem;
color: #ffffff;
color: #d94253;
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;
}
}
}
}
}
}
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
export const useData = () => {
const [isLoading, setIsLoading] = useState(true);
const [userData, setUserData] = useState(null);
useEffect(() => {
@@ -23,11 +24,13 @@ export const useData = () => {
),
};
setIsLoading(false);
setUserData(formattedResponse);
})();
}, []);
return {
isLoading,
userData,
};
};
+6 -2
View File
@@ -22,7 +22,7 @@ import './styles.scss';
const Statistics = () => {
const router = useRouter();
const { userData } = useData();
const { isLoading, userData } = useData();
if (!userData) {
return;
@@ -33,6 +33,10 @@ const Statistics = () => {
router.push('/auth/login');
};
if (isLoading) {
return <></>;
}
return (
<div className='accountContainer'>
<div className='header'>
@@ -57,7 +61,7 @@ const Statistics = () => {
</Link>
</div>
</div>
<div className='separator' />
<div className='statistics'>
<div className='statistic'>
<SlotCounter
+9 -20
View File
@@ -13,6 +13,7 @@
gap: 20px;
padding: 20px;
width: 100%;
background-color: #d94253;
& > .infosContainer {
display: flex;
@@ -21,13 +22,13 @@
& > .username {
font-size: 1.1rem;
color: #d94253;
color: #ffffff;
}
& > .sinceDate {
font-size: 0.9rem;
font-weight: 300;
color: #d94253;
color: #e1e1e1;
}
}
@@ -43,33 +44,17 @@
height: $size;
border: none;
background-color: transparent;
color: #d94253;
color: #ffffff;
cursor: pointer;
}
}
}
& > .separator {
display: block;
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%
);
}
& > .statistics {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 20px;
justify-items: center;
align-items: center;
gap: 15px;
padding: 40px 20px;
width: 100%;
@@ -77,6 +62,10 @@
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
border: 1px solid #d94253;
border-radius: 10px;
background-color: #f9e2e5;
font-weight: 300;
color: #d94253;