@movalib/movalib-commons 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/PV_README.md +23 -0
  2. package/dist/GenderSelector.d.ts +12 -0
  3. package/dist/GenderSelector.js +20 -0
  4. package/dist/MovaSignUp.d.ts +21 -0
  5. package/dist/MovaSignUp.js +175 -0
  6. package/dist/assets/images/logo/logo_large.png +0 -0
  7. package/dist/assets/images/logo/logo_pro_large.png +0 -0
  8. package/dist/helpers/Types.d.ts +9 -0
  9. package/dist/index.d.ts +2 -4
  10. package/dist/index.js +1 -2
  11. package/package.json +15 -12
  12. package/src/GenderSelector.tsx +32 -0
  13. package/src/MovaCopyright.tsx +17 -0
  14. package/src/MovaLogin.tsx +202 -0
  15. package/src/MovaSignUp.tsx +274 -0
  16. package/src/MovaSnackbar.tsx +65 -0
  17. package/src/TestButton.tsx +12 -0
  18. package/src/assets/images/logo/header_logo.png +0 -0
  19. package/src/assets/images/logo/header_logo_pro.png +0 -0
  20. package/src/helpers/Enums.ts +38 -0
  21. package/src/helpers/Tools.ts +1 -0
  22. package/src/helpers/Types.ts +20 -0
  23. package/src/helpers/Validator.ts +57 -0
  24. package/src/index.ts +19 -0
  25. package/src/models/Address.ts +28 -0
  26. package/src/models/Role.ts +16 -0
  27. package/src/models/User.ts +59 -0
  28. package/src/react-app-env.d.ts +1 -0
  29. package/tsconfig.json +114 -0
  30. /package/dist/assets/{assets/images → images}/logo/header_logo.png +0 -0
  31. /package/dist/assets/{assets/images → images}/logo/header_logo_pro.png +0 -0
  32. /package/{dist/assets → src}/assets/images/leaf_green_large.png +0 -0
  33. /package/{dist/assets → src}/assets/images/leaf_orange_large.png +0 -0
  34. /package/{dist/assets → src}/assets/images/leaf_pink_large.png +0 -0
  35. /package/{dist/assets → src}/assets/images/logo/logo_large.png +0 -0
  36. /package/{dist/assets → src}/assets/images/logo/logo_pro_large.png +0 -0
