Skip to content

Commit

Permalink
add routes to login and sign up
Browse files Browse the repository at this point in the history
  • Loading branch information
BenoitSerrano committed Dec 10, 2024
1 parent 3c284e9 commit 83639e7
Show file tree
Hide file tree
Showing 28 changed files with 589 additions and 18 deletions.
29 changes: 29 additions & 0 deletions src/client/src/lib/api/usersApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { userInfoType } from '../../types';
import { BASE_URL } from './constants';
import { performApiCall } from './utils';

const usersApi = {
createUser,
login,
};

async function createUser(params: {
email: string;
password: string;
}): Promise<{ token: string; userInfo: userInfoType }> {
const URL = `${BASE_URL}/users`;
return performApiCall(URL, 'POST', {
email: params.email,
password: params.password,
});
}

async function login(params: {
email: string;
password: string;
}): Promise<{ token: string; userInfo: userInfoType }> {
const URL = `${BASE_URL}/login`;
return performApiCall(URL, 'POST', { email: params.email, password: params.password });
}

export { usersApi };
33 changes: 33 additions & 0 deletions src/client/src/lib/localSessionHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { userInfoType } from '../types';
import { localStorage } from './localStorage';

const localSessionHandler = {
setToken,
logout,
getToken,
getIsAuthenticated,
setUserInfo,
};

function setToken(token: string) {
localStorage.jwtTokenHandler.set(token);
}

function setUserInfo(userInfo: userInfoType) {
localStorage.userInfoHandler.set(userInfo);
}

function getToken() {
return localStorage.jwtTokenHandler.get();
}

function getIsAuthenticated() {
return !!localStorage.jwtTokenHandler.get();
}

function logout() {
localStorage.userInfoHandler.remove();
localStorage.jwtTokenHandler.remove();
}

export { localSessionHandler };
3 changes: 2 additions & 1 deletion src/client/src/lib/localStorage/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { jwtTokenHandler } from './jwtTokenHandler';
import { userInfoHandler } from './userInfoHandler';

const localStorage = { jwtTokenHandler };
const localStorage = { jwtTokenHandler, userInfoHandler };

export { localStorage };
2 changes: 1 addition & 1 deletion src/client/src/lib/localStorage/jwtTokenHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const KEY = 'UPTIME_JWT_TOKEN';
const KEY = 'SENTINEL_JWT_TOKEN';

function get() {
return localStorage.getItem(KEY) || undefined;
Expand Down
23 changes: 23 additions & 0 deletions src/client/src/lib/localStorage/userInfoHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { userInfoType } from '../../types';

const KEY = 'SENTINEL_USER_INFO';

function get() {
const value = localStorage.getItem(KEY);
if (!value) {
return undefined;
}
return JSON.parse(value) as userInfoType;
}

function set(value: userInfoType) {
localStorage.setItem(KEY, JSON.stringify(value));
}

function remove() {
localStorage.removeItem(KEY);
}

const userInfoHandler = { get, set, remove };

export { userInfoHandler };
127 changes: 127 additions & 0 deletions src/client/src/pages/SignIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { TextField, Typography, styled } from '@mui/material';
import { FormEvent, useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { useAlert } from '../lib/alert';
import { useNavigate } from 'react-router-dom';
import { Card } from '../components/Card';
// import { Link } from '../components/Link';
import { pathHandler } from '../lib/pathHandler';
import { LoadingButton } from '@mui/lab';
import { usersApi } from '../lib/api/usersApi';
import { localSessionHandler } from '../lib/localSessionHandler';

function SignIn() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate();

const { displayAlert } = useAlert();

const mutation = useMutation({
mutationFn: usersApi.login,
onSuccess: (data) => {
const { token, userInfo } = data;
localSessionHandler.setToken(token);
localSessionHandler.setUserInfo(userInfo);

navigate(pathHandler.getRoutePath('HOME'));
},
onError: () => {
displayAlert({
variant: 'error',
text: 'Une erreur est survenue.',
});
},
});

return (
<>
<ContentContainer>
<Card size="medium">
<CardContent onSubmit={handleSubmit}>
<TitleContainer>
<Typography variant="h2">Se connecter</Typography>
</TitleContainer>

<FieldsContainer>
<FieldContainer>
<TextField
autoFocus
fullWidth
name="email"
type="email"
label="Adresse e-mail"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</FieldContainer>
<FieldContainer>
<TextField
fullWidth
name="password"
type="password"
label="Mot de passe"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
</FieldContainer>
{/* <DisplayPasswordLinkContainer>
<Link to="/request-reset-password">
<Typography>Mot de passe oublié ?</Typography>
</Link>
</DisplayPasswordLinkContainer> */}
</FieldsContainer>

<LoadingButton
loading={mutation.isPending}
type="submit"
variant="contained"
disabled={!password || !email}
>
Se connecter
</LoadingButton>
</CardContent>
</Card>
</ContentContainer>
</>
);

function handleSubmit(event: FormEvent<HTMLFormElement>) {
mutation.mutate({ email, password });
event.preventDefault();
}
}

const ContentContainer = styled('div')({
display: 'flex',
width: '100%',
justifyContent: 'center',
alignItems: 'center',
});

// const DisplayPasswordLinkContainer = styled('div')({
// display: 'flex',
// textDecorationLine: 'underline',
// alignItems: 'center',
// justifyContent: 'center',
// });

const CardContent = styled('form')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
}));

const FieldsContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
marginBottom: theme.spacing(2),
}));
const FieldContainer = styled('div')(({ theme }) => ({ marginBottom: theme.spacing(2) }));
const TitleContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(6),
textAlign: 'center',
}));

export { SignIn };
144 changes: 144 additions & 0 deletions src/client/src/pages/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Checkbox, FormControlLabel, TextField, Typography, styled } from '@mui/material';
import { ChangeEvent, FormEvent, useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { useAlert } from '../lib/alert';
import { useNavigate } from 'react-router-dom';
import { Card } from '../components/Card';
import { pathHandler } from '../lib/pathHandler';
import { LoadingButton } from '@mui/lab';
import { usersApi } from '../lib/api/usersApi';
import { Link } from 'react-router-dom';
import { localSessionHandler } from '../lib/localSessionHandler';

function SignUp() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isCGVChecked, setIsCGVChecked] = useState(false);
const navigate = useNavigate();

const { displayAlert } = useAlert();

const mutation = useMutation({
mutationFn: usersApi.createUser,
onSuccess: (data) => {
const { token, userInfo } = data;
localSessionHandler.setToken(token);
localSessionHandler.setUserInfo(userInfo);

navigate(pathHandler.getRoutePath('HOME'));
},
onError: () => {
displayAlert({
variant: 'error',
text: 'Une erreur est survenue.',
});
},
});

return (
<>
<ContentContainer>
<Card size="medium">
<CardContent onSubmit={handleSubmit}>
<TitleContainer>
<Typography variant="h2">Créer un compte</Typography>
</TitleContainer>

<FieldsContainer>
<FieldContainer>
<TextField
required
autoFocus
fullWidth
name="email"
type="email"
label="Adresse e-mail"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</FieldContainer>
<FieldContainer>
<TextField
required
fullWidth
name="password"
type="password"
label="Mot de passe"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
</FieldContainer>

<FieldContainer>
<FormControlLabel
required
control={
<Checkbox checked={isCGVChecked} onChange={onChangeCGV} />
}
label={
<Typography>
J'ai lu et j'accepte les 
<Link
target="_blank"
to={pathHandler.getRoutePath(
'TERMS_AND_CONDITIONS_OF_SALE',
)}
>
Conditions Générales de Vente
</Link>
</Typography>
}
/>
</FieldContainer>
</FieldsContainer>

<LoadingButton
loading={mutation.isPending}
type="submit"
variant="contained"
disabled={!password || !email || !isCGVChecked}
>
Créer un compte
</LoadingButton>
</CardContent>
</Card>
</ContentContainer>
</>
);

function handleSubmit(event: FormEvent<HTMLFormElement>) {
mutation.mutate({ email, password });
event.preventDefault();
}

function onChangeCGV(_event: ChangeEvent<HTMLInputElement>, checked: boolean) {
setIsCGVChecked(checked);
}
}

const ContentContainer = styled('div')({
display: 'flex',
width: '100%',
justifyContent: 'center',
alignItems: 'center',
});

const CardContent = styled('form')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3),
}));

const FieldsContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
marginBottom: theme.spacing(2),
}));
const FieldContainer = styled('div')(({ theme }) => ({ marginBottom: theme.spacing(2) }));
const TitleContainer = styled('div')(({ theme }) => ({
marginBottom: theme.spacing(6),
textAlign: 'center',
}));

export { SignUp };
5 changes: 5 additions & 0 deletions src/client/src/pages/TermsAndConditionsOfSale.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function TermsAndConditionsOfSale() {
return <div>Truc</div>;
}

export { TermsAndConditionsOfSale };
6 changes: 6 additions & 0 deletions src/client/src/routes/routeElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import { SystemPulses } from '../pages/SystemPulses';
import { Home } from '../pages/Home';
import { ROUTE_KEYS } from './routeKeys';
import { Monitors } from '../pages/Monitors/Monitors';
import { SignIn } from '../pages/SignIn';
import { SignUp } from '../pages/SignUp';
import { TermsAndConditionsOfSale } from '../pages/TermsAndConditionsOfSale';

const ROUTE_ELEMENTS: Record<(typeof ROUTE_KEYS)[number], { element: JSX.Element }> = {
HOME: { element: <Home /> },
SYSTEM_PULSES: { element: <SystemPulses /> },
SYSTEM_PULSE_SUMMARY: { element: <SystemPulseSummary /> },
MONITORS: { element: <Monitors /> },
SIGN_IN: { element: <SignIn /> },
SIGN_UP: { element: <SignUp /> },
TERMS_AND_CONDITIONS_OF_SALE: { element: <TermsAndConditionsOfSale /> },
};

export { ROUTE_ELEMENTS };
Loading

0 comments on commit 83639e7

Please sign in to comment.