test-tp1-ynov-react-kleas17 1.0.1
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.
- package/README.md +105 -0
- package/dist/App.css +196 -0
- package/dist/App.js +232 -0
- package/dist/App.test.js +298 -0
- package/dist/__snapshots__/App.test.js.snap +38 -0
- package/dist/api.js +48 -0
- package/dist/api.test.js +128 -0
- package/dist/index.css +13 -0
- package/dist/index.js +9 -0
- package/dist/logo.svg +1 -0
- package/dist/reportWebVitals.js +18 -0
- package/dist/setupTests.js +5 -0
- package/dist/validator.js +132 -0
- package/dist/validator.test.js +152 -0
- package/package.json +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Application d'inscription SPA
|
|
2
|
+
|
|
3
|
+
## Fonctionnalités
|
|
4
|
+
|
|
5
|
+
- Navigation SPA avec `react-router-dom`
|
|
6
|
+
- `/` : accueil, compteur des inscrits, liste des inscrits (Nom + Prénom)
|
|
7
|
+
- `/register` : formulaire d'inscription
|
|
8
|
+
- État partagé au niveau racine (tableau des utilisateurs)
|
|
9
|
+
- Données chargées/sauvegardées via API (`/users`) avec `axios`
|
|
10
|
+
- Résilience UI face aux erreurs backend :
|
|
11
|
+
- `400` : affichage du message métier renvoyé par le serveur
|
|
12
|
+
- `500` : affichage d'une alerte utilisateur sans crash de l'application
|
|
13
|
+
|
|
14
|
+
## Architecture API
|
|
15
|
+
|
|
16
|
+
- Fichier dédié : `src/api.js`
|
|
17
|
+
- API par défaut : `https://jsonplaceholder.typicode.com`
|
|
18
|
+
- Variables d'environnement prises en charge :
|
|
19
|
+
- `REACT_APP_API_URL`
|
|
20
|
+
- `REACT_APP_SERVER_PORT`
|
|
21
|
+
- `REACT_APP_API_TOKEN` (header `Authorization: Bearer ...`)
|
|
22
|
+
|
|
23
|
+
## Pyramide de tests
|
|
24
|
+
|
|
25
|
+
- UT : logique pure dans `src/validator.js`
|
|
26
|
+
- IT : interactions UI + rendu + navigation dans `src/App.test.js`
|
|
27
|
+
- IT API : appels réseau mockés dans `src/api.test.js` avec `jest.mock('axios')`
|
|
28
|
+
- E2E : parcours multi-vues avec `cy.intercept` dans `cypress/e2e/navigation.cy.js`
|
|
29
|
+
|
|
30
|
+
## Couverture des cas (activité 5)
|
|
31
|
+
|
|
32
|
+
- Succès (`200/201`)
|
|
33
|
+
- Erreur métier (`400`) avec message backend visible
|
|
34
|
+
- Erreur serveur (`500`) avec message utilisateur de résilience
|
|
35
|
+
- Aucun appel réseau réel dans Jest (axios entièrement mocké)
|
|
36
|
+
|
|
37
|
+
## Commandes
|
|
38
|
+
|
|
39
|
+
Dans `my-app` :
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install
|
|
43
|
+
npm start
|
|
44
|
+
npm test
|
|
45
|
+
npm run cypress:run
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## CI/CD
|
|
49
|
+
|
|
50
|
+
Le workflow GitHub Actions exécute :
|
|
51
|
+
|
|
52
|
+
1. tests Jest
|
|
53
|
+
2. tests E2E Cypress
|
|
54
|
+
3. build React
|
|
55
|
+
4. publication GitHub Pages
|
|
56
|
+
|
|
57
|
+
Le workflow injecte :
|
|
58
|
+
|
|
59
|
+
- `REACT_APP_API_URL=https://jsonplaceholder.typicode.com`
|
|
60
|
+
- `REACT_APP_API_TOKEN=${{ secrets.JSONPLACEHOLDER_TOKEN }}`
|
|
61
|
+
|
|
62
|
+
## Publication npm et SemVer
|
|
63
|
+
|
|
64
|
+
Le package est configuré pour la publication npm avec :
|
|
65
|
+
|
|
66
|
+
- `name: test-tp1-ynov-react-kleas17`
|
|
67
|
+
- `version` au format SemVer `MAJOR.MINOR.PATCH`
|
|
68
|
+
- `main: dist/index.js`
|
|
69
|
+
- `files: ["dist"]`
|
|
70
|
+
|
|
71
|
+
Commandes de versioning prises en charge :
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm version major
|
|
75
|
+
npm version minor
|
|
76
|
+
npm version patch
|
|
77
|
+
npm version prerelease
|
|
78
|
+
npm version premajor
|
|
79
|
+
npm version preminor
|
|
80
|
+
npm version prepatch
|
|
81
|
+
npm version patch -m "Upgrade to %s"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Pré-releases supportées :
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm run version:alpha # x.y.z-alpha.n
|
|
88
|
+
npm run version:beta # x.y.z-beta.n
|
|
89
|
+
npm run version:rc # x.y.z-rc.n
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Validation manuelle de publication :
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm run build-npm
|
|
96
|
+
npm adduser
|
|
97
|
+
npm publish
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Sécurité :
|
|
101
|
+
|
|
102
|
+
- Créer un token npm de type `Automation`.
|
|
103
|
+
- Ajouter `NPM_TOKEN` dans `Settings > Secrets and variables > Actions`.
|
|
104
|
+
- Ne jamais commiter un `.npmrc` contenant un token.
|
|
105
|
+
- Ne jamais republier une version existante : incrémenter systématiquement la version avant publication.
|
package/dist/App.css
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
.App {
|
|
2
|
+
background: linear-gradient(140deg, #062e72 0%, #16509f 45%, #fff6ec 100%);
|
|
3
|
+
min-height: 100vh;
|
|
4
|
+
padding: 2.25rem 1rem;
|
|
5
|
+
display: flex;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.form-container {
|
|
10
|
+
background: #ffffff;
|
|
11
|
+
border-radius: 14px;
|
|
12
|
+
width: min(100%, 820px);
|
|
13
|
+
padding: 1.5rem 1.4rem;
|
|
14
|
+
box-shadow: 0 16px 30px rgba(14, 42, 78, 0.14);
|
|
15
|
+
border: 1px solid #dbe8ff;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
h1 {
|
|
19
|
+
margin: 0 0 1.15rem;
|
|
20
|
+
font-size: 1.5rem;
|
|
21
|
+
color: #0f3f75;
|
|
22
|
+
letter-spacing: 0.01em;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.registered-list {
|
|
26
|
+
margin: 0.6rem 0 1rem;
|
|
27
|
+
padding-left: 1.15rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.registered-item {
|
|
31
|
+
margin-bottom: 0.3rem;
|
|
32
|
+
color: #204b7f;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.readme-link {
|
|
37
|
+
margin: -0.3rem 0 1rem;
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
form {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
44
|
+
column-gap: 0.95rem;
|
|
45
|
+
row-gap: 0.25rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.field-group:nth-child(3),
|
|
49
|
+
.field-group:nth-child(4),
|
|
50
|
+
.field-group:nth-child(5),
|
|
51
|
+
.field-group:nth-child(6) {
|
|
52
|
+
margin-top: 0.2rem;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.field-group {
|
|
56
|
+
display: grid;
|
|
57
|
+
gap: 0.35rem;
|
|
58
|
+
margin-bottom: 0.6rem;
|
|
59
|
+
padding: 0.75rem;
|
|
60
|
+
border: 1px solid #e1ebfb;
|
|
61
|
+
border-radius: 10px;
|
|
62
|
+
background: #f7faff;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
label {
|
|
66
|
+
font-size: 0.88rem;
|
|
67
|
+
font-weight: 600;
|
|
68
|
+
color: #255a96;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
input {
|
|
72
|
+
padding: 0.58rem 0.68rem;
|
|
73
|
+
border: 1px solid #c8dbf9;
|
|
74
|
+
border-radius: 8px;
|
|
75
|
+
font-size: 1rem;
|
|
76
|
+
background: #ffffff;
|
|
77
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
input:focus {
|
|
81
|
+
outline: none;
|
|
82
|
+
border-color: #02448d;
|
|
83
|
+
box-shadow: 0 0 0 3px rgba(47, 115, 190, 0.18);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
input[aria-invalid="true"] {
|
|
87
|
+
border-color: #e65d1f;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.error-text {
|
|
91
|
+
color: #cf4f15;
|
|
92
|
+
font-size: 0.85rem;
|
|
93
|
+
margin: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.submit-button {
|
|
97
|
+
width: 100%;
|
|
98
|
+
grid-column: 1 / -1;
|
|
99
|
+
margin-top: 0.35rem;
|
|
100
|
+
padding: 0.7rem 1rem;
|
|
101
|
+
border: none;
|
|
102
|
+
border-radius: 8px;
|
|
103
|
+
background: #03448d;
|
|
104
|
+
color: #ffffff;
|
|
105
|
+
font-size: 1rem;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
transition: background-color 0.2s ease, transform 0.12s ease, box-shadow 0.2s ease;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.submit-button.disabled {
|
|
112
|
+
background: #f4a76e;
|
|
113
|
+
cursor: not-allowed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.submit-button:not(.disabled):hover {
|
|
117
|
+
background: #02376f;
|
|
118
|
+
transform: translateY(-1px);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.submit-button:not(.disabled):focus-visible {
|
|
122
|
+
outline: none;
|
|
123
|
+
box-shadow: 0 0 0 3px rgba(47, 115, 190, 0.22);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.action-button {
|
|
127
|
+
display: inline-block;
|
|
128
|
+
text-decoration: none;
|
|
129
|
+
text-align: center;
|
|
130
|
+
border: none;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
padding: 0.7rem 1rem;
|
|
133
|
+
font-size: 1rem;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
transition: background-color 0.2s ease, transform 0.12s ease, box-shadow 0.2s ease;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.action-button:focus-visible {
|
|
140
|
+
outline: none;
|
|
141
|
+
box-shadow: 0 0 0 3px rgba(47, 115, 190, 0.22);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.primary-action {
|
|
145
|
+
background: #03448d;
|
|
146
|
+
color: #ffffff;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.primary-action:hover {
|
|
150
|
+
background: #02376f;
|
|
151
|
+
transform: translateY(-1px);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.secondary-action {
|
|
155
|
+
grid-column: 1 / -1;
|
|
156
|
+
margin-top: 0.45rem;
|
|
157
|
+
background: #ecf3ff;
|
|
158
|
+
color: #0f3f75;
|
|
159
|
+
border: 1px solid #c6daf8;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.secondary-action:hover {
|
|
163
|
+
background: #dfeeff;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.link-button {
|
|
167
|
+
box-sizing: border-box;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.toast {
|
|
171
|
+
margin-top: 0.95rem;
|
|
172
|
+
background: #fef3e9;
|
|
173
|
+
color: #9b3f11;
|
|
174
|
+
border: 1px solid #f6c7a4;
|
|
175
|
+
border-radius: 8px;
|
|
176
|
+
padding: 0.65rem 0.75rem;
|
|
177
|
+
font-size: 0.95rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@media (max-width: 500px) {
|
|
181
|
+
form {
|
|
182
|
+
grid-template-columns: 1fr;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.form-container {
|
|
186
|
+
padding: 1rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.submit-button {
|
|
190
|
+
margin-top: 0.2rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.secondary-action {
|
|
194
|
+
margin-top: 0.3rem;
|
|
195
|
+
}
|
|
196
|
+
}
|
package/dist/App.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import './App.css';
|
|
2
|
+
import { BrowserRouter, Link, Navigate, Route, Routes, useNavigate } from 'react-router-dom';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { ValidationError, validateAge, validateEmail, validateIdentity, validatePostalCode, validateUniqueEmail } from './validator';
|
|
5
|
+
import { createRegistration, getRegistrations } from './api';
|
|
6
|
+
const initialValues = {
|
|
7
|
+
nom: '',
|
|
8
|
+
prenom: '',
|
|
9
|
+
email: '',
|
|
10
|
+
dateNaissance: '',
|
|
11
|
+
cp: '',
|
|
12
|
+
ville: ''
|
|
13
|
+
};
|
|
14
|
+
const fieldLabels = {
|
|
15
|
+
nom: 'Nom',
|
|
16
|
+
prenom: 'Prénom',
|
|
17
|
+
email: 'Email',
|
|
18
|
+
dateNaissance: 'Date de naissance',
|
|
19
|
+
cp: 'Code postal',
|
|
20
|
+
ville: 'Ville'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Runs all form validations and returns an object keyed by field name.
|
|
25
|
+
* @param {{nom:string, prenom:string, email:string, dateNaissance:string, cp:string, ville:string}} values
|
|
26
|
+
* @param {Array<{email:string}>} users
|
|
27
|
+
* @returns {Record<string, string>} Field-level validation errors.
|
|
28
|
+
*/
|
|
29
|
+
function validateForm(values, users) {
|
|
30
|
+
const nextErrors = {};
|
|
31
|
+
const runValidation = (field, validator) => {
|
|
32
|
+
try {
|
|
33
|
+
validator();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error instanceof ValidationError) {
|
|
36
|
+
nextErrors[field] = error.message;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
nextErrors[field] = 'Erreur de validation';
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
runValidation('nom', () => validateIdentity(values.nom.trim()));
|
|
43
|
+
runValidation('prenom', () => validateIdentity(values.prenom.trim()));
|
|
44
|
+
runValidation('ville', () => validateIdentity(values.ville.trim()));
|
|
45
|
+
runValidation('email', () => validateEmail(values.email.trim()));
|
|
46
|
+
if (!nextErrors.email) {
|
|
47
|
+
runValidation('email', () => validateUniqueEmail(values.email, users));
|
|
48
|
+
}
|
|
49
|
+
runValidation('cp', () => validatePostalCode(values.cp.trim()));
|
|
50
|
+
runValidation('dateNaissance', () => validateAge(new Date(values.dateNaissance)));
|
|
51
|
+
return nextErrors;
|
|
52
|
+
}
|
|
53
|
+
function HomePage({
|
|
54
|
+
users,
|
|
55
|
+
onStartRegistration
|
|
56
|
+
}) {
|
|
57
|
+
return /*#__PURE__*/React.createElement("section", {
|
|
58
|
+
"data-cy": "home-page"
|
|
59
|
+
}, /*#__PURE__*/React.createElement("h1", null, "Bienvenue sur l'application d'inscription"), /*#__PURE__*/React.createElement("p", {
|
|
60
|
+
"data-cy": "registered-count"
|
|
61
|
+
}, users.length, " utilisateur(s) inscrit(s)"), users.length === 0 ? /*#__PURE__*/React.createElement("p", {
|
|
62
|
+
"data-cy": "empty-list"
|
|
63
|
+
}, "Aucun utilisateur inscrit pour le moment.") : /*#__PURE__*/React.createElement("ul", {
|
|
64
|
+
"data-cy": "registered-list",
|
|
65
|
+
className: "registered-list"
|
|
66
|
+
}, users.map((user, index) => /*#__PURE__*/React.createElement("li", {
|
|
67
|
+
key: `${user.email}-${index}`,
|
|
68
|
+
"data-cy": "registered-user",
|
|
69
|
+
className: "registered-item"
|
|
70
|
+
}, user.nom, " ", user.prenom))), /*#__PURE__*/React.createElement(Link, {
|
|
71
|
+
to: "/register",
|
|
72
|
+
"data-cy": "go-to-register",
|
|
73
|
+
className: "action-button primary-action link-button",
|
|
74
|
+
onClick: onStartRegistration
|
|
75
|
+
}, "Ajouter un utilisateur"));
|
|
76
|
+
}
|
|
77
|
+
function RegisterPage({
|
|
78
|
+
users,
|
|
79
|
+
onRegister
|
|
80
|
+
}) {
|
|
81
|
+
const navigate = useNavigate();
|
|
82
|
+
const [values, setValues] = useState(initialValues);
|
|
83
|
+
const [touched, setTouched] = useState({});
|
|
84
|
+
const errors = useMemo(() => validateForm(values, users), [values, users]);
|
|
85
|
+
const isValid = Object.keys(errors).length === 0;
|
|
86
|
+
const onChange = event => {
|
|
87
|
+
const {
|
|
88
|
+
name,
|
|
89
|
+
value
|
|
90
|
+
} = event.target;
|
|
91
|
+
setValues(previous => ({
|
|
92
|
+
...previous,
|
|
93
|
+
[name]: value
|
|
94
|
+
}));
|
|
95
|
+
};
|
|
96
|
+
const onBlur = event => {
|
|
97
|
+
const {
|
|
98
|
+
name
|
|
99
|
+
} = event.target;
|
|
100
|
+
setTouched(previous => ({
|
|
101
|
+
...previous,
|
|
102
|
+
[name]: true
|
|
103
|
+
}));
|
|
104
|
+
};
|
|
105
|
+
const onSubmit = async event => {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
if (!isValid) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const registrationSucceeded = await onRegister(values);
|
|
111
|
+
if (!registrationSucceeded) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
setValues(initialValues);
|
|
115
|
+
setTouched({});
|
|
116
|
+
navigate('/');
|
|
117
|
+
};
|
|
118
|
+
const shouldShowError = fieldName => Boolean(touched[fieldName] || values[fieldName]);
|
|
119
|
+
return /*#__PURE__*/React.createElement("section", {
|
|
120
|
+
"data-cy": "register-page"
|
|
121
|
+
}, /*#__PURE__*/React.createElement("h1", null, "Formulaire utilisateur"), /*#__PURE__*/React.createElement("form", {
|
|
122
|
+
onSubmit: onSubmit,
|
|
123
|
+
noValidate: true
|
|
124
|
+
}, Object.keys(initialValues).map(fieldName => /*#__PURE__*/React.createElement("div", {
|
|
125
|
+
key: fieldName,
|
|
126
|
+
className: "field-group"
|
|
127
|
+
}, /*#__PURE__*/React.createElement("label", {
|
|
128
|
+
htmlFor: fieldName
|
|
129
|
+
}, fieldLabels[fieldName]), /*#__PURE__*/React.createElement("input", {
|
|
130
|
+
id: fieldName,
|
|
131
|
+
name: fieldName,
|
|
132
|
+
type: fieldName === 'dateNaissance' ? 'date' : 'text',
|
|
133
|
+
value: values[fieldName],
|
|
134
|
+
onChange: onChange,
|
|
135
|
+
onBlur: onBlur,
|
|
136
|
+
"aria-invalid": Boolean(errors[fieldName]),
|
|
137
|
+
"aria-describedby": `${fieldName}-error`,
|
|
138
|
+
"data-cy": fieldName
|
|
139
|
+
}), shouldShowError(fieldName) && errors[fieldName] ? /*#__PURE__*/React.createElement("p", {
|
|
140
|
+
id: `${fieldName}-error`,
|
|
141
|
+
role: "alert",
|
|
142
|
+
className: "error-text"
|
|
143
|
+
}, errors[fieldName]) : null)), /*#__PURE__*/React.createElement("button", {
|
|
144
|
+
type: "submit",
|
|
145
|
+
disabled: !isValid,
|
|
146
|
+
className: `submit-button ${!isValid ? 'disabled' : ''}`,
|
|
147
|
+
"data-cy": "submit"
|
|
148
|
+
}, "Soumettre"), /*#__PURE__*/React.createElement(Link, {
|
|
149
|
+
to: "/",
|
|
150
|
+
"data-cy": "go-home",
|
|
151
|
+
className: "action-button secondary-action link-button"
|
|
152
|
+
}, "Retour \xE0 l'accueil")));
|
|
153
|
+
}
|
|
154
|
+
function App() {
|
|
155
|
+
const [users, setUsers] = useState([]);
|
|
156
|
+
const [toastMessage, setToastMessage] = useState('');
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
let isMounted = true;
|
|
159
|
+
const loadRegistrations = async () => {
|
|
160
|
+
try {
|
|
161
|
+
const loadedUsers = await getRegistrations();
|
|
162
|
+
if (isMounted) {
|
|
163
|
+
setUsers(loadedUsers);
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
if (isMounted) {
|
|
167
|
+
setUsers([]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
loadRegistrations();
|
|
172
|
+
return () => {
|
|
173
|
+
isMounted = false;
|
|
174
|
+
};
|
|
175
|
+
}, []);
|
|
176
|
+
const onRegister = async newUser => {
|
|
177
|
+
try {
|
|
178
|
+
await createRegistration(newUser);
|
|
179
|
+
setUsers(previousUsers => [...previousUsers, newUser]);
|
|
180
|
+
setToastMessage('Inscription enregistrée');
|
|
181
|
+
return true;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
const statusCode = error?.response?.status;
|
|
184
|
+
const backendMessage = error?.response?.data?.message;
|
|
185
|
+
if (typeof statusCode === 'number' && statusCode >= 400 && statusCode < 500 && backendMessage) {
|
|
186
|
+
setToastMessage(backendMessage);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (typeof statusCode === 'number' && statusCode >= 500) {
|
|
190
|
+
setToastMessage('Serveur indisponible, veuillez réessayer plus tard.');
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
setToastMessage("Erreur lors de l'inscription");
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
return /*#__PURE__*/React.createElement(BrowserRouter, {
|
|
198
|
+
basename: process.env.PUBLIC_URL,
|
|
199
|
+
future: {
|
|
200
|
+
v7_startTransition: true,
|
|
201
|
+
v7_relativeSplatPath: true
|
|
202
|
+
}
|
|
203
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
204
|
+
className: "App"
|
|
205
|
+
}, /*#__PURE__*/React.createElement("main", {
|
|
206
|
+
className: "form-container"
|
|
207
|
+
}, /*#__PURE__*/React.createElement(Routes, null, /*#__PURE__*/React.createElement(Route, {
|
|
208
|
+
path: "/",
|
|
209
|
+
element: /*#__PURE__*/React.createElement(HomePage, {
|
|
210
|
+
users: users,
|
|
211
|
+
onStartRegistration: () => setToastMessage('')
|
|
212
|
+
})
|
|
213
|
+
}), /*#__PURE__*/React.createElement(Route, {
|
|
214
|
+
path: "/register",
|
|
215
|
+
element: /*#__PURE__*/React.createElement(RegisterPage, {
|
|
216
|
+
users: users,
|
|
217
|
+
onRegister: onRegister
|
|
218
|
+
})
|
|
219
|
+
}), /*#__PURE__*/React.createElement(Route, {
|
|
220
|
+
path: "*",
|
|
221
|
+
element: /*#__PURE__*/React.createElement(Navigate, {
|
|
222
|
+
to: "/",
|
|
223
|
+
replace: true
|
|
224
|
+
})
|
|
225
|
+
})), toastMessage ? /*#__PURE__*/React.createElement("div", {
|
|
226
|
+
className: "toast",
|
|
227
|
+
role: "status",
|
|
228
|
+
"aria-live": "polite",
|
|
229
|
+
"data-cy": "success"
|
|
230
|
+
}, toastMessage) : null)));
|
|
231
|
+
}
|
|
232
|
+
export default App;
|