@@ -0,0 +1,274 @@
1
+ import { CSSProperties, FormEvent, FunctionComponent, useState } from "react";
2
+ import { LoadingButton } from '@mui/lab';
3
+ import LogoLarge from './assets/images/logo/logo_large.png';
4
+ import LogoProLarge from './assets/images/logo/logo_pro_large.png';
5
+ import GreenLeafImage from "./assets/images/leaf_green_large.png";
6
+ import PinkLeafImage from "./assets/images/leaf_pink_large.png";
7
+ import { Alert, AlertColor, Box, Checkbox, Container, CssBaseline, FormControl, FormControlLabel, FormHelperText, Grid, Link, SelectChangeEvent, TextField, Typography } from "@mui/material";
8
+ import MovaCopyright from "./MovaCopyright";
9
+ import { MovaLoginForm, MovaFormField, MovaUserSignUpForm } from "./helpers/Types";
10
+ import { MovaAppType } from "./helpers/Enums";
11
+ import { validateEmail } from "./helpers/Validator";
12
+ import GenderSelector from "./GenderSelector";
13
+
14
+ // Permet de centrer le contenu de l'application
15
+ const styles: CSSProperties = {
16
+ display: 'flex',
17
+ justifyContent: 'center',
18
+ alignItems: 'center',
19
+ height: '100vh', // Ajustez la hauteur en fonction de vos besoins
20
+ };
21
+
22
+ const initialUserFormState = {
23
+ firstname: { value: '', isValid: true },
24
+ lastname: { value: '', isValid: true },
25
+ email: { value: '', isValid: true },
26
+ password: { value: '', isValid: true },
27
+ gender: { value: '', isValid: true },
28
+ birthDate: { value: '', isValid: true },
29
+ acceptsTerms: { value: false, isValid: true },
30
+ };
31
+
32
+ /**
33
+ * Propriétés du composant
34
+ * movaAppType : type d'application Movalib au sein de laquelle le composant est injectée
35
+ * onSubmit : callbakc invoquée en cas de formulaire valide
36
+ * alertMessage : éventuel message à afficher
37
+ * alertSeverity : niveau d'alerte pour le message à afficher
38
+ * loading : permet de mettre éventuellement le bouton de soumission en état de chargement
39
+ */
40
+ interface MovaSignUpProps {
41
+ movaAppType: MovaAppType,
42
+ onSubmit: (form: MovaLoginForm) => void,
43
+ alertMessage?: string,
44
+ alertSeverity?: AlertColor,
45
+ loading?: boolean
46
+ }
47
+
48
+ const MovaLogin: FunctionComponent<MovaSignUpProps> = ({ loading, movaAppType, onSubmit, alertMessage, alertSeverity }) => {
49
+
50
+ const [userForm, setUserForm] = useState<MovaUserSignUpForm>(initialUserFormState);
51
+
52
+ const [message, setMessage] = useState<string>("");
53
+ //const [loading, setLoading] = useState(false);
54
+
55
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
56
+ const fieldName: string = e.target.name;
57
+ const fieldValue: string = e.target.value;
58
+ const newField: MovaFormField = { [fieldName]: { value: fieldValue, isValid: true } };
59
+
60
+ setUserForm({ ...userForm, ...newField});
61
+ }
62
+
63
+ const handleCheckboxChange = (e: SelectChangeEvent<string>, checked: boolean): void => {
64
+ const fieldName: string = e.target.name;
65
+ const fieldValue: boolean = checked;
66
+ const newField: MovaFormField = { [fieldName]: { value: fieldValue, isValid: true } };
67
+
68
+ setUserForm({ ...userForm, ...newField});
69
+ }
70
+
71
+ const handleSelectChange = (e: SelectChangeEvent<string>): void => {
72
+ const fieldName: string = e.target.name;
73
+ const fieldValue: string = e.target.value;
74
+ const newField: MovaFormField = { [fieldName]: { value: fieldValue, isValid: true } };
75
+
76
+ setUserForm({ ...userForm, ...newField});
77
+ }
78
+
79
+ const handleSubmit = async (e: FormEvent) => {
80
+ e.preventDefault();
81
+ try {
82
+ if(validateForm() && onSubmit) {
83
+ // Si le formulaire est valide, on appel la function callback transmise en props
84
+ onSubmit(userForm);
85
+ }
86
+
87
+ }catch (error){
88
+ console.error('Error occurred during submission:', error);
89
+ }
90
+ }
91
+
92
+ const validateForm = () => {
93
+ let newForm: MovaUserSignUpForm = userForm;
94
+
95
+ // Validator email
96
+ if(!validateEmail(userForm.email.value)) {
97
+ const errorMsg: string = 'Adresse email invalide';
98
+ const newField: MovaFormField = { value: userForm.email.value, error: errorMsg, isValid: false };
99
+ newForm = { ...newForm, ...{ email: newField } };
100
+ } else {
101
+ const newField: MovaFormField = { value: userForm.email.value, error: '', isValid: true };
102
+ newForm = { ...newForm, ...{ email: newField } };
103
+ }
104
+
105
+ // Validator password
106
+ if(userForm.password.value.length < 8) {
107
+ const errorMsg: string = 'Votre mot de passe doit faire au moins 8 caractères de long.';
108
+ const newField: MovaFormField = {value: userForm.password.value, error: errorMsg, isValid: false};
109
+ newForm = { ...newForm, ...{ password: newField } };
110
+ } else {
111
+ const newField: MovaFormField = { value: userForm.password.value, error: '', isValid: true };
112
+ newForm = { ...newForm, ...{ password: newField } };
113
+ }
114
+
115
+ setUserForm(newForm);
116
+
117
+ return newForm.email.isValid && newForm.password.isValid;
118
+ }
119
+
120
+ const getMovaLogo = () => {
121
+
122
+ return movaAppType === MovaAppType.GARAGE ? LogoProLarge :
123
+ movaAppType === MovaAppType.USER ? LogoLarge :
124
+ movaAppType === MovaAppType.ADMIN ? LogoLarge : LogoLarge;
125
+ }
126
+
127
+
128
+ return (
129
+ <div style={styles}>
130
+ <img src={GreenLeafImage} style={{position: 'fixed',
131
+ float:'left',
132
+ width: '250px',
133
+ height: '400px',
134
+ top: '-20%',
135
+ left: '3%',
136
+ opacity: '0.3',
137
+ zIndex: -8}} alt='Feuille Verte Movalib'></img>
138
+
139
+ <Container component="main" maxWidth="sm">
140
+ <CssBaseline />
141
+ <Box
142
+ sx={{
143
+ marginTop: 6,
144
+ display: 'flex',
145
+ flexDirection: 'column',
146
+ alignItems: 'center',
147
+ }}
148
+ >
149
+ <img src={getMovaLogo()} style={{width:'50%'}}/>
150
+ <br />
151
+ <Typography variant="h2">Nouveau compte utilisateur</Typography>
152
+ </Box>
153
+
154
+ <Box component="form" onSubmit={handleSubmit} noValidate sx={{ mt: 1 }}>
155
+ <TextField
156
+ margin="normal"
157
+ required
158
+ autoFocus
159
+ fullWidth
160
+ id="firstname"
161
+ label="Prénom"
162
+ name="firstname"
163
+ autoComplete="given-name"
164
+ onChange={e => handleInputChange(e)}
165
+ value={userForm.firstname.value}
166
+ error={Boolean(userForm.firstname.error)}
167
+ helperText={userForm.firstname.error}
168
+ />
169
+ <TextField
170
+ margin="normal"
171
+ required
172
+ fullWidth
173
+ id="lastname"
174
+ label="Nom"
175
+ name="lastname"
176
+ autoComplete="family-name"
177
+ onChange={e => handleInputChange(e)}
178
+ value={userForm.lastname.value}
179
+ error={Boolean(userForm.lastname.error)}
180
+ helperText={userForm.lastname.error}
181
+ />
182
+ <TextField
183
+ margin="normal"
184
+ required
185
+ fullWidth
186
+ id="email"
187
+ label="Adresse email"
188
+ name="email"
189
+ autoComplete="email"
190
+ autoFocus
191
+ onChange={e => handleInputChange(e)}
192
+ value={userForm.email.value}
193
+ error={!userForm.email.isValid}
194
+ helperText={userForm.email.error}
195
+ />
196
+ <TextField
197
+ margin="normal"
198
+ required
199
+ fullWidth
200
+ name="password"
201
+ label="Mot de passe"
202
+ type="password"
203
+ id="password"
204
+ autoComplete="current-password"
205
+ onChange={e => handleInputChange(e)}
206
+ value={userForm.password.value}
207
+ error={!userForm.password.isValid}
208
+ helperText={userForm.password.error}
209
+ />
210
+ <GenderSelector handleSelectChange={handleSelectChange} form={userForm} required/>
211
+ <TextField
212
+ margin="normal"
213
+ required
214
+ label="Date de naissance"
215
+ type="date"
216
+ InputLabelProps={{
217
+ shrink: true,
218
+ }}
219
+ autoComplete="bday"
220
+ value={userForm.password.value}
221
+ error={!userForm.password.isValid}
222
+ helperText={userForm.password.error}
223
+ onChange={(e) => handleInputChange(e)}
224
+ />
225
+ <FormControl error={!userForm.acceptsTerms.isValid}>
226
+ <FormControlLabel
227
+ control={
228
+ <Checkbox
229
+ color="primary"
230
+ checked={userForm.acceptsTerms.value}
231
+ onChange={(e, checked) => handleCheckboxChange(e, checked)}
232
+ />
233
+ }
234
+ label={
235
+ <span>
236
+ J'accepte les{' '}
237
+ <Link href="/terms-and-conditions" target="_blank">
238
+ Conditions Générales d'Utilisation
239
+ </Link>
240
+ </span>
241
+ }
242
+ />
243
+ <FormHelperText>{userForm.acceptsTerms.error}</FormHelperText>
244
+ </FormControl>
245
+
246
+ {alertMessage && alertSeverity && <Alert severity={alertSeverity} sx={{ mb: 2 }}>{alertMessage}</Alert>}
247
+
248
+
249
+ <LoadingButton
250
+ loading={loading}
251
+ type="submit"
252
+ fullWidth
253
+ variant="contained"
254
+ sx={{ mt: 3, mb: 2 }}>
255
+ <span>Créer mon compte</span>
256
+ </LoadingButton>
257
+
258
+ </Box>
259
+ <MovaCopyright sx={{ mt: 8, mb: 4 }} />
260
+ </Container>
261
+
262
+ <img src={PinkLeafImage} style={{position: 'fixed',
263
+ float:'right',
264
+ width: '250px',
265
+ height: '400px',
266
+ bottom: '-20%',
267
+ right: '3%',
268
+ opacity: '0.3',
269
+ zIndex: '-10'}} alt='Feuille Rose Movalib'></img>
270
+ </div>
271
+ );
272
+ };
273
+
274
+ export default MovaLogin;
@@ -0,0 +1,65 @@
1
+ // MovaSnackbar.tsx
2
+ import React, { FunctionComponent } from 'react';
3
+ import { Snackbar, Alert, IconButton, SnackbarCloseReason } from '@mui/material';
4
+ import CloseIcon from '@mui/icons-material/CloseRounded';
5
+
6
+ interface MovaSnackbarProps {
7
+ snackbar: {
8
+ open: boolean;
9
+ message: string;
10
+ severity: 'success' | 'info' | 'warning' | 'error' | undefined;
11
+ };
12
+ setSnackbar: (snackbar: {
13
+ open: boolean;
14
+ message: string;
15
+ severity: 'success' | 'info' | 'warning' | 'error' | undefined;
16
+ }) => void;
17
+ }
18
+
19
+ const MovaSnackbar: FunctionComponent<MovaSnackbarProps> = ({ snackbar, setSnackbar }) => {
20
+
21
+ /**
22
+ * Gestion de la fermeture de la snackbar. Si la raison de cette fermeture est
23
+ * un clic en dehors de la zone de contenu on intercepte et annule la fermeture 'clickaway'.
24
+ * @param event Evénnement de fermeture
25
+ * @param reason Origine de l'événnement de fermeture
26
+ * @returns
27
+ */
28
+ const handleClose = (event: globalThis.Event | React.SyntheticEvent<any, globalThis.Event>,
29
+ reason: SnackbarCloseReason) => {
30
+
31
+ if (reason === 'clickaway') return;
32
+
33
+ setSnackbar({ open: false, message: '', severity: undefined });
34
+ };
35
+
36
+ const handleCloseAlert = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
37
+ setSnackbar({ open: false, message: '', severity: undefined });
38
+ };
39
+
40
+ return (
41
+ <Snackbar open={snackbar.open} autoHideDuration={5000} onClose={handleClose}>
42
+ <Alert severity={snackbar.severity} variant="filled" sx={{ width: '100%' }}
43
+ action={
44
+ <>
45
+ {/* TODO : possiblité d'annuler l'action qui vient d'avoir lieu */}
46
+ {/* <Button color="inherit" size="small" onClick={handleCancelAction}>
47
+ Annuler
48
+ </Button> */}
49
+ <IconButton
50
+ size="small"
51
+ aria-label="close"
52
+ color="inherit"
53
+ onClick={handleCloseAlert}
54
+ >
55
+ <CloseIcon fontSize="small" />
56
+ </IconButton>
57
+ </>
58
+ }>
59
+ {snackbar.message}
60
+ </Alert>
61
+ </Snackbar>
62
+ );
63
+ };
64
+
65
+ export default MovaSnackbar;
@@ -0,0 +1,12 @@
1
+ // src/Button.tsx
2
+ import React from 'react';
3
+
4
+ interface TestButtonProps {
5
+ label: string;
6
+ }
7
+
8
+ const TestButton: React.FC<TestButtonProps> = ({ label }) => {
9
+ return <button>{label}</button>;
10
+ };
11
+
12
+ export default TestButton;
@@ -0,0 +1,38 @@
1
+
2
+ export enum MovaAppType {
3
+ /**
4
+ * Application Garagiste
5
+ */
6
+ GARAGE = "GARAGE",
7
+ /**
8
+ * Application Utilisateur (automobiliste)
9
+ */
10
+ USER = "USER",
11
+ /**
12
+ * Application Administrateur (MovaTeam)
13
+ */
14
+ ADMIN = "ADMIN"
15
+ }
16
+
17
+ export enum RoleType {
18
+ /**
19
+ * Client du centre auto (automobiliste)
20
+ */
21
+ CUSTOMER = "CUSTOMER",
22
+ /**
23
+ * Technicien du centre auto
24
+ */
25
+ TECHNICIAN = "GARAGE_LEADER",
26
+ /**
27
+ * Chef d'atelier
28
+ */
29
+ GARAGE_LEADER = "GARAGE_LEADER",
30
+ /**
31
+ * Administrateur du centre auto (propriétaire ou dirigeant)
32
+ */
33
+ GARAGE_ADMIN = "ADMINISTRATOR",
34
+ /**
35
+ * Super-Administrateur (team Movalib)
36
+ */
37
+ MOVALIB_ADMIN = "MOVALIB_ADMIN",
38
+ }
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,20 @@
1
+ export type MovaUserSignUpForm = {
2
+ firstname: MovaFormField,
3
+ lastname: MovaFormField,
4
+ email: MovaFormField,
5
+ password: MovaFormField,
6
+ gender: MovaFormField,
7
+ birthDate: MovaFormField,
8
+ acceptsTerms: MovaFormField
9
+ }
10
+
11
+ export type MovaLoginForm = {
12
+ email: MovaFormField,
13
+ password: MovaFormField
14
+ }
15
+
16
+ export type MovaFormField = {
17
+ value?: any,
18
+ error?: string,
19
+ isValid?: boolean
20
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Un email valide
3
+ */
4
+ const emailRegex:RegExp = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
5
+
6
+ /**
7
+ * Un mot de passe qui contient au moins une majuscule, une minuscule, un chiffre
8
+ * et un caractère spécial. La longueur du mot de passe doit être d'au moins 8 caractères.
9
+ */
10
+ const passwordRegex:RegExp = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
11
+
12
+ /**
13
+ * Un numéro de téléphone (10 chiffres)
14
+ */
15
+ const phoneNumberRegex:RegExp = /^\d{0,10}$/;
16
+
17
+ /**
18
+ * Un code postal (5 chiffres)
19
+ */
20
+ const postalCodeRegex:RegExp = /^\d{5}$/;
21
+
22
+ /**
23
+ * Une URL valide
24
+ */
25
+ const urlRegex:RegExp =/^(ftp|http|https):\/\/[^ "]+$/;
26
+
27
+ /**
28
+ * Un nom pouvant contenir des lettres, chiffres d'une taille comprise entre 3 et 20 caractères
29
+ */
30
+ const userNameRegex:RegExp = /^[a-z0-9_-]{3,20}$/;
31
+
32
+ /**
33
+ * Un texte pouvant contenir des lettres, chiffres et quelques caractères spéciaux
34
+ */
35
+ const textRegex:RegExp = /^[a-zA-Z0-9\s.,!?'"()+=-_-éèçà]+$/;
36
+
37
+
38
+ export function validatePhoneNumber(phoneNumber: string) : boolean {
39
+ if (phoneNumber === null || phoneNumber === undefined) {
40
+ return false;
41
+ }
42
+ return phoneNumberRegex.test(phoneNumber);
43
+ }
44
+
45
+ export function validateText(text: string) : boolean {
46
+ if (text === null || text === undefined) {
47
+ return false;
48
+ }
49
+ return textRegex.test(text);
50
+ }
51
+
52
+ export function validateEmail(email: string) : boolean {
53
+ if (email === null || email === undefined) {
54
+ return false;
55
+ }
56
+ return emailRegex.test(email);
57
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ // src/index.ts
2
+
3
+ // Export des composants
4
+ export { default as TestButton } from './TestButton';
5
+ export { default as MovaSnackbar } from './MovaSnackbar';
6
+ export { default as MovaLogin } from './MovaLogin';
7
+ export { default as MovaCopyright } from './MovaCopyright';
8
+
9
+ // Export des classes
10
+ export { default as User } from './models/User';
11
+ export { default as Role } from './models/Role';
12
+ export { default as Address } from './models/Address';
13
+
14
+ // Export des types
15
+ export type { MovaFormField, MovaLoginForm, MovaUserSignUpForm } from './helpers/Types';
16
+
17
+
18
+ // Export des enums
19
+ export { RoleType, MovaAppType } from './helpers/Enums';
@@ -0,0 +1,28 @@
1
+ export default class Address {
2
+
3
+ id: string; // UUID
4
+ streetName: string; // Street number & name
5
+ postalCode: string; // Postal code
6
+ cityName: string; // City name
7
+ country: string; // Country name
8
+ countryCode: string; // Country ISO code
9
+ additional?: string; // Additional address information (optional)
10
+
11
+ constructor(
12
+ id: string,
13
+ streetName: string,
14
+ postalCode: string,
15
+ cityName: string,
16
+ country: string,
17
+ countryCode: string,
18
+ additional?: string
19
+ ) {
20
+ this.id = id;
21
+ this.streetName = streetName;
22
+ this.postalCode = postalCode;
23
+ this.cityName = cityName;
24
+ this.country = country;
25
+ this.countryCode = countryCode;
26
+ this.additional = additional;
27
+ }
28
+ }
@@ -0,0 +1,16 @@
1
+ export default class Role {
2
+
3
+ id:string;
4
+ isActive: boolean;
5
+ authorizations: string[];
6
+
7
+ constructor(
8
+ id:string,
9
+ isActive: boolean = true,
10
+ authorizations: string[]
11
+ ) {
12
+ this.id = id;
13
+ this.isActive = isActive;
14
+ this.authorizations = authorizations;
15
+ }
16
+ }
@@ -0,0 +1,59 @@
1
+ import { RoleType } from "../helpers/Enums";
2
+ import Address from './Address';
3
+
4
+ export default class User {
5
+
6
+ // Properties
7
+ id: string;
8
+ roles: string[];
9
+ firstname: string;
10
+ lastname: string;
11
+ avatar: string;
12
+ email: string;
13
+ password: string;
14
+ created: Date;
15
+ addresses? : Address[]
16
+ phoneNumber?: string;
17
+
18
+ constructor(
19
+ id: string,
20
+ roles: string[],
21
+ firstname: string = '',
22
+ lastname: string = '',
23
+ avatar: string = '',
24
+ email: string = '',
25
+ password: string = '',
26
+ created: Date = new Date(),
27
+ addresses?: Address[],
28
+ phoneNumber?: string
29
+ ) {
30
+ this.id = id;
31
+ this.roles = roles;
32
+ this.firstname = firstname;
33
+ this.lastname = lastname;
34
+ this.avatar = avatar;
35
+ this.email = email;
36
+ this.password = password;
37
+ this.created = created;
38
+ this.addresses = addresses;
39
+ this.phoneNumber = phoneNumber;
40
+ }
41
+
42
+ static getFirstLetter = (user: User) : string => {
43
+
44
+ const firstLetter = user.lastname[0].toUpperCase();
45
+ return /[0-9]/.test(firstLetter) ? '0-9' : firstLetter;
46
+ }
47
+
48
+ static isCustomer = (user: User) : boolean => {
49
+ if(!user || !user.roles) return false;
50
+
51
+ return Boolean(user.roles.find(r => r = RoleType.CUSTOMER));
52
+ }
53
+
54
+ static isTechnician = (user: User) : boolean => {
55
+ if(!user || !user.roles) return false;
56
+
57
+ return Boolean(user.roles.find(r => r = RoleType.TECHNICIAN));
58
+ }
59
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="react-scripts" />