From 7ae7e127f89ef107264c1234c96adffd92364d32 Mon Sep 17 00:00:00 2001 From: horek Date: Tue, 28 May 2024 17:30:38 +0200 Subject: [PATCH] feat: add modal account settings with change password --- webapp-next/components/layouts/Menu.tsx | 24 ++- webapp-next/components/layouts/MenuLinks.tsx | 4 +- webapp-next/components/modals/settings.tsx | 209 +++++++++++++++++++ webapp-next/pages/api/auth/update-user.ts | 48 +++++ 4 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 webapp-next/components/modals/settings.tsx create mode 100644 webapp-next/pages/api/auth/update-user.ts diff --git a/webapp-next/components/layouts/Menu.tsx b/webapp-next/components/layouts/Menu.tsx index 754bf93..d77f523 100644 --- a/webapp-next/components/layouts/Menu.tsx +++ b/webapp-next/components/layouts/Menu.tsx @@ -1,4 +1,4 @@ -import { Box, Flex, Image, Spacer, Stack } from '@chakra-ui/react'; +import { Box, Flex, Image, Modal, ModalOverlay, Spacer, Stack, useDisclosure } from '@chakra-ui/react'; import { Cm2dContext, baseFilters } from '@/utils/cm2d-provider'; import { useContext } from 'react'; import { FiltersAges } from '../filters/Ages'; @@ -16,6 +16,7 @@ import { hasAtLeastOneFilter, ELASTIC_API_KEY_NAME, swrPOSTFetch } from '@/utils import { FilterAssociateCauses } from '../filters/AssociateCauses'; import { RegionFilter } from '../filters/Regions'; import useSWRMutation from 'swr/mutation'; +import SettingsModal from '../modals/settings'; export const ageRanges = [ { from: 0, to: 0, key: 'Moins de 1 an' }, @@ -33,6 +34,12 @@ export const ageRanges = [ export function Menu() { const context = useContext(Cm2dContext); + const { + isOpen: isOpenSettings, + onClose: onCloseSettings, + onOpen: onOpenSettings + } = useDisclosure(); + if (!context) { throw new Error('Menu must be used within a Cm2dProvider'); } @@ -162,6 +169,11 @@ export function Menu() { icon: '/icons/about-circle.svg', link: '/about' }, + { + label: 'Paramètres du compte', + icon: '/icons/settings.svg', + onClick: onOpenSettings + }, { label: 'Mentions légales', icon: '/icons/shield-user.svg', @@ -185,6 +197,16 @@ export function Menu() { + + + + ); } diff --git a/webapp-next/components/layouts/MenuLinks.tsx b/webapp-next/components/layouts/MenuLinks.tsx index 9fd68e6..cd9013a 100644 --- a/webapp-next/components/layouts/MenuLinks.tsx +++ b/webapp-next/components/layouts/MenuLinks.tsx @@ -4,7 +4,7 @@ import NextLink from 'next/link'; interface Link { label: string; icon: string; - link: string; + link?: string; onClick?: () => void; } @@ -18,7 +18,7 @@ export const MenuLinks: React.FC = ({ links }) => { {links.map((link, index) => ( void; +}; + +type FormChangePassword = { + password: string; + confirmPassword: string; +}; + +export async function auth(url: string, { arg }: { arg: T }) { + return fetch(url, { + method: "POST", + body: JSON.stringify(arg), + headers: { "Content-Type": "application/json" }, + }); +} + +export default function SettingsModal({ user, onClose }: SettingsProps) { + const { + handleSubmit, + register, + watch, + reset, + formState: { errors, isSubmitting, isSubmitSuccessful }, + } = useForm(); + + const [isSuccessUpdateUser, setIsSuccessUpdateUser] = useState(false); + + const onSubmit: SubmitHandler = async ({ password }) => { + const response = await triggerUpdateUser({ + username: user.username as string, + password, + }); + if (response?.ok) { + setIsSuccessUpdateUser(true); + reset(); + } + }; + + const { trigger: triggerUpdateUser } = useSWRMutation( + "/api/auth/update-user", + auth<{ username: string; password: string }> + ); + + return ( + + Paramètres + + +
+ + + Identifiant + + + + User Icon + + + + + + + Changer de mot de passe + + + + Nouveau mot de passe + + + + {errors.password && errors.password.message} + + + + + Nouveau mot de passe (confirmation) + + + value === watch("password") || + "Les mots de passe ne correspondent pas", + })} + /> + + {errors.confirmPassword && errors.confirmPassword.message} + + + + Les mots de passe doivent contenir au moins 12 caractères, dont une + majuscule, une minuscule, un chiffre et un caractère spécial. + + {isSuccessUpdateUser && ( + + + Votre mot de passe a bien été modifié ! + setIsSuccessUpdateUser(false)} + /> + + )} + +
+ + + + + + +
+ ); +} diff --git a/webapp-next/pages/api/auth/update-user.ts b/webapp-next/pages/api/auth/update-user.ts new file mode 100644 index 0000000..caccba1 --- /dev/null +++ b/webapp-next/pages/api/auth/update-user.ts @@ -0,0 +1,48 @@ +import { Client } from "@elastic/elasticsearch"; +import fs from "fs"; +import type { NextApiRequest, NextApiResponse } from "next"; +import path from "path"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method === "POST") { + const { username, password } = req.body as { + username: string; + password: string; + }; + + const adminClient = new Client({ + node: process.env.ELASTIC_HOST, + auth: { + username: process.env.ELASTIC_USERNAME as string, + password: process.env.ELASTIC_PASSWORD as string, + }, + tls: { + ca: fs.readFileSync(path.resolve(process.cwd(), "./certs/ca/ca.crt")), + rejectUnauthorized: false, + }, + }); + + try { + await adminClient.security.changePassword({ + username: username, + body: { + password, + }, + }); + + res.status(200).send({ response: "ok" }); + } catch (error: any) { + if (error.statusCode) { + res.status(error.statusCode).end(); + } else { + res.status(500).end(); + } + } + } else { + res.setHeader("Allow", "POST"); + res.status(405).end("Method Not Allowed"); + } +}