data-primals-engine 1.0.11 → 1.1.0
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/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +212 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- package/vitest.config.js +2 -2
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
|
2
|
+
import { useAuthContext } from '../contexts/AuthContext.jsx';
|
|
3
|
+
import { useUI } from '../contexts/UIContext.jsx';
|
|
4
|
+
import { useNotificationContext } from '../NotificationProvider.jsx';
|
|
5
|
+
import { tutorialsConfig } from '../tutorials.js';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
8
|
+
import useLocalStorage from "./useLocalStorage.js";
|
|
9
|
+
import { event_off, event_on } from "data-primals-engine/core";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hook pour gérer la logique des tutoriels multi-étapes.
|
|
13
|
+
* Il interagit avec l'état de l'utilisateur, lance les tours guidés (TourSpotlight)
|
|
14
|
+
* et communique avec le backend pour vérifier la progression et attribuer les récompenses.
|
|
15
|
+
*/
|
|
16
|
+
export const useTutorials = () => {
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const queryClient = useQueryClient();
|
|
19
|
+
const { me, setMe } = useAuthContext();
|
|
20
|
+
const { allTourSteps, setCurrentTourSteps, setIsTourOpen } = useUI();
|
|
21
|
+
const { addNotification } = useNotificationContext();
|
|
22
|
+
|
|
23
|
+
// Refs pour gérer la file d'attente des vérifications
|
|
24
|
+
const isCheckingRef = useRef(false);
|
|
25
|
+
const isCheckQueuedRef = useRef(false);
|
|
26
|
+
|
|
27
|
+
const { data: tutorials, isLoading: isLoadingTutorials } = useQuery('tutorials', async () => {
|
|
28
|
+
return tutorialsConfig.map(t => ({ ...t, _id: t.id }));
|
|
29
|
+
}, { staleTime: Infinity });
|
|
30
|
+
|
|
31
|
+
const activeTutorialState = me?.activeTutorial;
|
|
32
|
+
|
|
33
|
+
const activeTutorialDetails = useMemo(() => {
|
|
34
|
+
if (!activeTutorialState || !tutorials) return null;
|
|
35
|
+
const tutorial = tutorials.find(t => t._id === activeTutorialState.id);
|
|
36
|
+
if (!tutorial) return null;
|
|
37
|
+
const currentStage = tutorial.stages.find(s => s.stage === activeTutorialState.stage);
|
|
38
|
+
if (!currentStage) return null;
|
|
39
|
+
return {
|
|
40
|
+
totalStages: tutorial.stages.length,
|
|
41
|
+
currentStage: currentStage,
|
|
42
|
+
};
|
|
43
|
+
}, [activeTutorialState, tutorials]);
|
|
44
|
+
|
|
45
|
+
// --- MUTATIONS VERS LE BACKEND ---
|
|
46
|
+
|
|
47
|
+
const [activeTuto, setActiveTuto] = useLocalStorage('activeTuto', null);
|
|
48
|
+
|
|
49
|
+
const { mutate: updateActiveTutorial } = useMutation(
|
|
50
|
+
(tutorialState) => fetch('/api/tutorials/set-active', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'Content-Type': 'application/json' },
|
|
53
|
+
body: JSON.stringify({ tutorialState }),
|
|
54
|
+
}).then(res => res.json()),
|
|
55
|
+
{
|
|
56
|
+
onSuccess: (data) => {
|
|
57
|
+
if (/^demo[0-9]{1,2}$/.test(me?.username)) {
|
|
58
|
+
setActiveTuto(data.updatedData.activeTutorial);
|
|
59
|
+
gtag("event", "signed_in (demo)");
|
|
60
|
+
}
|
|
61
|
+
setMe(prevMe => ({ ...prevMe, activeTutorial: data.updatedData.activeTutorial }));
|
|
62
|
+
queryClient.invalidateQueries('me');
|
|
63
|
+
},
|
|
64
|
+
onError: () => addNotification({
|
|
65
|
+
title: t('tutorial.error.update_state_message', 'Impossible de sauvegarder votre progression.'),
|
|
66
|
+
status: 'error',
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const { mutateAsync: checkCompletionCondition } = useMutation(
|
|
72
|
+
(condition) => fetch('/api/tutorials/check-completion', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
body: JSON.stringify(condition),
|
|
76
|
+
}).then(res => res.json())
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const { mutate: claimRewards } = useMutation(
|
|
80
|
+
(tutorialId) => fetch(`/api/tutorials/${tutorialId}/claim-rewards`, { method: 'POST' }).then(res => res.json()),
|
|
81
|
+
{
|
|
82
|
+
onSuccess: (data) => {
|
|
83
|
+
const newMe = {...me, ...data.userUpdate};
|
|
84
|
+
setMe(newMe);
|
|
85
|
+
queryClient.setQueryData('me', newMe);
|
|
86
|
+
if (data.notification) {
|
|
87
|
+
addNotification({
|
|
88
|
+
title: data.notification.title,
|
|
89
|
+
id: 'claim-'+(new Date().getMilliseconds()),
|
|
90
|
+
message: data.notification.message,
|
|
91
|
+
status: 'success',
|
|
92
|
+
timeout: 8000
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onError: () => addNotification({
|
|
97
|
+
title: t('tutorial.error.claim_rewards_message', 'Un problème est survenu lors de l\'attribution de vos récompenses.'),
|
|
98
|
+
status: 'error',
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const launchTour = (tourName) => {
|
|
104
|
+
const tourSteps = allTourSteps[tourName];
|
|
105
|
+
if (tourSteps && tourSteps.length > 0) {
|
|
106
|
+
setCurrentTourSteps(tourSteps);
|
|
107
|
+
setIsTourOpen(true);
|
|
108
|
+
} else {
|
|
109
|
+
console.warn(`[Tutoriels] Tour guidé non trouvé pour le nom : ${tourName}`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const startTutorial = (tutorialId) => {
|
|
114
|
+
const tutorial = tutorials?.find(t => t._id === tutorialId);
|
|
115
|
+
if (!tutorial) return;
|
|
116
|
+
const firstStage = tutorial.stages?.[0];
|
|
117
|
+
if (!firstStage) return;
|
|
118
|
+
const newActiveState = { id: tutorialId, stage: 1 };
|
|
119
|
+
updateActiveTutorial(newActiveState);
|
|
120
|
+
launchTour(firstStage.tourName);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const triggerTutorialCheck = useCallback(async () => {
|
|
124
|
+
if (isCheckingRef.current) {
|
|
125
|
+
console.log('[Tutoriels] Vérification déjà en cours, mise en file d\'attente.');
|
|
126
|
+
isCheckQueuedRef.current = true; // Mettre en attente
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!me?.activeTutorial || !tutorials) return;
|
|
130
|
+
|
|
131
|
+
isCheckingRef.current = true;
|
|
132
|
+
console.log('[Tutoriels] Démarrage de la vérification en chaîne...');
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
let currentActiveState = me.activeTutorial;
|
|
136
|
+
|
|
137
|
+
while (true) {
|
|
138
|
+
const tutorial = tutorials.find(t => t._id === currentActiveState.id);
|
|
139
|
+
if (!tutorial) break;
|
|
140
|
+
|
|
141
|
+
console.log({currentActiveState})
|
|
142
|
+
const stageConfig = tutorial.stages.find(s => s.stage === currentActiveState.stage);
|
|
143
|
+
if (!stageConfig) {
|
|
144
|
+
console.log('[Tutoriels] Fin du tutoriel, attribution des récompenses.');
|
|
145
|
+
claimRewards(tutorial._id);
|
|
146
|
+
updateActiveTutorial(null);
|
|
147
|
+
setIsTourOpen(false);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { isCompleted } = await checkCompletionCondition(stageConfig.completionCondition);
|
|
152
|
+
|
|
153
|
+
if (isCompleted) {
|
|
154
|
+
console.log(`[Tutoriels] Étape ${currentActiveState.stage} complétée, passage à la suivante.`);
|
|
155
|
+
|
|
156
|
+
// Clés de traduction structurées et robustes
|
|
157
|
+
const titleKey = `tutorial.${tutorial.id}.stage.${currentActiveState.stage}.name`;
|
|
158
|
+
const descriptionKey = `tutorial.${tutorial.id}.stage.${currentActiveState.stage}.description`;
|
|
159
|
+
|
|
160
|
+
addNotification({
|
|
161
|
+
title: t(titleKey, stageConfig.name),
|
|
162
|
+
message: t(descriptionKey, stageConfig.description),
|
|
163
|
+
status: 'success'
|
|
164
|
+
});
|
|
165
|
+
currentActiveState = { id: currentActiveState.id, stage: currentActiveState.stage + 1 };
|
|
166
|
+
} else {
|
|
167
|
+
console.log(`[Tutoriels] Arrêt à l'étape ${currentActiveState.stage} (non complétée).`);
|
|
168
|
+
if (currentActiveState.stage !== me.activeTutorial.stage) {
|
|
169
|
+
updateActiveTutorial(currentActiveState);
|
|
170
|
+
launchTour(stageConfig.tourName);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("[Tutoriels] Erreur lors de la vérification en chaîne :", error);
|
|
177
|
+
} finally {
|
|
178
|
+
isCheckingRef.current = false;
|
|
179
|
+
console.log('[Tutoriels] Vérification en chaîne terminée, verrou libéré.');
|
|
180
|
+
|
|
181
|
+
// Si une vérification a été mise en attente, on la lance maintenant.
|
|
182
|
+
if (isCheckQueuedRef.current) {
|
|
183
|
+
console.log('[Tutoriels] Lancement de la vérification mise en file d\'attente.');
|
|
184
|
+
isCheckQueuedRef.current = false;
|
|
185
|
+
triggerTutorialCheck();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}, [
|
|
189
|
+
tutorials, checkCompletionCondition, claimRewards, updateActiveTutorial,
|
|
190
|
+
addNotification, launchTour, t, setIsTourOpen, me
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
useEffect(() => {
|
|
194
|
+
setMe(prevMe => ({ ...prevMe, activeTutorial: activeTuto }));
|
|
195
|
+
}, [activeTuto]);
|
|
196
|
+
|
|
197
|
+
// Écoute les événements de modification de données pour vérifier la progression en arrière-plan.
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
const handleDataChange = (payload) => {
|
|
200
|
+
console.log(`[Tutoriels] Événement de données reçu.`, payload);
|
|
201
|
+
if (me?.activeTutorial) {
|
|
202
|
+
triggerTutorialCheck();
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
console.log(me.activeTutorial + new Date().getMilliseconds());
|
|
207
|
+
const eventTypes = ['API_ADD_DATA', 'API_EDIT_DATA', 'API_DELETE_DATA'];
|
|
208
|
+
eventTypes.forEach(type => event_on(type, handleDataChange));
|
|
209
|
+
|
|
210
|
+
return () => {
|
|
211
|
+
eventTypes.forEach(type => event_off(type, handleDataChange));
|
|
212
|
+
};
|
|
213
|
+
}, [me?.activeTutorial]);
|
|
214
|
+
|
|
215
|
+
// Vérifie la progression lorsqu'un tutoriel devient actif ou que son étape change.
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
triggerTutorialCheck();
|
|
218
|
+
},[]);
|
|
219
|
+
return {
|
|
220
|
+
tutorials,
|
|
221
|
+
isLoadingTutorials,
|
|
222
|
+
startTutorial,
|
|
223
|
+
activeTutorialState,
|
|
224
|
+
activeTutorialDetails,
|
|
225
|
+
triggerTutorialCheck,
|
|
226
|
+
};
|
|
227
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/hooks/useWindowSize.js
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
function useWindowSize() {
|
|
5
|
+
const [windowSize, setWindowSize] = useState({
|
|
6
|
+
width: window.innerWidth,
|
|
7
|
+
height: window.innerHeight,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
function handleResize() {
|
|
12
|
+
setWindowSize({
|
|
13
|
+
width: window.innerWidth,
|
|
14
|
+
height: window.innerHeight,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
window.addEventListener('resize', handleResize);
|
|
18
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
return windowSize;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default useWindowSize;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import i18n from "i18next";
|
|
2
|
+
import {initReactI18next, Trans} from "react-i18next";
|
|
3
|
+
import LanguageDetector from "i18next-browser-languagedetector";
|
|
4
|
+
import {websiteTranslations} from "./translations.js";
|
|
5
|
+
|
|
6
|
+
const options = {
|
|
7
|
+
// order and from where user language should be detected
|
|
8
|
+
order: [
|
|
9
|
+
"querystring",
|
|
10
|
+
"cookie",
|
|
11
|
+
"localStorage",
|
|
12
|
+
"sessionStorage",
|
|
13
|
+
"navigator",
|
|
14
|
+
"htmlTag",
|
|
15
|
+
"path",
|
|
16
|
+
"subdomain"
|
|
17
|
+
],
|
|
18
|
+
|
|
19
|
+
// keys or params to lookup language from
|
|
20
|
+
lookupQuerystring: "lang"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
i18n
|
|
24
|
+
// detect user language
|
|
25
|
+
// learn more: https://github.com/i18next/i18next-browser-languageDetector
|
|
26
|
+
.use(LanguageDetector)
|
|
27
|
+
// pass the i18n instance to react-i18next.
|
|
28
|
+
.use(initReactI18next)
|
|
29
|
+
// init i18next
|
|
30
|
+
// for all options read: https://www.i18next.com/overview/configuration-options
|
|
31
|
+
.init({
|
|
32
|
+
debug: false,
|
|
33
|
+
detection: options,
|
|
34
|
+
fallbackLng: "fr",
|
|
35
|
+
keySeparator: false,
|
|
36
|
+
interpolation: {
|
|
37
|
+
escapeValue: false // not needed for react as it escapes by default
|
|
38
|
+
},
|
|
39
|
+
resources: websiteTranslations,
|
|
40
|
+
react: {
|
|
41
|
+
bindI18n: 'loaded languageChanged',
|
|
42
|
+
bindI18nStore: 'added',
|
|
43
|
+
useSuspense: true
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export default i18n;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* index.css */
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
6
|
+
line-height: 1.5;
|
|
7
|
+
font-weight: 400;
|
|
8
|
+
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
color: rgba(255, 255, 255, 0.87);
|
|
11
|
+
background-color: #242424;
|
|
12
|
+
|
|
13
|
+
font-synthesis: none;
|
|
14
|
+
text-rendering: optimizeLegibility;
|
|
15
|
+
-webkit-font-smoothing: antialiased;
|
|
16
|
+
-moz-osx-font-smoothing: grayscale;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
#root {
|
|
21
|
+
width: 100%;
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: stretch;
|
|
25
|
+
justify-content: flex-start;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
display: flex;
|
|
32
|
+
min-width: 320px;
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
h1 {
|
|
37
|
+
font-size: 2em; /* Slightly reduced for better balance */
|
|
38
|
+
line-height: 1.2;
|
|
39
|
+
margin-bottom: 0.5em; /* Added margin for spacing */
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
h2 {
|
|
43
|
+
font-size: 1.5em;
|
|
44
|
+
line-height: 1.3;
|
|
45
|
+
margin-bottom: 0.5em;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
h3 {
|
|
49
|
+
font-size: 1.4em;
|
|
50
|
+
line-height: 1.3;
|
|
51
|
+
margin-bottom: 0.5em;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
h4 {
|
|
55
|
+
font-size: 1.2em;
|
|
56
|
+
line-height: 1.3;
|
|
57
|
+
margin-bottom: 0.5em;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
button {
|
|
61
|
+
border-radius: 8px;
|
|
62
|
+
border: 1px solid transparent;
|
|
63
|
+
padding: 0.15em 0.25em;
|
|
64
|
+
font-size: 0.9em;
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
font-family: inherit;
|
|
67
|
+
background-color: #1a1a1a;
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
transition: border-color 0.25s;
|
|
70
|
+
}
|
|
71
|
+
button:hover {
|
|
72
|
+
border-color: #4767FF;
|
|
73
|
+
}
|
|
74
|
+
button:focus,
|
|
75
|
+
button:focus-visible {
|
|
76
|
+
outline: 4px auto -webkit-focus-ring-color;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@media (prefers-color-scheme: light) {
|
|
80
|
+
:root {
|
|
81
|
+
color: #213547;
|
|
82
|
+
background-color: #ffffff;
|
|
83
|
+
}
|
|
84
|
+
a:hover {
|
|
85
|
+
color: #4767FF;
|
|
86
|
+
}
|
|
87
|
+
button {
|
|
88
|
+
background-color: #f9f9f9;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StrictMode } from 'react'
|
|
2
|
+
import { createRoot } from 'react-dom/client'
|
|
3
|
+
import './index.css'
|
|
4
|
+
import App from './App.jsx'
|
|
5
|
+
import {BrowserRouter} from "react-router-dom";
|
|
6
|
+
|
|
7
|
+
createRoot(document.getElementById('root')).render(
|
|
8
|
+
<StrictMode>
|
|
9
|
+
<App />
|
|
10
|
+
</StrictMode>,
|
|
11
|
+
)
|