Skip to content

Commit

Permalink
feat: add modal account settings with change password
Browse files Browse the repository at this point in the history
  • Loading branch information
HoreKk committed May 29, 2024
1 parent a5723b5 commit 7ae7e12
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 3 deletions.
24 changes: 23 additions & 1 deletion webapp-next/components/layouts/Menu.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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' },
Expand All @@ -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');
}
Expand Down Expand Up @@ -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',
Expand All @@ -185,6 +197,16 @@ export function Menu() {
</Box>
<UserCard user={user} />
</Box>
<Modal
isOpen={isOpenSettings}
onClose={onCloseSettings}
size="lg"
isCentered
closeOnOverlayClick={false}
>
<ModalOverlay />
<SettingsModal user={user} onClose={onCloseSettings} />
</Modal>
</Flex>
);
}
4 changes: 2 additions & 2 deletions webapp-next/components/layouts/MenuLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import NextLink from 'next/link';
interface Link {
label: string;
icon: string;
link: string;
link?: string;
onClick?: () => void;
}

Expand All @@ -18,7 +18,7 @@ export const MenuLinks: React.FC<Props> = ({ links }) => {
{links.map((link, index) => (
<Link
as={NextLink}
href={link.link}
href={link.link ? link.link : ""}
display="flex"
alignItems="left"
mt={8}
Expand Down
209 changes: 209 additions & 0 deletions webapp-next/components/modals/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import {
Alert,
AlertIcon,
Box,
Button,
ButtonGroup,
CloseButton,
Divider,
FormControl,
FormErrorMessage,
FormLabel,
Heading,
Image,
Input,
InputGroup,
InputLeftElement,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
Text,
} from "@chakra-ui/react";
import type { User } from "@/utils/cm2d-provider";
import { type SubmitHandler, useForm } from "react-hook-form";
import useSWRMutation from "swr/mutation";
import { useState } from "react";

type SettingsProps = {
user: User;
onClose: () => void;
};

type FormChangePassword = {
password: string;
confirmPassword: string;
};

export async function auth<T>(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<FormChangePassword>();

const [isSuccessUpdateUser, setIsSuccessUpdateUser] = useState(false);

const onSubmit: SubmitHandler<FormChangePassword> = 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 (
<ModalContent>
<ModalHeader>Paramètres</ModalHeader>
<ModalCloseButton />
<ModalBody>
<form id="account-form" onSubmit={handleSubmit(onSubmit)}>
<FormControl mb={[4, 6]}>
<FormLabel
htmlFor="username"
fontSize={["10px", "12px"]}
fontWeight={500}
>
Identifiant
</FormLabel>
<InputGroup>
<InputLeftElement pointerEvents="none">
<Image
src={"/icons/user.svg"}
alt="User Icon"
boxSize={9}
pt={2}
/>
</InputLeftElement>
<Input
type="text"
id="username"
placeholder="Saisissez votre adresse email"
fontSize={"12px"}
bg={"secondary.500"}
value={user.username}
isDisabled
/>
</InputGroup>
</FormControl>
<Divider my={6} />
<Heading size="sm" mb={6}>
Changer de mot de passe
</Heading>
<FormControl mb={[4, 6]} isInvalid={!!errors.password}>
<FormLabel
htmlFor="password"
fontSize={["2xs", "xs"]}
fontWeight={500}
>
Nouveau mot de passe
</FormLabel>
<Input
type="password"
id="password"
placeholder="Saisissez votre nouveau mot de passe"
fontSize="xs"
bg="secondary.500"
autoComplete="new-password"
{...register("password", {
required: "Ce champ est obligatoire",
minLength: {
value: 12,
message:
"Le mot de passe doit contenir au moins 12 caractères",
},
pattern: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).+$/,
message:
"Le mot de passe doit contenir au moins une lettre minuscule, une lettre majuscule, un chiffre et un caractère spécial",
},
})}
/>
<FormErrorMessage fontSize="xs">
{errors.password && errors.password.message}
</FormErrorMessage>
</FormControl>
<FormControl mb={[4, 6]} isInvalid={!!errors.password}>
<FormLabel
htmlFor="confirmPassword"
fontSize={["2xs", "xs"]}
fontWeight={500}
>
Nouveau mot de passe (confirmation)
</FormLabel>
<Input
type="password"
id="confirmPassword"
placeholder="Saisissez à nouveau votre nouveau mot de passe"
fontSize="xs"
bg="secondary.500"
autoComplete="new-password"
{...register("confirmPassword", {
required: "Ce champ est obligatoire",
validate: (value) =>
value === watch("password") ||
"Les mots de passe ne correspondent pas",
})}
/>
<FormErrorMessage fontSize="xs">
{errors.confirmPassword && errors.confirmPassword.message}
</FormErrorMessage>
</FormControl>
<Text fontSize="xs" color="gray.400">
Les mots de passe doivent contenir au moins 12 caractères, dont une
majuscule, une minuscule, un chiffre et un caractère spécial.
</Text>
{isSuccessUpdateUser && (
<Alert status="success" mt={4}>
<AlertIcon />
<Box>Votre mot de passe a bien été modifié !</Box>
<CloseButton
alignSelf="flex-start"
position="absolute"
// align center vertically
top="50%"
transform="translateY(-50%)"
right={2}
onClick={() => setIsSuccessUpdateUser(false)}
/>
</Alert>
)}
</form>
</ModalBody>
<ModalFooter>
<ButtonGroup>
<Button colorScheme="gray" onClick={onClose}>
Fermer
</Button>
<Button
colorScheme="primary"
type="submit"
isLoading={isSubmitting}
form="account-form"
>
Enregistrer
</Button>
</ButtonGroup>
</ModalFooter>
</ModalContent>
);
}
48 changes: 48 additions & 0 deletions webapp-next/pages/api/auth/update-user.ts
Original file line number Diff line number Diff line change
@@ -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");
}
}

0 comments on commit 7ae7e12

Please # to comment.