@arc-js/core 0.0.57 → 0.0.59
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 +371 -409
- package/config.d.ts +1 -5
- package/config.js +1 -6
- package/config.min.js +1 -1
- package/{rooting.hooks.jsx → hooks.jsx} +3 -6
- package/{rooting.hooks.tsx → hooks.tsx} +4 -5
- package/package.json +2 -6
- package/utils.d.ts +1 -2
- package/utils.js +1 -6
- package/utils.min.js +1 -1
- package/auto-rooting.jsx +0 -25
- package/auto-rooting.tsx +0 -52
- package/minimal-config.d.ts +0 -33
- package/minimal-config.js +0 -93
- package/minimal-config.min.js +0 -2
- package/routes-utils.d.ts +0 -17
- package/routes-utils.js +0 -243
- package/routes-utils.min.js +0 -2
- package/types.d.ts +0 -31
- package/types.js +0 -1
- package/types.min.js +0 -2
- package/vite.config.ts +0 -14
package/README.md
CHANGED
|
@@ -5,32 +5,33 @@
|
|
|
5
5
|

|
|
6
6
|

|
|
7
7
|
|
|
8
|
-
**@arc-js/core** est un
|
|
8
|
+
**@arc-js/core** est un ensemble de hooks et utilitaires de routage avancés pour les applications React avec TypeScript/JavaScript. Il fournit des fonctionnalités de navigation avancées, une gestion automatique des langues et des utilitaires pour simplifier le développement d'applications React Router.
|
|
9
9
|
|
|
10
10
|
## ✨ Fonctionnalités Principales
|
|
11
11
|
|
|
12
|
-
###
|
|
13
|
-
- **Génération automatique des routes** à partir de la structure du système de fichiers
|
|
14
|
-
- **Support des layouts hiérarchiques** avec héritage automatique
|
|
15
|
-
- **Pages d'erreur spécifiques** par sous-répertoire
|
|
16
|
-
- **Configuration modulaire** avec support des modules indépendants
|
|
17
|
-
|
|
18
|
-
### 🧭 Hooks de Navigation Avancés
|
|
12
|
+
### 🧭 Navigation Avancée
|
|
19
13
|
- **Navigation type-safe** avec validation des paramètres
|
|
20
14
|
- **Gestion automatique des query strings** avec support multi-langue
|
|
21
15
|
- **Résolution de routes** avec paramètres dynamiques
|
|
22
16
|
- **Navigation avec rechargement** pour les mises à jour critiques
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
17
|
+
- **Analyse d'URL** complète avec extraction des données
|
|
18
|
+
|
|
19
|
+
### 🌍 Support Multi-Langue
|
|
20
|
+
- **Gestion automatique** du paramètre de langue `lang`
|
|
21
|
+
- **Support intégré** pour français et anglais
|
|
22
|
+
- **Fallback sécurisé** vers le français par défaut
|
|
23
|
+
- **Codes de langue** standardisés (fr_FR, en_US)
|
|
24
|
+
|
|
25
|
+
### ⚡ Utilitaires de Routage
|
|
26
|
+
- **Validation de route actuelle** avec correspondance de chemin
|
|
27
|
+
- **Extraction des paramètres** de query string
|
|
28
|
+
- **Génération d'URL** avec paramètres et queries
|
|
29
|
+
- **Navigation externe** vers des URL spécifiques
|
|
30
|
+
|
|
31
|
+
### 🛡️ Gestion d'Erreurs
|
|
32
|
+
- **Fallback sécurisé** pour les routes invalides
|
|
33
|
+
- **Validation stricte** des paramètres d'entrée
|
|
34
|
+
- **Logs détaillés** en mode développement
|
|
34
35
|
- **Types TypeScript complets** pour une meilleure autocomplétion
|
|
35
36
|
|
|
36
37
|
## 📦 Installation
|
|
@@ -38,48 +39,21 @@
|
|
|
38
39
|
### Via npm/yarn/pnpm
|
|
39
40
|
|
|
40
41
|
```bash
|
|
41
|
-
npm install @arc-js/core react-router-dom react
|
|
42
|
+
npm install @arc-js/core react-router-dom @arc-js/qust react
|
|
42
43
|
# ou
|
|
43
|
-
yarn add @arc-js/core react-router-dom react
|
|
44
|
+
yarn add @arc-js/core react-router-dom @arc-js/qust react
|
|
44
45
|
# ou
|
|
45
|
-
pnpm add @arc-js/core react-router-dom react
|
|
46
|
+
pnpm add @arc-js/core react-router-dom @arc-js/qust react
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
### Dépendances requises
|
|
49
50
|
- React 18+
|
|
50
51
|
- React Router DOM 6+
|
|
51
52
|
- TypeScript 5.0+ (recommandé)
|
|
52
|
-
- @arc-js/qust (pour la manipulation de query strings)
|
|
53
|
+
- @arc-js/qust 1.0.0+ (pour la manipulation de query strings)
|
|
53
54
|
|
|
54
55
|
## 🚀 Démarrage Rapide
|
|
55
56
|
|
|
56
|
-
### Structure de projet recommandée
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
src/
|
|
60
|
-
├── pages/
|
|
61
|
-
│ ├── _layout.tsx # Layout racine
|
|
62
|
-
│ ├── _error.tsx # Page d'erreur racine
|
|
63
|
-
│ ├── index.tsx # Page d'accueil
|
|
64
|
-
│ ├── about/
|
|
65
|
-
│ │ ├── _layout.tsx # Layout spécifique à /about
|
|
66
|
-
│ │ ├── _error.tsx # Erreur spécifique à /about
|
|
67
|
-
│ │ ├── index.tsx # /about
|
|
68
|
-
│ │ └── team.tsx # /about/team
|
|
69
|
-
│ └── users/
|
|
70
|
-
│ ├── _layout.tsx # Layout spécifique à /users
|
|
71
|
-
│ ├── [id].tsx # /users/:id (paramètre dynamique)
|
|
72
|
-
│ └── index.tsx # /users
|
|
73
|
-
├── modules/
|
|
74
|
-
│ └── admin/
|
|
75
|
-
│ ├── config.json # Configuration du module admin
|
|
76
|
-
│ └── pages/
|
|
77
|
-
│ ├── _layout.tsx # Layout du module admin
|
|
78
|
-
│ ├── dashboard.tsx # /admin/dashboard
|
|
79
|
-
│ └── users.tsx # /admin/users
|
|
80
|
-
└── main.tsx
|
|
81
|
-
```
|
|
82
|
-
|
|
83
57
|
### Configuration de base
|
|
84
58
|
|
|
85
59
|
```typescript
|
|
@@ -87,33 +61,56 @@ src/
|
|
|
87
61
|
import React from 'react';
|
|
88
62
|
import ReactDOM from 'react-dom/client';
|
|
89
63
|
import { BrowserRouter } from 'react-router-dom';
|
|
90
|
-
import
|
|
91
|
-
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
|
|
92
|
-
|
|
93
|
-
const App = () => {
|
|
94
|
-
const routes = getRoutes();
|
|
95
|
-
const router = createBrowserRouter(routes);
|
|
96
|
-
|
|
97
|
-
return <RouterProvider router={router} />;
|
|
98
|
-
};
|
|
64
|
+
import App from './App';
|
|
99
65
|
|
|
100
66
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
101
67
|
<React.StrictMode>
|
|
102
|
-
<
|
|
68
|
+
<BrowserRouter>
|
|
69
|
+
<App />
|
|
70
|
+
</BrowserRouter>
|
|
103
71
|
</React.StrictMode>
|
|
104
72
|
);
|
|
105
73
|
```
|
|
106
74
|
|
|
107
|
-
###
|
|
75
|
+
### Utilisation du hook principal
|
|
108
76
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
77
|
+
```typescript
|
|
78
|
+
// App.tsx
|
|
79
|
+
import { useRootingActions } from '@arc-js/core';
|
|
80
|
+
|
|
81
|
+
const App = () => {
|
|
82
|
+
const {
|
|
83
|
+
params,
|
|
84
|
+
queries,
|
|
85
|
+
resolveRoute,
|
|
86
|
+
goToRoute,
|
|
87
|
+
checkIfIsCurrentRoute
|
|
88
|
+
} = useRootingActions();
|
|
89
|
+
|
|
90
|
+
// Navigation vers une page avec paramètres
|
|
91
|
+
const navigateToUser = (userId: string) => {
|
|
92
|
+
goToRoute({
|
|
93
|
+
path: '/users/:id',
|
|
94
|
+
params: { id: userId },
|
|
95
|
+
queries: { lang: 'fr', view: 'profile' }
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Vérifier si on est sur la page d'accueil
|
|
100
|
+
const isHome = checkIfIsCurrentRoute('/');
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<h1>Mon Application</h1>
|
|
105
|
+
{isHome && <p>Vous êtes sur la page d'accueil</p>}
|
|
106
|
+
<button onClick={() => navigateToUser('123')}>
|
|
107
|
+
Voir l'utilisateur 123
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default App;
|
|
117
114
|
```
|
|
118
115
|
|
|
119
116
|
## 📚 Documentation API
|
|
@@ -125,32 +122,29 @@ import { useRootingActions } from '@arc-js/core';
|
|
|
125
122
|
|
|
126
123
|
const MyComponent = () => {
|
|
127
124
|
const {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
// Paramètres de l'URL actuelle
|
|
126
|
+
params, // { id: "123", slug: "article" }
|
|
127
|
+
queries, // { lang: "fr", page: "1" }
|
|
128
|
+
pathName, // "/users/123/profile"
|
|
129
|
+
urlSearch, // "?lang=fr&page=1"
|
|
130
|
+
|
|
131
|
+
// Fonctions de navigation
|
|
132
|
+
navigate, // navigate() de react-router-dom
|
|
133
|
+
resolveRoute, // Générer une URL complète
|
|
132
134
|
goToRoute, // Naviguer vers une route
|
|
133
135
|
goAndReloadRoute, // Naviguer avec rechargement
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
goToUrl, // Naviguer vers une URL externe
|
|
137
|
+
|
|
138
|
+
// Utilitaires
|
|
139
|
+
getParams, // Obtenir les paramètres d'URL
|
|
136
140
|
getUrlData, // Analyser une URL
|
|
137
|
-
checkIfIsCurrentRoute // Vérifier
|
|
141
|
+
checkIfIsCurrentRoute // Vérifier la route actuelle
|
|
138
142
|
} = useRootingActions();
|
|
139
143
|
|
|
140
|
-
// Exemple d'utilisation
|
|
141
|
-
const handleClick = () => {
|
|
142
|
-
goToRoute({
|
|
143
|
-
path: '/users/:id',
|
|
144
|
-
params: { id: '123' },
|
|
145
|
-
queries: { lang: 'fr', view: 'details' }
|
|
146
|
-
});
|
|
147
|
-
};
|
|
148
|
-
|
|
149
144
|
return (
|
|
150
145
|
<div>
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
</button>
|
|
146
|
+
<p>ID Utilisateur: {params.id}</p>
|
|
147
|
+
<p>Langue: {queries.lang || 'fr'}</p>
|
|
154
148
|
</div>
|
|
155
149
|
);
|
|
156
150
|
};
|
|
@@ -161,258 +155,194 @@ const MyComponent = () => {
|
|
|
161
155
|
```typescript
|
|
162
156
|
// Configuration de navigation
|
|
163
157
|
interface ConfigGoToRoute {
|
|
164
|
-
path?: string;
|
|
165
|
-
params?: any;
|
|
166
|
-
queries?: any;
|
|
167
|
-
refreshPage?: boolean;
|
|
168
|
-
replace?: boolean;
|
|
169
|
-
enableLoader?: boolean;
|
|
158
|
+
path?: string; // Chemin de la route (ex: "/users/:id")
|
|
159
|
+
params?: any; // Paramètres de route (ex: { id: "123" })
|
|
160
|
+
queries?: any; // Query parameters (ex: { lang: "fr" })
|
|
161
|
+
refreshPage?: boolean; // Forcer le rechargement
|
|
162
|
+
replace?: boolean; // Remplacer l'historique
|
|
163
|
+
enableLoader?: boolean; // Afficher un loader
|
|
170
164
|
}
|
|
171
165
|
|
|
172
|
-
// Configuration de résolution
|
|
166
|
+
// Configuration de résolution
|
|
173
167
|
interface ConfigResolveRoute {
|
|
174
168
|
path?: string;
|
|
175
169
|
params?: any;
|
|
176
170
|
queries?: any;
|
|
177
171
|
}
|
|
178
172
|
|
|
179
|
-
//
|
|
180
|
-
interface
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
error?: React.ComponentType<{}>;
|
|
173
|
+
// Configuration de navigation externe
|
|
174
|
+
interface ConfigGoToSpecificUrl {
|
|
175
|
+
path?: string;
|
|
176
|
+
queries?: any;
|
|
177
|
+
refreshPage?: boolean;
|
|
178
|
+
replace?: boolean;
|
|
179
|
+
enableLoader?: boolean;
|
|
187
180
|
}
|
|
188
181
|
|
|
189
|
-
//
|
|
190
|
-
interface
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
// Valeur de retour du hook
|
|
183
|
+
interface RootingActionReturns {
|
|
184
|
+
params: Readonly<Params<string>>;
|
|
185
|
+
queries: any;
|
|
186
|
+
getParams: () => URLSearchParams;
|
|
187
|
+
navigate: NavigateFunction;
|
|
188
|
+
resolveRoute: (config: ConfigResolveRoute) => string;
|
|
189
|
+
goToRoute: (config: ConfigGoToRoute) => void;
|
|
190
|
+
goAndReloadRoute: (config: ConfigGoAndReloadRoute) => void;
|
|
191
|
+
pathName: string;
|
|
192
|
+
urlSearch: string;
|
|
193
|
+
getUrlData: (url: string | URL) => {
|
|
194
|
+
host: string;
|
|
195
|
+
hostname: string;
|
|
196
|
+
pathname: string;
|
|
197
|
+
search: string;
|
|
198
|
+
queries: any;
|
|
199
|
+
} | undefined;
|
|
200
|
+
goToUrl: (config: ConfigGoToSpecificUrl) => string;
|
|
201
|
+
checkIfIsCurrentRoute: (path?: string, exact?: boolean, strict?: boolean) => boolean;
|
|
195
202
|
}
|
|
196
203
|
```
|
|
197
204
|
|
|
198
205
|
### Fonctions utilitaires
|
|
199
206
|
|
|
200
207
|
```typescript
|
|
201
|
-
import { nativeResolveRoute } from '@arc-js/core';
|
|
208
|
+
import { nativeResolveRoute, getLang, getLangCode } from '@arc-js/core';
|
|
202
209
|
|
|
203
|
-
//
|
|
204
|
-
const
|
|
205
|
-
path: '/users/:id',
|
|
210
|
+
// Générer une URL sans utiliser le hook
|
|
211
|
+
const userProfileUrl = nativeResolveRoute({
|
|
212
|
+
path: '/users/:id/profile',
|
|
206
213
|
params: { id: '456' },
|
|
207
|
-
queries: { lang: 'en', tab: '
|
|
214
|
+
queries: { lang: 'en', tab: 'settings' }
|
|
208
215
|
});
|
|
216
|
+
// Résultat: "/users/456/profile?lang=en&tab=settings"
|
|
209
217
|
|
|
210
|
-
|
|
218
|
+
// Gestion des langues
|
|
219
|
+
const currentLang = getLang('fr'); // "fr" (valide)
|
|
220
|
+
const fallbackLang = getLang('es'); // "fr" (fallback)
|
|
221
|
+
const langCode = getLangCode('en'); // "en_US"
|
|
211
222
|
```
|
|
212
223
|
|
|
213
224
|
## 🔧 Utilisation Avancée
|
|
214
225
|
|
|
215
|
-
###
|
|
226
|
+
### Navigation avec gestion de langue
|
|
216
227
|
|
|
217
228
|
```typescript
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
229
|
+
const LanguageAwareNavigation = () => {
|
|
230
|
+
const { goToRoute, queries } = useRootingActions();
|
|
231
|
+
const currentLang = queries.lang || 'fr';
|
|
232
|
+
|
|
233
|
+
const navigateWithLang = (path: string, params?: any, additionalQueries?: any) => {
|
|
234
|
+
goToRoute({
|
|
235
|
+
path,
|
|
236
|
+
params,
|
|
237
|
+
queries: {
|
|
238
|
+
...additionalQueries,
|
|
239
|
+
lang: currentLang
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
};
|
|
225
243
|
|
|
226
244
|
return (
|
|
227
245
|
<div>
|
|
228
|
-
<
|
|
229
|
-
|
|
246
|
+
<button onClick={() => navigateWithLang('/about', null, { section: 'team' })}>
|
|
247
|
+
Voir l'équipe
|
|
248
|
+
</button>
|
|
230
249
|
</div>
|
|
231
250
|
);
|
|
232
251
|
};
|
|
233
|
-
|
|
234
|
-
export default UserPage;
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Layouts hiérarchiques
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
// src/pages/_layout.tsx (Layout racine)
|
|
241
|
-
const RootLayout = ({ children }) => (
|
|
242
|
-
<div className="app">
|
|
243
|
-
<header>Mon Application</header>
|
|
244
|
-
<main>{children}</main>
|
|
245
|
-
<footer>© 2024</footer>
|
|
246
|
-
</div>
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// src/pages/admin/_layout.tsx (Layout admin)
|
|
250
|
-
const AdminLayout = ({ children }) => (
|
|
251
|
-
<div className="admin">
|
|
252
|
-
<nav>Menu Admin</nav>
|
|
253
|
-
<div className="admin-content">{children}</div>
|
|
254
|
-
</div>
|
|
255
|
-
);
|
|
256
252
|
```
|
|
257
253
|
|
|
258
|
-
###
|
|
254
|
+
### Analyse d'URL externe
|
|
259
255
|
|
|
260
256
|
```typescript
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
<div>
|
|
264
|
-
<h1>Une erreur est survenue</h1>
|
|
265
|
-
<p>Veuillez réessayer plus tard.</p>
|
|
266
|
-
</div>
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
// src/pages/admin/_error.tsx (Erreur admin)
|
|
270
|
-
const AdminErrorPage = () => (
|
|
271
|
-
<div className="admin-error">
|
|
272
|
-
<h1>Erreur d'administration</h1>
|
|
273
|
-
<p>Contactez le support technique.</p>
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
### Configuration de modules
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
// src/modules/blog/config.json
|
|
282
|
-
{
|
|
283
|
-
"path": "/blog",
|
|
284
|
-
"name": "Blog",
|
|
285
|
-
"description": "Module de blog",
|
|
286
|
-
"author": "Équipe Rédaction",
|
|
287
|
-
"isEnabled": true
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// src/modules/blog/pages/index.tsx sera accessible à /blog
|
|
291
|
-
// src/modules/blog/pages/[slug].tsx sera accessible à /blog/:slug
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## 🎯 Exemples Complets
|
|
295
|
-
|
|
296
|
-
### Exemple 1 : Application avec authentification
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
// src/pages/_layout.tsx
|
|
300
|
-
import { useRootingActions } from '@arc-js/core';
|
|
301
|
-
import { Link } from 'react-router-dom';
|
|
302
|
-
|
|
303
|
-
const AppLayout = ({ children }) => {
|
|
304
|
-
const { checkIfIsCurrentRoute } = useRootingActions();
|
|
305
|
-
const isActive = (path: string) => checkIfIsCurrentRoute(path);
|
|
306
|
-
|
|
307
|
-
return (
|
|
308
|
-
<div>
|
|
309
|
-
<nav>
|
|
310
|
-
<Link to="/" className={isActive('/') ? 'active' : ''}>
|
|
311
|
-
Accueil
|
|
312
|
-
</Link>
|
|
313
|
-
<Link to="/about" className={isActive('/about') ? 'active' : ''}>
|
|
314
|
-
À propos
|
|
315
|
-
</Link>
|
|
316
|
-
<Link to="/contact" className={isActive('/contact') ? 'active' : ''}>
|
|
317
|
-
Contact
|
|
318
|
-
</Link>
|
|
319
|
-
</nav>
|
|
320
|
-
{children}
|
|
321
|
-
</div>
|
|
322
|
-
);
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
// src/pages/protected/_layout.tsx
|
|
326
|
-
import { useEffect } from 'react';
|
|
327
|
-
import { useRootingActions } from '@arc-js/core';
|
|
328
|
-
|
|
329
|
-
const ProtectedLayout = ({ children }) => {
|
|
330
|
-
const { goToRoute } = useRootingActions();
|
|
257
|
+
const UrlAnalyzer = () => {
|
|
258
|
+
const { getUrlData } = useRootingActions();
|
|
331
259
|
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
260
|
+
const analyzeUrl = () => {
|
|
261
|
+
const urlData = getUrlData('https://example.com/products/123?color=red&size=L');
|
|
262
|
+
|
|
263
|
+
if (urlData) {
|
|
264
|
+
console.log('Host:', urlData.host); // "example.com"
|
|
265
|
+
console.log('Path:', urlData.pathname); // "/products/123"
|
|
266
|
+
console.log('Queries:', urlData.queries); // { color: "red", size: "L" }
|
|
339
267
|
}
|
|
340
|
-
}
|
|
268
|
+
};
|
|
341
269
|
|
|
342
|
-
return
|
|
270
|
+
return (
|
|
271
|
+
<button onClick={analyzeUrl}>
|
|
272
|
+
Analyser l'URL
|
|
273
|
+
</button>
|
|
274
|
+
);
|
|
343
275
|
};
|
|
344
276
|
```
|
|
345
277
|
|
|
346
|
-
###
|
|
278
|
+
### Navigation conditionnelle
|
|
347
279
|
|
|
348
280
|
```typescript
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
import { useState, useEffect } from 'react';
|
|
352
|
-
|
|
353
|
-
const DashboardPage = () => {
|
|
354
|
-
const { queries, goToRoute, resolveRoute } = useRootingActions();
|
|
355
|
-
const [lang, setLang] = useState(queries.lang || 'fr');
|
|
281
|
+
const SmartNavigation = () => {
|
|
282
|
+
const { goToRoute, checkIfIsCurrentRoute } = useRootingActions();
|
|
356
283
|
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return resolveRoute({
|
|
366
|
-
path: '/dashboard/reports/:id',
|
|
367
|
-
params: { id: reportId },
|
|
368
|
-
queries: { lang, format: 'pdf' }
|
|
369
|
-
});
|
|
284
|
+
const navigateSmartly = (path: string) => {
|
|
285
|
+
// Si déjà sur la page, ne rien faire
|
|
286
|
+
if (checkIfIsCurrentRoute(path)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Sinon, naviguer
|
|
291
|
+
goToRoute({ path });
|
|
370
292
|
};
|
|
371
293
|
|
|
372
294
|
return (
|
|
373
|
-
<
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
Télécharger le rapport mensuel
|
|
382
|
-
</a>
|
|
383
|
-
</div>
|
|
295
|
+
<nav>
|
|
296
|
+
<button onClick={() => navigateSmartly('/')}>
|
|
297
|
+
Accueil
|
|
298
|
+
</button>
|
|
299
|
+
<button onClick={() => navigateSmartly('/about')}>
|
|
300
|
+
À propos
|
|
301
|
+
</button>
|
|
302
|
+
</nav>
|
|
384
303
|
);
|
|
385
304
|
};
|
|
386
305
|
```
|
|
387
306
|
|
|
388
|
-
|
|
307
|
+
## 🎯 Exemples Complets
|
|
308
|
+
|
|
309
|
+
### Exemple 1 : Formulaire avec redirection
|
|
389
310
|
|
|
390
311
|
```typescript
|
|
391
|
-
// src/pages/contact/index.tsx
|
|
392
312
|
import { useState } from 'react';
|
|
393
313
|
import { useRootingActions } from '@arc-js/core';
|
|
394
314
|
|
|
395
|
-
const
|
|
315
|
+
const ContactForm = () => {
|
|
396
316
|
const { goToRoute, goAndReloadRoute } = useRootingActions();
|
|
397
|
-
const [formData, setFormData] = useState({ name: '', email: '' });
|
|
317
|
+
const [formData, setFormData] = useState({ name: '', email: '', message: '' });
|
|
398
318
|
|
|
399
319
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
400
320
|
e.preventDefault();
|
|
401
321
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
goToRoute({
|
|
408
|
-
path: '/contact/success',
|
|
409
|
-
queries: { ref: 'contact-form' }
|
|
322
|
+
try {
|
|
323
|
+
// Envoyer les données
|
|
324
|
+
const response = await fetch('/api/contact', {
|
|
325
|
+
method: 'POST',
|
|
326
|
+
body: JSON.stringify(formData)
|
|
410
327
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
328
|
+
|
|
329
|
+
if (response.ok) {
|
|
330
|
+
// Redirection simple vers la page de confirmation
|
|
331
|
+
goToRoute({
|
|
332
|
+
path: '/contact/success',
|
|
333
|
+
queries: { ref: 'contact-form' }
|
|
334
|
+
});
|
|
335
|
+
} else {
|
|
336
|
+
// Redirection avec rechargement pour nettoyer les erreurs
|
|
337
|
+
goAndReloadRoute({
|
|
338
|
+
path: '/contact',
|
|
339
|
+
queries: { error: 'submission_failed' }
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
goToRoute({
|
|
414
344
|
path: '/contact',
|
|
415
|
-
queries: { error: '
|
|
345
|
+
queries: { error: 'network_error' }
|
|
416
346
|
});
|
|
417
347
|
}
|
|
418
348
|
};
|
|
@@ -422,166 +352,220 @@ const ContactPage = () => {
|
|
|
422
352
|
<input
|
|
423
353
|
value={formData.name}
|
|
424
354
|
onChange={e => setFormData({...formData, name: e.target.value})}
|
|
425
|
-
placeholder="
|
|
355
|
+
placeholder="Votre nom"
|
|
426
356
|
/>
|
|
427
357
|
<input
|
|
358
|
+
type="email"
|
|
428
359
|
value={formData.email}
|
|
429
360
|
onChange={e => setFormData({...formData, email: e.target.value})}
|
|
430
|
-
placeholder="
|
|
361
|
+
placeholder="Votre email"
|
|
362
|
+
/>
|
|
363
|
+
<textarea
|
|
364
|
+
value={formData.message}
|
|
365
|
+
onChange={e => setFormData({...formData, message: e.target.value})}
|
|
366
|
+
placeholder="Votre message"
|
|
367
|
+
rows={4}
|
|
431
368
|
/>
|
|
432
|
-
<button type="submit">
|
|
369
|
+
<button type="submit">
|
|
370
|
+
Envoyer
|
|
371
|
+
</button>
|
|
433
372
|
</form>
|
|
434
373
|
);
|
|
435
374
|
};
|
|
436
375
|
```
|
|
437
376
|
|
|
438
|
-
|
|
377
|
+
### Exemple 2 : Système de pagination
|
|
439
378
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
```bash
|
|
443
|
-
# .env
|
|
444
|
-
VITE_APP_NAME="Mon Application"
|
|
445
|
-
VITE_API_URL="http://localhost:3000"
|
|
379
|
+
```typescript
|
|
380
|
+
import { useRootingActions } from '@arc-js/core';
|
|
446
381
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
382
|
+
const PaginatedList = ({ items, currentPage, totalPages }) => {
|
|
383
|
+
const { goToRoute, queries } = useRootingActions();
|
|
384
|
+
|
|
385
|
+
const goToPage = (page: number) => {
|
|
386
|
+
goToRoute({
|
|
387
|
+
path: '/items',
|
|
388
|
+
queries: {
|
|
389
|
+
...queries,
|
|
390
|
+
page: page.toString()
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div>
|
|
397
|
+
<h2>Liste des éléments</h2>
|
|
398
|
+
|
|
399
|
+
{/* Liste des éléments */}
|
|
400
|
+
<ul>
|
|
401
|
+
{items.map(item => (
|
|
402
|
+
<li key={item.id}>{item.name}</li>
|
|
403
|
+
))}
|
|
404
|
+
</ul>
|
|
405
|
+
|
|
406
|
+
{/* Pagination */}
|
|
407
|
+
<div className="pagination">
|
|
408
|
+
<button
|
|
409
|
+
disabled={currentPage <= 1}
|
|
410
|
+
onClick={() => goToPage(currentPage - 1)}
|
|
411
|
+
>
|
|
412
|
+
Précédent
|
|
413
|
+
</button>
|
|
414
|
+
|
|
415
|
+
<span>Page {currentPage} sur {totalPages}</span>
|
|
416
|
+
|
|
417
|
+
<button
|
|
418
|
+
disabled={currentPage >= totalPages}
|
|
419
|
+
onClick={() => goToPage(currentPage + 1)}
|
|
420
|
+
>
|
|
421
|
+
Suivant
|
|
422
|
+
</button>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
};
|
|
450
427
|
```
|
|
451
428
|
|
|
452
|
-
###
|
|
429
|
+
### Exemple 3 : Menu de navigation intelligent
|
|
453
430
|
|
|
454
431
|
```typescript
|
|
455
|
-
|
|
456
|
-
import { getRoutes as getBaseRoutes } from '@arc-js/core';
|
|
457
|
-
import { RouteDefinition } from '@arc-js/core/types';
|
|
432
|
+
import { useRootingActions } from '@arc-js/core';
|
|
458
433
|
|
|
459
|
-
|
|
460
|
-
const
|
|
434
|
+
const NavigationMenu = () => {
|
|
435
|
+
const { goToRoute, checkIfIsCurrentRoute } = useRootingActions();
|
|
461
436
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
component: () => <div>Route personnalisée</div>,
|
|
468
|
-
layout: undefined,
|
|
469
|
-
error: undefined
|
|
470
|
-
}
|
|
437
|
+
const menuItems = [
|
|
438
|
+
{ path: '/', label: 'Accueil' },
|
|
439
|
+
{ path: '/products', label: 'Produits' },
|
|
440
|
+
{ path: '/about', label: 'À propos' },
|
|
441
|
+
{ path: '/contact', label: 'Contact' }
|
|
471
442
|
];
|
|
472
443
|
|
|
473
|
-
return
|
|
474
|
-
|
|
444
|
+
return (
|
|
445
|
+
<nav className="navigation">
|
|
446
|
+
{menuItems.map(item => {
|
|
447
|
+
const isActive = checkIfIsCurrentRoute(item.path);
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
<button
|
|
451
|
+
key={item.path}
|
|
452
|
+
className={`nav-item \${isActive ? 'active' : ''}`}
|
|
453
|
+
onClick={() => goToRoute({ path: item.path })}
|
|
454
|
+
>
|
|
455
|
+
{item.label}
|
|
456
|
+
</button>
|
|
457
|
+
);
|
|
458
|
+
})}
|
|
459
|
+
</nav>
|
|
460
|
+
);
|
|
461
|
+
};
|
|
475
462
|
```
|
|
476
463
|
|
|
477
|
-
###
|
|
464
|
+
### Exemple 4 : Sélecteur de langue
|
|
478
465
|
|
|
479
466
|
```typescript
|
|
480
|
-
// custom-router.tsx
|
|
481
467
|
import { useRootingActions } from '@arc-js/core';
|
|
482
|
-
import type { ConfigGoToRoute } from '@arc-js/core';
|
|
483
468
|
|
|
484
|
-
|
|
485
|
-
const
|
|
469
|
+
const LanguageSelector = () => {
|
|
470
|
+
const { goToRoute, queries } = useRootingActions();
|
|
471
|
+
const currentLang = queries.lang || 'fr';
|
|
486
472
|
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if (analyticsEvent) {
|
|
493
|
-
window.gtag?.('event', analyticsEvent, {
|
|
494
|
-
path: config.path,
|
|
495
|
-
params: config.params
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Utiliser la navigation standard
|
|
500
|
-
return baseActions.goToRoute(config);
|
|
501
|
-
};
|
|
473
|
+
const languages = [
|
|
474
|
+
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
|
|
475
|
+
{ code: 'en', name: 'English', flag: '🇬🇧' },
|
|
476
|
+
{ code: 'es', name: 'Español', flag: '🇪🇸' }
|
|
477
|
+
];
|
|
502
478
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
479
|
+
const changeLanguage = (langCode: string) => {
|
|
480
|
+
goToRoute({
|
|
481
|
+
path: window.location.pathname,
|
|
482
|
+
queries: {
|
|
483
|
+
...queries,
|
|
484
|
+
lang: langCode
|
|
485
|
+
}
|
|
486
|
+
});
|
|
506
487
|
};
|
|
488
|
+
|
|
489
|
+
return (
|
|
490
|
+
<div className="language-selector">
|
|
491
|
+
{languages.map(lang => (
|
|
492
|
+
<button
|
|
493
|
+
key={lang.code}
|
|
494
|
+
className={`lang-btn \${currentLang === lang.code ? 'active' : ''}`}
|
|
495
|
+
onClick={() => changeLanguage(lang.code)}
|
|
496
|
+
title={lang.name}
|
|
497
|
+
>
|
|
498
|
+
{lang.flag} {lang.name}
|
|
499
|
+
</button>
|
|
500
|
+
))}
|
|
501
|
+
</div>
|
|
502
|
+
);
|
|
507
503
|
};
|
|
508
504
|
```
|
|
509
505
|
|
|
510
|
-
##
|
|
506
|
+
## 🔧 Configuration
|
|
511
507
|
|
|
512
|
-
###
|
|
508
|
+
### Configuration des langues
|
|
513
509
|
|
|
510
|
+
```typescript
|
|
511
|
+
// Configuration par défaut dans @arc-js/core
|
|
512
|
+
export const langs = ['en', 'fr']; // Langues supportées
|
|
513
|
+
export const langCodes = {
|
|
514
|
+
'fr': 'fr_FR',
|
|
515
|
+
'en': 'en_US',
|
|
516
|
+
};
|
|
514
517
|
```
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
└── public/
|
|
522
|
-
└── _error.tsx # Erreur section publique
|
|
518
|
+
|
|
519
|
+
### Variables d'environnement
|
|
520
|
+
|
|
521
|
+
```bash
|
|
522
|
+
# .env
|
|
523
|
+
NODE_ENV=development # development | debug | production
|
|
523
524
|
```
|
|
524
525
|
|
|
525
|
-
|
|
526
|
+
## 🛡️ Gestion des Erreurs
|
|
526
527
|
|
|
527
|
-
|
|
528
|
-
// src/pages/_error.tsx
|
|
529
|
-
import { useRouteError, Link } from 'react-router-dom';
|
|
530
|
-
import { useRootingActions } from '@arc-js/core';
|
|
528
|
+
### Fallback sécurisé
|
|
531
529
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const {
|
|
535
|
-
|
|
536
|
-
console.error('Route Error:', error);
|
|
530
|
+
```typescript
|
|
531
|
+
const SafeNavigation = () => {
|
|
532
|
+
const { resolveRoute, goToRoute } = useRootingActions();
|
|
537
533
|
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
534
|
+
const safeGoToRoute = (config: ConfigGoToRoute) => {
|
|
535
|
+
try {
|
|
536
|
+
goToRoute(config);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
console.error('Navigation error:', error);
|
|
539
|
+
// Fallback vers la page d'accueil
|
|
540
|
+
goToRoute({ path: '/' });
|
|
541
|
+
}
|
|
543
542
|
};
|
|
544
543
|
|
|
545
544
|
return (
|
|
546
|
-
<
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
<div className="error-actions">
|
|
551
|
-
<button onClick={handleRetry}>
|
|
552
|
-
Réessayer
|
|
553
|
-
</button>
|
|
554
|
-
<Link to="/">
|
|
555
|
-
Retour à l'accueil
|
|
556
|
-
</Link>
|
|
557
|
-
</div>
|
|
558
|
-
</div>
|
|
545
|
+
<button onClick={() => safeGoToRoute({ path: '/invalid-route' })}>
|
|
546
|
+
Naviguer en sécurité
|
|
547
|
+
</button>
|
|
559
548
|
);
|
|
560
549
|
};
|
|
561
550
|
```
|
|
562
551
|
|
|
563
552
|
## 📋 Table des Conventions
|
|
564
553
|
|
|
565
|
-
###
|
|
554
|
+
### Paramètres de query string
|
|
566
555
|
|
|
567
|
-
|
|
|
568
|
-
|
|
569
|
-
| `
|
|
570
|
-
| `
|
|
571
|
-
| `
|
|
572
|
-
| `
|
|
573
|
-
| `[param].tsx` | `/:param` | Paramètre dynamique |
|
|
574
|
-
| `[...slug].tsx` | `/*` | Catch-all route |
|
|
556
|
+
| Paramètre | Type | Description | Valeur par défaut |
|
|
557
|
+
|-----------|------|-------------|-------------------|
|
|
558
|
+
| `lang` | `fr` \| `en` | Langue de l'application | `fr` |
|
|
559
|
+
| `page` | number | Numéro de page | `1` |
|
|
560
|
+
| `sort` | string | Tri des résultats | `date` |
|
|
561
|
+
| `filter` | string | Filtre appliqué | `all` |
|
|
575
562
|
|
|
576
|
-
###
|
|
563
|
+
### Codes de langue
|
|
577
564
|
|
|
578
|
-
|
|
|
579
|
-
|
|
580
|
-
| `
|
|
581
|
-
| `
|
|
582
|
-
| `modal` | string | Ouvrir un modal spécifique |
|
|
583
|
-
| `page` | number | Numéro de page (pagination) |
|
|
584
|
-
| `sort` | string | Tri des résultats |
|
|
565
|
+
| Code court | Code long | Description |
|
|
566
|
+
|------------|-----------|-------------|
|
|
567
|
+
| `fr` | `fr_FR` | Français (France) |
|
|
568
|
+
| `en` | `en_US` | Anglais (États-Unis) |
|
|
585
569
|
|
|
586
570
|
## 🔧 Build et Développement
|
|
587
571
|
|
|
@@ -593,32 +577,11 @@ const ErrorPage = () => {
|
|
|
593
577
|
"dev": "vite",
|
|
594
578
|
"build": "tsc && vite build",
|
|
595
579
|
"preview": "vite preview",
|
|
596
|
-
"type-check": "tsc --noEmit"
|
|
597
|
-
"generate-routes": "node scripts/generate-routes.js"
|
|
580
|
+
"type-check": "tsc --noEmit"
|
|
598
581
|
}
|
|
599
582
|
}
|
|
600
583
|
```
|
|
601
584
|
|
|
602
|
-
### Configuration Vite
|
|
603
|
-
|
|
604
|
-
```typescript
|
|
605
|
-
// vite.config.ts
|
|
606
|
-
import { defineConfig } from 'vite';
|
|
607
|
-
import react from '@vitejs/plugin-react';
|
|
608
|
-
|
|
609
|
-
export default defineConfig({
|
|
610
|
-
plugins: [react()],
|
|
611
|
-
resolve: {
|
|
612
|
-
alias: {}
|
|
613
|
-
},
|
|
614
|
-
build: {
|
|
615
|
-
rollupOptions: {
|
|
616
|
-
external: ['react', 'react-dom', 'react-router-dom']
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
```
|
|
621
|
-
|
|
622
585
|
### Configuration TypeScript
|
|
623
586
|
|
|
624
587
|
```json
|
|
@@ -630,7 +593,6 @@ export default defineConfig({
|
|
|
630
593
|
"skipLibCheck": true,
|
|
631
594
|
"moduleResolution": "bundler",
|
|
632
595
|
"allowImportingTsExtensions": true,
|
|
633
|
-
"resolveJsonModule": true,
|
|
634
596
|
"isolatedModules": true,
|
|
635
597
|
"noEmit": true,
|
|
636
598
|
"jsx": "react-jsx",
|
|
@@ -640,7 +602,7 @@ export default defineConfig({
|
|
|
640
602
|
"noFallthroughCasesInSwitch": true,
|
|
641
603
|
"types": ["vite/client"]
|
|
642
604
|
},
|
|
643
|
-
"include": ["src"]
|
|
605
|
+
"include": ["src", "node_modules/@arc-js/core/**/*"]
|
|
644
606
|
}
|
|
645
607
|
```
|
|
646
608
|
|
|
@@ -657,6 +619,6 @@ Envoyez nous un mail à l'adresse `contact.inicode@gmail.com` pour :
|
|
|
657
619
|
|
|
658
620
|
---
|
|
659
621
|
|
|
660
|
-
**@arc-js/core** -
|
|
622
|
+
**@arc-js/core** - Les hooks et utilitaires de routage avancés pour React et TypeScript.
|
|
661
623
|
|
|
662
624
|
*Développé par l'équipe INICODE*
|