@movalib/movalib-commons 1.0.66 → 1.0.68

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 (44) hide show
  1. package/.env.development +6 -0
  2. package/.env.production +4 -0
  3. package/devIndex.tsx +127 -12
  4. package/dist/devIndex.js +104 -8
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.js +5 -1
  7. package/dist/src/MovaLogin.js +11 -2
  8. package/dist/src/MovaSignUp.d.ts +1 -1
  9. package/dist/src/MovaSignUp.js +42 -10
  10. package/dist/src/QRCode.d.ts +1 -0
  11. package/dist/src/QRCode.js +13 -12
  12. package/dist/src/ScheduleFields.d.ts +24 -0
  13. package/dist/src/ScheduleFields.js +202 -0
  14. package/dist/src/helpers/ApiHelper.d.ts +20 -0
  15. package/dist/src/helpers/ApiHelper.js +74 -0
  16. package/dist/src/helpers/CookieUtils.d.ts +6 -0
  17. package/dist/src/helpers/CookieUtils.js +28 -0
  18. package/dist/src/helpers/Enums.d.ts +8 -1
  19. package/dist/src/helpers/Enums.js +10 -2
  20. package/dist/src/helpers/Tools.d.ts +6 -2
  21. package/dist/src/helpers/Tools.js +20 -7
  22. package/dist/src/services/AuthenticationService.d.ts +6 -0
  23. package/dist/src/services/AuthenticationService.js +106 -0
  24. package/dist/src/services/GarageService.d.ts +5 -0
  25. package/dist/src/services/GarageService.js +16 -0
  26. package/dist/src/services/UserService.d.ts +10 -0
  27. package/dist/src/services/UserService.js +76 -0
  28. package/index.ts +3 -1
  29. package/package.json +9 -7
  30. package/public/index.html +1 -1
  31. package/src/MovaLogin.tsx +24 -5
  32. package/src/MovaSignUp.tsx +71 -15
  33. package/src/QRCode.css +4 -0
  34. package/src/QRCode.tsx +12 -13
  35. package/src/ScheduleFields.tsx +301 -0
  36. package/src/helpers/ApiHelper.ts +95 -0
  37. package/src/helpers/CookieUtils.ts +23 -0
  38. package/src/helpers/Enums.ts +8 -1
  39. package/src/helpers/Tools.ts +20 -2
  40. package/src/services/AuthenticationService.ts +68 -0
  41. package/src/services/GarageService.ts +14 -0
  42. package/src/services/UserService.ts +25 -0
  43. package/tsconfig.json +1 -1
  44. package/webpack.config.js +9 -0
