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.
Files changed (166) hide show
  1. package/client/README.md +8 -0
  2. package/client/index.html +31 -0
  3. package/client/index.js +25 -0
  4. package/client/package-lock.json +11664 -0
  5. package/client/package.json +70 -0
  6. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  7. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  8. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  9. package/client/public/doc/API.postman_collection.json +214 -0
  10. package/client/public/github.svg +1 -0
  11. package/client/public/profilCompany.jpg +0 -0
  12. package/client/public/profilDeveloper.jpg +0 -0
  13. package/client/public/profilEngineer.jpg +0 -0
  14. package/client/public/profilPersonal.jpg +0 -0
  15. package/client/public/profils .psd +0 -0
  16. package/client/public/react.svg +9 -0
  17. package/client/src/APIInfo.jsx +328 -0
  18. package/client/src/AddWidgetTypeModal.jsx +44 -0
  19. package/client/src/AddWidgetTypeModal.scss +87 -0
  20. package/client/src/App.css +42 -0
  21. package/client/src/App.jsx +586 -0
  22. package/client/src/App.scss +1466 -0
  23. package/client/src/AssistantChat.jsx +280 -0
  24. package/client/src/AssistantChat.scss +229 -0
  25. package/client/src/Button.jsx +12 -0
  26. package/client/src/Button.scss +197 -0
  27. package/client/src/CalculationBuilder.jsx +338 -0
  28. package/client/src/CalculationStepRow.jsx +199 -0
  29. package/client/src/CalendarConfigModal.jsx +49 -0
  30. package/client/src/ChartConfigModal.jsx +402 -0
  31. package/client/src/ConditionBuilder.jsx +622 -0
  32. package/client/src/ConditionBuilder.scss +672 -0
  33. package/client/src/ConditionBuilder2.jsx +757 -0
  34. package/client/src/ContentView.jsx +274 -0
  35. package/client/src/CronBuilder.jsx +126 -0
  36. package/client/src/CronBuilder.scss +128 -0
  37. package/client/src/CronPartBuilder.jsx +150 -0
  38. package/client/src/Dashboard.jsx +210 -0
  39. package/client/src/Dashboard.scss +445 -0
  40. package/client/src/DashboardChart.jsx +362 -0
  41. package/client/src/DashboardFlexViewItem.jsx +129 -0
  42. package/client/src/DashboardView.jsx +554 -0
  43. package/client/src/DataChart.jsx +349 -0
  44. package/client/src/DataEditor.jsx +556 -0
  45. package/client/src/DataLayout.jsx +709 -0
  46. package/client/src/DataTable.jsx +996 -0
  47. package/client/src/DataTable.scss +73 -0
  48. package/client/src/Dialog.jsx +90 -0
  49. package/client/src/Dialog.scss +117 -0
  50. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  51. package/client/src/DocumentationPageLayout.jsx +93 -0
  52. package/client/src/DocumentationPageLayout.scss +262 -0
  53. package/client/src/Draggable.jsx +222 -0
  54. package/client/src/Draggable.scss +49 -0
  55. package/client/src/ExportDialog.jsx +172 -0
  56. package/client/src/Field.jsx +1558 -0
  57. package/client/src/FlexBuilder.jsx +269 -0
  58. package/client/src/FlexBuilder.scss +226 -0
  59. package/client/src/FlexBuilderControls.jsx +170 -0
  60. package/client/src/FlexBuilderModal.jsx +75 -0
  61. package/client/src/FlexBuilderModal.scss +109 -0
  62. package/client/src/FlexBuilderPreview.jsx +48 -0
  63. package/client/src/FlexBuilderUtils.js +84 -0
  64. package/client/src/FlexDataRenderer.jsx +170 -0
  65. package/client/src/FlexDataRenderer.scss +80 -0
  66. package/client/src/FlexNode.jsx +290 -0
  67. package/client/src/FlexTreeUtils.js +129 -0
  68. package/client/src/HiddenableCell.jsx +46 -0
  69. package/client/src/HiddenableCell.scss +36 -0
  70. package/client/src/KPIDialog.jsx +39 -0
  71. package/client/src/KPIWidget.jsx +139 -0
  72. package/client/src/KanbanCard.jsx +92 -0
  73. package/client/src/KanbanColumn.jsx +29 -0
  74. package/client/src/KanbanConfigModal.jsx +115 -0
  75. package/client/src/KanbanView.jsx +113 -0
  76. package/client/src/KanbanView.scss +107 -0
  77. package/client/src/MessageRotator.jsx +77 -0
  78. package/client/src/MessageRotator.scss +14 -0
  79. package/client/src/ModelCreator.jsx +640 -0
  80. package/client/src/ModelCreator.scss +154 -0
  81. package/client/src/ModelCreatorField.jsx +810 -0
  82. package/client/src/ModelImporter.jsx +120 -0
  83. package/client/src/ModelList.jsx +205 -0
  84. package/client/src/Notification.jsx +137 -0
  85. package/client/src/Notification.scss +126 -0
  86. package/client/src/NotificationProvider.jsx +73 -0
  87. package/client/src/PackGallery.jsx +211 -0
  88. package/client/src/PackGallery.scss +156 -0
  89. package/client/src/Pagination.jsx +141 -0
  90. package/client/src/RTE.jsx +415 -0
  91. package/client/src/RTETrans.jsx +103 -0
  92. package/client/src/RelationField.jsx +247 -0
  93. package/client/src/RelationValue.jsx +230 -0
  94. package/client/src/RestoreConfirmationModal.jsx +183 -0
  95. package/client/src/RestoreDialog.jsx +134 -0
  96. package/client/src/RestoreDialog.scss +67 -0
  97. package/client/src/RichTextEditorModal.jsx +34 -0
  98. package/client/src/RichTextEditorModal.scss +54 -0
  99. package/client/src/TourSpotlight.jsx +234 -0
  100. package/client/src/TourSpotlight.scss +100 -0
  101. package/client/src/TutorialsMenu.jsx +83 -0
  102. package/client/src/TutorialsMenu.scss +217 -0
  103. package/client/src/TutorialsRewardModal.jsx +65 -0
  104. package/client/src/Utils.jsx +78 -0
  105. package/client/src/ViewSwitcher.jsx +54 -0
  106. package/client/src/ViewSwitcher.scss +74 -0
  107. package/client/src/Webpage.jsx +69 -0
  108. package/client/src/_variables.scss +21 -0
  109. package/client/src/assets/react.svg +1 -0
  110. package/client/src/constants.js +68 -0
  111. package/client/src/contexts/AuthContext.jsx +28 -0
  112. package/client/src/contexts/ModelContext.jsx +328 -0
  113. package/client/src/contexts/UIContext.jsx +52 -0
  114. package/client/src/core/data.js +35 -0
  115. package/client/src/entry-client.jsx +15 -0
  116. package/client/src/entry-server.jsx +13 -0
  117. package/client/src/filter.js +222 -0
  118. package/client/src/hooks/data.js +101 -0
  119. package/client/src/hooks/useDebounce.js +20 -0
  120. package/client/src/hooks/useDragAndDrop.js +59 -0
  121. package/client/src/hooks/useLocalStorage.js +100 -0
  122. package/client/src/hooks/useMount.js +42 -0
  123. package/client/src/hooks/useTutorials.jsx +227 -0
  124. package/client/src/hooks/useWindowSize.js +24 -0
  125. package/client/src/i18n.js +47 -0
  126. package/client/src/index.css +90 -0
  127. package/client/src/index.js +10 -0
  128. package/client/src/main.jsx +11 -0
  129. package/client/src/translations.js +15940 -0
  130. package/client/src/tutorials.js +112 -0
  131. package/client/vite.config.js +30 -0
  132. package/eslint.config.js +50 -0
  133. package/package.json +48 -27
  134. package/server.js +9 -0
  135. package/src/constants.js +12 -23
  136. package/src/core.js +9 -8
  137. package/src/data.js +81 -80
  138. package/src/defaultModels.js +114 -115
  139. package/src/email.js +8 -9
  140. package/src/engine.js +5 -5
  141. package/src/events.js +1 -2
  142. package/src/i18n.js +212 -211
  143. package/src/middlewares/middleware-mongodb.js +94 -93
  144. package/src/migrate.js +24 -24
  145. package/src/modules/assistant.js +55 -55
  146. package/src/modules/bucket.js +10 -9
  147. package/src/modules/data.js +387 -357
  148. package/src/modules/file.js +3 -6
  149. package/src/modules/mongodb.js +6 -7
  150. package/src/modules/user.js +24 -47
  151. package/src/modules/workflow.js +68 -67
  152. package/src/openai.jobs.js +2 -2
  153. package/src/packs.js +30 -31
  154. package/src/providers.js +101 -3
  155. package/src/setenv.js +1 -1
  156. package/src/user.js +0 -17
  157. package/src/workers/crypto-worker.js +21 -20
  158. package/src/workers/import-export-worker.js +18 -18
  159. package/test/data.backup.integration.test.js +7 -12
  160. package/test/data.integration.test.js +27 -25
  161. package/test/globalSetup.js +2 -2
  162. package/test/import_export.integration.test.js +6 -6
  163. package/test/model.integration.test.js +21 -22
  164. package/test/workflow.integration.test.js +2 -5
  165. package/test/workflow.robustness.test.js +6 -13
  166. 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,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App.jsx';
4
+
5
+ const root = ReactDOM.createRoot(document.getElementById('root'));
6
+ root.render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
@@ -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
+ )