@@ -0,0 +1,301 @@
1
+ import * as React from 'react';
2
+ import { FunctionComponent, useEffect, useState } from 'react';
3
+ import { Checkbox, FormControlLabel, Grid, IconButton, FormHelperText, FormControl, RadioGroup, Radio } from '@mui/material';
4
+ import AddIcon from '@mui/icons-material/AddRounded';
5
+ import DeleteIcon from '@mui/icons-material/DeleteRounded';
6
+ import { TimePicker } from '@mui/x-date-pickers';
7
+ import theme from '../theme'; // Import du thème personnalisé
8
+ import Schedule from './models/Schedule';
9
+ import { DayOfWeek } from './helpers/Enums';
10
+ import Logger from './helpers/Logger';
11
+ import { flexCenter } from './helpers/Tools';
12
+
13
+ export const FR_WEEK_DAYS: string[] = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'];
14
+
15
+ type DayInterval = {
16
+ startTime: Date | '' | null,
17
+ endTime: Date | '' | null,
18
+ countryCode: string,
19
+ error: string | null
20
+ }
21
+
22
+ export type DaySchedule = {
23
+ day: DayOfWeek,
24
+ checked: boolean,
25
+ allDay: boolean,
26
+ intervals: DayInterval[],
27
+ };
28
+
29
+ const initialSchedules: DaySchedule[] = [
30
+ {day: DayOfWeek.MONDAY, checked: false, allDay: false, intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
31
+ {day: DayOfWeek.TUESDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
32
+ {day: DayOfWeek.WEDNESDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
33
+ {day: DayOfWeek.THURSDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
34
+ {day: DayOfWeek.FRIDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
35
+ {day: DayOfWeek.SATURDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
36
+ {day: DayOfWeek.SUNDAY, checked: false, allDay: false,intervals: [{startTime: null, endTime: null, countryCode: 'fr', error: null}]},
37
+ ];
38
+
39
+ interface ScheduleFieldsProps {
40
+ timePickerStep: number,
41
+ size: 'small' | 'medium',
42
+ onChange: (schedules: DaySchedule[]) => void,
43
+ schedules?: Schedule[] | undefined;
44
+ }
45
+
46
+ const ScheduleFields: FunctionComponent<ScheduleFieldsProps> = ({ timePickerStep, schedules, size, onChange }) => {
47
+
48
+ const mergeSchedules = (apiSchedules: Schedule[]): DaySchedule[] => {
49
+ // Créez une copie profonde du tableau initialSchedules pour éviter toute mutation accidentelle
50
+ const mergedSchedules = JSON.parse(JSON.stringify(initialSchedules));
51
+
52
+ // Parcourez chaque élément de apiSchedules et mettez à jour updatedSchedules
53
+ const today = new Date().toISOString().substring(0, 10);
54
+
55
+ for (const apiSchedule of apiSchedules) {
56
+ const dayIndex = mergedSchedules.findIndex((schedule: { day: DayOfWeek; }) => schedule.day === apiSchedule.dayOfWeek);
57
+ if (dayIndex !== -1) {
58
+ mergedSchedules[dayIndex].checked = apiSchedule.intervals.length > 0;
59
+ mergedSchedules[dayIndex].intervals = apiSchedule.intervals.map(interval => ({
60
+ ...interval,
61
+ // On injecte une date fictive même si nous n'utilisons que l'heure, le Timepicker attend une Date JS Valide
62
+ startTime: new Date(`${today}T${interval.startTime!}`),
63
+ endTime: new Date(`${today}T${interval.endTime!}`),
64
+ error: null, // Réinitialisez l'erreur car les données sont maintenant mises à jour
65
+ }));
66
+ mergedSchedules[dayIndex].allDay = apiSchedule.isAllWorkingDay;
67
+ }
68
+ }
69
+
70
+ Logger.info(mergedSchedules);
71
+
72
+ return mergedSchedules;
73
+ }
74
+
75
+ /**
76
+ * Cette fonction renvoie une liste de DaySchedule depuis une liste de Schedule
77
+ * @returns
78
+ */
79
+ const initializeFromSchedules = () => {
80
+
81
+ if (schedules && schedules.length > 0 ){
82
+
83
+ return schedules.map(s => {
84
+ const iList: DayInterval[] = s.intervals.map(i => ({
85
+ startTime: new Date(i.startTime!),
86
+ endTime: new Date(i.endTime!),
87
+ countryCode: i.countryCode,
88
+ error: null,
89
+ }));
90
+
91
+ return {
92
+ day: s.dayOfWeek,
93
+ checked: iList.length > 0, // le checked est à true si il y a au moins un interval de saisi
94
+ intervals: iList,
95
+ };
96
+ });
97
+ }
98
+ return [];
99
+ }
100
+
101
+ // Si des schedules sont transmises on initialise un nouveau tableau de DaySchedule[]
102
+ const [schedule, setSchedule] = useState<DaySchedule[]>(initialSchedules);
103
+
104
+ // Chargement initial des données du garage
105
+ useEffect(() => {
106
+
107
+ if(schedules && schedules.length > 0){
108
+ setSchedule(mergeSchedules(schedules));
109
+ }
110
+
111
+ }, [schedules]); // Exécute le hook à chaque fois que les schedules transmis en props changent
112
+
113
+ const handleDayChecked = (dayIndex: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
114
+ const newSchedule = [...schedule];
115
+ newSchedule[dayIndex].checked = event.target.checked;
116
+ // On réinitialise aussi les intervals
117
+ newSchedule[dayIndex].intervals = [{startTime: null, endTime: null, countryCode: 'fr', error: null}];
118
+ onChange(newSchedule);
119
+ setSchedule(newSchedule);
120
+ };
121
+
122
+ const handleAllDayChecked = (e: React.ChangeEvent<HTMLInputElement>, value: string, dayIndex: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
123
+ e.preventDefault();
124
+
125
+ const newSchedule = [...schedule];
126
+ newSchedule[dayIndex].allDay = event.target.checked;
127
+ onChange(newSchedule);
128
+ setSchedule(newSchedule);
129
+ };
130
+
131
+ const validateInterval = (start: Date | '', end: Date | ''): string | null => {
132
+ if (start instanceof Date && end instanceof Date && start.getTime() > end.getTime()) {
133
+ return "L'heure de début doit être antérieure à l'heure de fin";
134
+ }
135
+ return null;
136
+ };
137
+
138
+ const validateIntervals = (intervals: DayInterval[]): string | null => {
139
+
140
+ // On tri les intervals par date de début
141
+ const sortedIntervals = [...intervals].sort((a, b) =>
142
+ a.startTime && b.startTime ? a.startTime.toISOString().localeCompare(b.startTime.toISOString()) : 0);
143
+
144
+ console.log(sortedIntervals)
145
+ for (let i = 0; i < sortedIntervals.length; i++) {
146
+ if (
147
+ sortedIntervals[i].startTime?.toString().localeCompare(sortedIntervals[i].endTime?.toString()!)! >= 0 ||
148
+ (i < sortedIntervals.length - 1 &&
149
+ sortedIntervals[i].endTime?.toString().localeCompare(sortedIntervals[i + 1].startTime?.toString()!)! > 0)
150
+ ) {
151
+ return 'Attention vos intervales se chevauchent'; // start time is after or equal to end time, or end time of current interval is after start time of next interval
152
+ }
153
+ }
154
+ return null; // all intervals are valid and there are no overlapping intervals
155
+ }
156
+
157
+
158
+ const handleIntervalChange = (dayIndex: number, intervalIndex: number, type: 'startTime' | 'endTime') =>
159
+ (newValue: "" | Date | null) => {
160
+
161
+ const newSchedule = [...schedule];
162
+
163
+ newSchedule[dayIndex].intervals[intervalIndex][type] = newValue!;
164
+
165
+ newSchedule[dayIndex].intervals[intervalIndex].error = validateInterval(
166
+ newSchedule[dayIndex].intervals[intervalIndex].startTime!,
167
+ newSchedule[dayIndex].intervals[intervalIndex].endTime!
168
+ );
169
+
170
+ //Contrôle le chevauchement d'intervales
171
+ if(type === 'endTime' && intervalIndex > 0 && !newSchedule[dayIndex].intervals[intervalIndex].error){
172
+ console.log(dayIndex);
173
+ console.log(newSchedule[dayIndex].intervals);
174
+ newSchedule[dayIndex].intervals[intervalIndex].error = validateIntervals(
175
+ newSchedule[dayIndex].intervals);
176
+ }
177
+
178
+ setSchedule(newSchedule);
179
+ onChange(newSchedule);
180
+ };
181
+
182
+ const addInterval = (dayIndex: number) => () => {
183
+ Logger.info(dayIndex);
184
+
185
+ const newSchedule = [...schedule];
186
+ newSchedule[dayIndex].intervals.push({startTime: null, endTime: null, countryCode: 'fr', error: null});
187
+ Logger.info(newSchedule);
188
+ setSchedule(newSchedule);
189
+ };
190
+
191
+ const deleteInterval = (dayIndex: number, intervalIndex: number) => () => {
192
+ const newSchedule = [...schedule];
193
+ newSchedule[dayIndex].intervals.splice(intervalIndex, 1);
194
+ onChange(newSchedule);
195
+ setSchedule(newSchedule);
196
+ }
197
+
198
+ const getDayOfWeekIndex = (day: DayOfWeek) => {
199
+ if(day){
200
+ // Convertir les valeurs de l'énumération en un tableau
201
+ const daysArray = Object.values(DayOfWeek);
202
+ return daysArray.indexOf(day);
203
+ }
204
+ return -1;
205
+ }
206
+
207
+ return (
208
+ <Grid container spacing={1} sx={{ minWidth: '650px', maxWidth: '800px'}} >
209
+ {schedule.map((daySchedule, dayIndex) => (
210
+ <>
211
+ <Grid item xs={1} />
212
+
213
+ <Grid item xs={2} key={dayIndex+1}>
214
+ <FormControlLabel
215
+ control={
216
+ <Checkbox
217
+ checked={daySchedule.checked}
218
+ onChange={handleDayChecked(dayIndex)}
219
+ />
220
+ }
221
+ label={FR_WEEK_DAYS[getDayOfWeekIndex(daySchedule.day)]}
222
+ />
223
+ </Grid>
224
+
225
+ <Grid item xs={7} key={(dayIndex+1)*Math.random()}>
226
+ {daySchedule.intervals.map((interval, intervalIndex) => (
227
+ <Grid container key={intervalIndex+1}
228
+ style={{ paddingTop: intervalIndex > 0 ? theme.spacing(1) : 0 }}>
229
+ <Grid item xs={6} key={(intervalIndex+1)*Math.random()} sx={{ textAlign: 'center' }}>
230
+ <TimePicker
231
+ views={['hours', 'minutes']}
232
+ minutesStep={timePickerStep ?? 30} // Valeur par défaut de 30 minutes au cas ou
233
+ disabled={!daySchedule.checked}
234
+ value={interval.startTime}
235
+ formatDensity='dense'
236
+ onChange={handleIntervalChange(dayIndex, intervalIndex, 'startTime')}
237
+ slotProps={{
238
+ textField: {
239
+ size: 'small',
240
+ sx: {
241
+ width:'150px',
242
+ minWidth:'100px',
243
+ padding: 0,
244
+ },
245
+ },
246
+ actionBar: {
247
+ sx: { display: 'none' },
248
+ },
249
+ }}
250
+ />
251
+ </Grid>
252
+ <Grid item xs={6} key={(intervalIndex+1)*Math.random()} sx={{ display: 'contents'}}>
253
+ <TimePicker
254
+ minutesStep={timePickerStep ?? 30} // Valeur par défaut de 30 minutes au cas ou
255
+ disabled={!(interval.startTime instanceof Date)}
256
+ value={interval.endTime}
257
+ formatDensity='dense'
258
+ onChange={handleIntervalChange(dayIndex, intervalIndex, 'endTime')}
259
+ //onClose={handleIntervalClose}
260
+ closeOnSelect={false}
261
+ slotProps={{
262
+ textField: {
263
+ size: 'small',
264
+ sx: {
265
+ width:'150px',
266
+ minWidth:'100px',
267
+ padding: 0,
268
+ },
269
+ },
270
+ actionBar: {
271
+ sx: { display: 'none' },
272
+ },
273
+ }}
274
+ />
275
+ {intervalIndex > 0 &&
276
+ <IconButton onClick={deleteInterval(dayIndex, intervalIndex)} disabled={!daySchedule.checked} sx={{ ml: 2 }}>
277
+ <DeleteIcon />
278
+ </IconButton>
279
+ }
280
+ </Grid>
281
+ <Grid item xs={12} key={(intervalIndex+1)*Math.random()} >
282
+ {interval.error && <FormHelperText error sx={{mb:1}}>{interval.error}</FormHelperText>}
283
+ </Grid>
284
+ </Grid>
285
+ ))}
286
+ </Grid>
287
+
288
+ <Grid item xs={1} key={(dayIndex+1)*Math.random()} sx={{ pl: 0 }}>
289
+ <IconButton onClick={addInterval(dayIndex)} disabled={!daySchedule.checked}>
290
+ <AddIcon />
291
+ </IconButton>
292
+ </Grid>
293
+
294
+ <Grid item xs={1} />
295
+ </>
296
+ ))}
297
+ </Grid>
298
+ );
299
+ }
300
+
301
+ export default ScheduleFields;
@@ -0,0 +1,95 @@
1
+ import { APIMethod } from "./Enums";
2
+ import { COOKIE_GARAGE_TOKEN, readCookie } from "./CookieUtils";
3
+ import Logger from "./Logger";
4
+
5
+ export const API_BASE_URL = process.env.REACT_APP_API_URL ?? 'https://localhost:8443/api';
6
+
7
+ export type APIResponse<T> = {
8
+ success: boolean;
9
+ data?: T;
10
+ error?: string;
11
+ };
12
+
13
+ export type APIRequest = {
14
+ url: string,
15
+ method: APIMethod,
16
+ body?: string | FormData
17
+ }
18
+
19
+ function handleError(error: Error): APIResponse<null> {
20
+ let msg = error.message.includes('fetch') ? "Connexion impossible" : error.message;
21
+ return {
22
+ success: false,
23
+ error: msg
24
+ };
25
+ }
26
+
27
+ function handleResponse(response: Response): Promise<APIResponse<any>> {
28
+
29
+ const contentType = response.headers.get("content-type");
30
+ const isJson = contentType && contentType.includes("application/json");
31
+
32
+ const dataPromise = isJson ? response.json() : response.text();
33
+
34
+ return dataPromise.then(data => {
35
+
36
+ if (!response.ok) {
37
+
38
+ Logger.error(data);
39
+
40
+ let errorMsg: string;
41
+
42
+ switch(response.status){
43
+ case 403:
44
+ errorMsg = 'Accès non autorisé (403)'; break;
45
+ case 404:
46
+ errorMsg = 'La ressource demandée est introuvable (404)'; break;
47
+ case 500:
48
+ errorMsg = 'Une erreur interne du serveur est survenue (500)'; break;
49
+ default:
50
+ errorMsg = (typeof data === 'string') ? data : `Une erreur est survenue (${response.status})`; break;
51
+ }
52
+
53
+ return { success: false, error: errorMsg };
54
+ }
55
+
56
+ return { success: true, data };
57
+ });
58
+ }
59
+
60
+
61
+ /**
62
+ * ATTENTION : cela signifie que toutes les requêtes du Front seront de type "preflight"
63
+ * Il faut penser à configurer l'API pour autoriser les headers
64
+ * @param options
65
+ * @param dispatch
66
+ * @returns
67
+ */
68
+ export const request = (options:APIRequest): Promise<APIResponse<any>> => {
69
+
70
+ const headers = new Headers();
71
+
72
+ // Si le corps est de type FormData, il ne faut pas définir le content-type manuellement (le navigateur prend le relai est injecte un boundary (délimiteur)
73
+ if (!(options.body instanceof FormData)) {
74
+ headers.append('Content-Type', 'application/json');
75
+ }
76
+
77
+ // Récupération du TOKEN depuis les cookies et intégration au header
78
+ if(readCookie(COOKIE_GARAGE_TOKEN)) {
79
+ headers.append('Authorization', 'Bearer ' + readCookie(COOKIE_GARAGE_TOKEN))
80
+ }
81
+
82
+ // On intègre les headers aux options
83
+ const defaults = {headers: headers};
84
+ options = Object.assign({}, defaults, options);
85
+
86
+ Logger.info(options);
87
+
88
+ // Appel API
89
+ return fetch(options.url, options)
90
+ .then(handleResponse)
91
+ .catch(error => {
92
+ Logger.error('There has been a problem with your fetch operation : ', error.message);
93
+ return handleError(error);
94
+ });
95
+ }
@@ -0,0 +1,23 @@
1
+ import Cookies from "js-cookie";
2
+
3
+ export const COOKIE_GARAGE_TOKEN: string = 'movalibGarageToken';
4
+ export const COOKIE_INDIVIDUAL_TOKEN: string = 'movalibIndividualToken';
5
+ export const COOKIE_DEFAULT_EXPIRES: number = 7;
6
+
7
+ export const createCookie = (name:string, value:string) => {
8
+ if(name && value){
9
+ Cookies.set(name, value, { expires: COOKIE_DEFAULT_EXPIRES, sameSite: 'None', secure: true });
10
+ }
11
+ }
12
+
13
+ export const deleteCookie = (name:string) => {
14
+ if(name){
15
+ Cookies.remove(name);
16
+ }
17
+ }
18
+
19
+ export const readCookie = (name:string) => {
20
+ if(name){
21
+ return Cookies.get(name);
22
+ }
23
+ }
@@ -1,3 +1,10 @@
1
+ export enum APIMethod {
2
+ GET = 'GET',
3
+ PUT = 'PUT',
4
+ PATCH = 'PATCH',
5
+ POST = 'POST',
6
+ DELETE = 'DELETE'
7
+ }
1
8
 
2
9
  export enum DateFormatTypes {
3
10
  SHORT_FORMAT_DATE = 'dd/MM/yyyy',
@@ -81,7 +88,7 @@ export enum MovaAppType {
81
88
  /**
82
89
  * Application Utilisateur (automobiliste)
83
90
  */
84
- USER = "USER",
91
+ INDIVIDUAL = "INDIVIDUAL",
85
92
  /**
86
93
  * Application Administrateur (MovaTeam)
87
94
  */
@@ -1,5 +1,23 @@
1
+ import Cookies from "js-cookie";
1
2
  import VehicleTire from "../models/VehicleTire";
2
3
  import { MovaFormField } from "./Types";
4
+ import { CSSProperties } from "react";
5
+
6
+ export const flexEnd:CSSProperties = {
7
+ display: 'flex',
8
+ justifyContent: 'end',
9
+ alignItems: 'center'
10
+ }
11
+
12
+ export const flexCenter:CSSProperties = {
13
+ display: 'flex',
14
+ justifyContent: 'center',
15
+ alignItems: 'center'
16
+ }
17
+
18
+ export const isEmpty = (data: Object): boolean => {
19
+ return Object.keys(data).length === 0;
20
+ }
3
21
 
4
22
  export const formatFrenchVehiclePlate = (input: string | undefined): string => {
5
23
  if(input){
@@ -20,7 +38,7 @@ export const formatFrenchVehiclePlate = (input: string | undefined): string => {
20
38
  return "";
21
39
  };
22
40
 
23
- export function formatVehicleTire(vehicleTire: VehicleTire){
41
+ export const formatVehicleTire = (vehicleTire: VehicleTire) => {
24
42
  if(vehicleTire){
25
43
  let concatened = `${vehicleTire.width}${vehicleTire.height}${vehicleTire.diameter}${vehicleTire.speedIndex}`;
26
44
  return formatVehicleTireStr(concatened);
@@ -28,7 +46,7 @@ export function formatVehicleTire(vehicleTire: VehicleTire){
28
46
  return '';
29
47
  }
30
48
 
31
- export function formatVehicleTireStr(input: string){
49
+ export const formatVehicleTireStr = (input: string) => {
32
50
 
33
51
  let formatted = input.toUpperCase().replace(/[^0-9A-QS-Za-qs-z]/g, '').slice(0, 10);
34
52
 
@@ -0,0 +1,68 @@
1
+ import { APIResponse, API_BASE_URL, request } from "../helpers/ApiHelper";
2
+ import { COOKIE_GARAGE_TOKEN, COOKIE_INDIVIDUAL_TOKEN, createCookie } from "../helpers/CookieUtils";
3
+ import { APIMethod, MovaAppType } from "../helpers/Enums";
4
+ import Logger from "../helpers/Logger";
5
+ import User from "../models/User";
6
+ import UserService from "./UserService";
7
+
8
+ export default class AuthenticationService {
9
+
10
+ static async login(appType: MovaAppType, email: string, password: string): Promise<APIResponse<User>> {
11
+ try {
12
+
13
+ // Contextualisation selon l'application demandeuse
14
+ let url = '';
15
+ let tokenCookie = '';
16
+
17
+ Logger.info(API_BASE_URL);
18
+
19
+ switch(appType){
20
+ case MovaAppType.GARAGE:
21
+ url = `${API_BASE_URL}/garage/login`;
22
+ tokenCookie = COOKIE_GARAGE_TOKEN;
23
+ break;
24
+ case MovaAppType.INDIVIDUAL:
25
+ url = `${API_BASE_URL}/login`;
26
+ tokenCookie = COOKIE_INDIVIDUAL_TOKEN;
27
+ break;
28
+ }
29
+
30
+ let tokenResponse = await request({
31
+ url: url,
32
+ method: APIMethod.POST,
33
+ body: JSON.stringify({
34
+ email : email,
35
+ password: password
36
+ })
37
+ });
38
+
39
+ if(tokenResponse.success){
40
+
41
+ Logger.info(tokenResponse);
42
+
43
+ createCookie(tokenCookie, tokenResponse.data.accessToken);
44
+
45
+ // Si le login est un succès, on renvoie les infos de l'utilisateur connecté
46
+ let userResponse = await UserService.getCurrentUser();
47
+
48
+ if(!userResponse || !userResponse.success) {
49
+ const errorMsg = 'Erreur au chargement de votre profil';
50
+ Logger.error(errorMsg);
51
+ return { success: false, error: errorMsg };
52
+ }
53
+
54
+ return { success: true, data: userResponse.data };
55
+
56
+ } else {
57
+
58
+ return tokenResponse;
59
+ }
60
+
61
+ } catch (error: unknown) {
62
+ Logger.error('Error occurred during login:', error);
63
+ let errorMessage = error instanceof Error ? error.message : 'Erreur inconnue';
64
+
65
+ return { success: false, error: errorMessage };
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,14 @@
1
+ import { APIResponse, API_BASE_URL, request } from "../helpers/ApiHelper";
2
+ import { APIMethod } from "../helpers/Enums";
3
+ import Garage from "../models/Garage";
4
+
5
+ export default class GarageService {
6
+
7
+ static getAdministratedGarages(): Promise<APIResponse<Garage[]>> {
8
+ return request({
9
+ url: `${API_BASE_URL}/user/garages`,
10
+ method: APIMethod.GET,
11
+ });
12
+ }
13
+
14
+ }
@@ -0,0 +1,25 @@
1
+ import { APIResponse, API_BASE_URL, request } from "../helpers/ApiHelper";
2
+ import { APIMethod } from "../helpers/Enums";
3
+ import Logger from "../helpers/Logger";
4
+ import User from "../models/User";
5
+
6
+ export default class UserService {
7
+
8
+ /**
9
+ * @param email
10
+ * @param password
11
+ * @returns
12
+ */
13
+ static async getCurrentUser(): Promise<APIResponse<User>> {
14
+ try {
15
+ return await request({
16
+ url: `${API_BASE_URL}/user/me`,
17
+ method: APIMethod.GET
18
+ });
19
+ } catch (error) {
20
+ Logger.error('Error occurred during getting user:', error);
21
+ return Promise.reject(error);
22
+ }
23
+ }
24
+
25
+ }
package/tsconfig.json CHANGED
@@ -36,7 +36,7 @@
36
36
  // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
37
37
  // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
38
38
  // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
39
- // "types": [], /* Specify type package names to be included without being referenced in a source file. */
39
+ //"types": [], /* Specify type package names to be included without being referenced in a source file. */
40
40
  // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
41
41
  // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
42
42
  // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
package/webpack.config.js CHANGED
@@ -1,10 +1,15 @@
1
1
  const HtmlWebpackPlugin = require('html-webpack-plugin');
2
2
  const path = require('path');
3
+ const webpack = require('webpack');
3
4
 
4
5
  module.exports = {
5
6
  mode: 'development', // ou 'production' ou 'none'
6
7
  module: {
7
8
  rules: [
9
+ {
10
+ test: /\.css$/i,
11
+ use: ['style-loader', 'css-loader'],
12
+ },
8
13
  {
9
14
  test: /\.tsx?$/,
10
15
  use: 'ts-loader',
@@ -31,5 +36,9 @@ module.exports = {
31
36
  new HtmlWebpackPlugin({
32
37
  template: path.resolve(__dirname, 'public', 'index.html'), // Chemin vers votre fichier HTML de base
33
38
  }),
39
+ new webpack.DefinePlugin({
40
+ 'REACT_APP_API_URL': JSON.stringify(process.env.REACT_APP_API_URL),
41
+ 'REACT_APP_MOVALIB_APP_URL': JSON.stringify(process.env.REACT_APP_MOVALIB_APP_URL)
42
+ }),
34
43
  ],
35
44
  };