@akinon/ui-shell-dev 1.3.5 → 1.4.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.
@@ -1,86 +1,145 @@
1
1
  import { useAppShell } from '@akinon/app-shell';
2
- import { Button, Card, Divider, Tag } from '@akinon/ui-react';
2
+ import { Card, Divider, Tag } from '@akinon/ui-react';
3
3
  import React from 'react';
4
- import { useNavigate } from 'react-router-dom';
4
+
5
+ import { ShellConfig } from '@/types';
6
+
7
+ // Component to display shared data from shell config
8
+ const SharedDataDisplay: React.FC<{ data: Record<string, unknown> }> = ({
9
+ data
10
+ }) => {
11
+ // Filter out default fields to show only custom data from config
12
+ const customData = { ...data };
13
+ delete customData.lng;
14
+ delete customData.username;
15
+ delete customData.searchParams;
16
+
17
+ return (
18
+ <pre
19
+ style={{
20
+ backgroundColor: '#1e1e1e',
21
+ color: '#d4d4d4',
22
+ padding: '16px',
23
+ borderRadius: '6px',
24
+ border: '1px solid #424b6b',
25
+ fontSize: '13px',
26
+ fontFamily:
27
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace',
28
+ lineHeight: '1.5',
29
+ overflow: 'auto',
30
+ margin: 0,
31
+ whiteSpace: 'pre-wrap',
32
+ wordBreak: 'break-word'
33
+ }}
34
+ >
35
+ {Object.keys(customData).length > 0
36
+ ? JSON.stringify(customData, null, 2)
37
+ : '// No custom data configured in shell.config.js\n// Add data to the "data" field to share it with your application'}
38
+ </pre>
39
+ );
40
+ };
5
41
 
6
42
  export const WelcomePage: React.FC = () => {
7
- const navigate = useNavigate();
8
43
  const { apps } = useAppShell();
9
- const app = apps[0]; // Get the first (and likely only) app
44
+
45
+ const config: ShellConfig =
46
+ ((window as unknown as { __SHELL_CONFIG__?: ShellConfig })
47
+ .__SHELL_CONFIG__ as ShellConfig) || {};
48
+ const data = config.data;
10
49
 
11
50
  return (
12
51
  <div>
13
- <Card title="Application Configuration" style={{ marginBottom: '24px' }}>
14
- <div
15
- style={{
16
- display: 'grid',
17
- gridTemplateColumns: '1fr 1fr',
18
- gap: '16px'
19
- }}
20
- >
21
- <div>
22
- <strong>Application Name:</strong>
23
- <p style={{ margin: '12px 0 4px 0' }}>
24
- {app?.name || 'Development App'}
25
- </p>
26
- </div>
27
- <div>
28
- <strong>Application Type:</strong>
29
- <p style={{ margin: '12px 0 4px 0' }}>
30
- <Tag color={app?.type === 'full_page' ? 'blue' : 'green'}>
31
- {app?.type === 'full_page' ? 'fullpage' : 'plugin'}
32
- </Tag>
33
- </p>
34
- </div>
35
- <div>
36
- <strong>Development URL:</strong>
37
- <p
38
- style={{
39
- margin: '12px 0 4px 0',
40
- color: '#999',
41
- fontFamily: 'monospace',
42
- fontSize: '13px'
43
- }}
44
- >
45
- {app?.url || 'http://localhost:4001'}
46
- </p>
47
- </div>
48
- <div>
49
- <strong>Shell Theme:</strong>
50
- <p style={{ margin: '12px 0 4px 0', color: '#999' }}>omnitron</p>
52
+ <Card title="Applications Configuration" className="mb-8">
53
+ {apps.length === 0 ? (
54
+ <p style={{ color: '#999' }}>No applications configured</p>
55
+ ) : (
56
+ <div
57
+ style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}
58
+ >
59
+ {apps.map((app, index) => (
60
+ <div
61
+ key={app.id}
62
+ style={{
63
+ borderBottom:
64
+ index < apps.length - 1 ? '1px solid #e0e0e0' : 'none',
65
+ paddingBottom: index < apps.length - 1 ? '20px' : '0'
66
+ }}
67
+ >
68
+ <h4 style={{ margin: '0 0 16px 0' }}>
69
+ {app.name} {apps.length > 1 && `(App ${index + 1})`}
70
+ </h4>
71
+ <div
72
+ style={{
73
+ display: 'grid',
74
+ gridTemplateColumns: '1fr 1fr',
75
+ gap: '16px'
76
+ }}
77
+ >
78
+ <div>
79
+ <strong>Application Type:</strong>
80
+ <p style={{ margin: '12px 0 4px 0' }}>
81
+ <Tag color={app.type === 'full_page' ? 'blue' : 'green'}>
82
+ {app.type === 'full_page' ? 'Full Page' : 'Plugin'}
83
+ </Tag>
84
+ </p>
85
+ </div>
86
+ <div>
87
+ <strong>Development URL:</strong>
88
+ <p
89
+ style={{
90
+ margin: '12px 0 4px 0',
91
+ color: '#999',
92
+ fontFamily: 'monospace',
93
+ fontSize: '13px'
94
+ }}
95
+ >
96
+ {app.url}
97
+ </p>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ ))}
102
+ <div style={{ marginTop: '10px' }}>
103
+ <strong>Shell Theme:</strong>
104
+ <p style={{ margin: '12px 0 4px 0', color: '#999' }}>
105
+ {config?.shell?.theme}
106
+ </p>
107
+ </div>
51
108
  </div>
52
- </div>
109
+ )}
53
110
  </Card>
54
111
 
55
- <Card title="Quick Actions" style={{ marginBottom: '24px' }}>
56
- <div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap' }}>
57
- <Button
58
- type="primary"
59
- onClick={() => navigate(`/external/${app?.slug}`)}
60
- >
61
- Launch Application
62
- </Button>
63
- {app?.type === 'plugin' && (
64
- <Button onClick={() => navigate('/plugin-test')}>
65
- Test Plugin Integration
66
- </Button>
67
- )}
68
- </div>
112
+ <Card title="Shared Data" className="mb-8">
113
+ {data && <SharedDataDisplay data={data} />}
69
114
  </Card>
70
115
 
71
- <Card title="Development Instructions">
116
+ <Card title="Development Instructions" className="mb-8">
72
117
  <div style={{ lineHeight: '1.6' }}>
73
118
  <h4 style={{ margin: '0 0 8px 0' }}>Getting Started:</h4>
74
119
  <ol style={{ paddingLeft: '20px' }}>
75
120
  <li>
76
- Make sure your application is running at{' '}
77
- <code>{app?.url || 'http://localhost:4001'}</code>
121
+ Make sure your application{apps.length > 1 ? 's are' : ' is'}{' '}
122
+ running at{' '}
123
+ <code>
124
+ {apps.length > 0 ? apps[0].url : 'http://localhost:4001'}
125
+ </code>
126
+ {apps.length > 1 && (
127
+ <>
128
+ {' and '}
129
+ {apps.slice(1).map((a, i) => (
130
+ <span key={a.id}>
131
+ {i > 0 && ', '}
132
+ <code>{a.url}</code>
133
+ </span>
134
+ ))}
135
+ </>
136
+ )}
78
137
  </li>
79
138
  <li>
80
139
  Click &quot;Launch Application&quot; to load your app in the shell
81
140
  environment
82
141
  </li>
83
- {app?.type === 'plugin' && (
142
+ {apps.some(a => a.type === 'plugin') && (
84
143
  <li>
85
144
  Use &quot;Test Plugin Integration&quot; to test your plugin in
86
145
  different placeholder configurations
@@ -94,24 +153,309 @@ export const WelcomePage: React.FC = () => {
94
153
  <Divider />
95
154
 
96
155
  <h4 style={{ margin: '16px 0 8px 0' }}>
97
- Application Type:{' '}
98
- {app?.type === 'full_page' ? 'fullpage' : 'plugin'}
156
+ Application Types:{' '}
157
+ {apps.map((a, i) => (
158
+ <span key={a.id}>
159
+ {i > 0 && ', '}
160
+ {a.type === 'full_page' ? 'Full Page' : 'Plugin'}
161
+ </span>
162
+ ))}
99
163
  </h4>
100
- {app?.type === 'full_page' ? (
164
+ {apps.some(a => a.type === 'full_page') && (
101
165
  <p style={{ margin: 0, color: '#666' }}>
102
- Your fullpage application will render as complete pages within the
103
- shell environment, providing full control over the content area
104
- and routing.
166
+ Full page applications render as complete pages within the shell
167
+ environment, providing full control over the content area and
168
+ routing.
105
169
  </p>
106
- ) : (
107
- <p style={{ margin: 0, color: '#666' }}>
108
- Your plugin application will render within designated placeholders
109
- in the host application, providing component-level integration
170
+ )}
171
+ {apps.some(a => a.type === 'plugin') && (
172
+ <p style={{ margin: '8px 0 0 0', color: '#666' }}>
173
+ Plugin applications render within designated placeholders in the
174
+ host application, providing component-level integration
110
175
  capabilities.
111
176
  </p>
112
177
  )}
113
178
  </div>
114
179
  </Card>
180
+
181
+ <Card title="Component Usage">
182
+ <div style={{ lineHeight: '1.6' }}>
183
+ <h4 style={{ margin: '0 0 8px 0' }}>@akinon/ui-* Components:</h4>
184
+ <p style={{ margin: '0 0 16px 0', color: '#999' }}>
185
+ This shell environment includes all Akinon UI components. You can
186
+ use them in your application by importing from the respective
187
+ packages.
188
+ </p>
189
+
190
+ <pre
191
+ style={{
192
+ backgroundColor: '#1e1e1e',
193
+ color: '#d4d4d4',
194
+ padding: '16px',
195
+ borderRadius: '6px',
196
+ border: '1px solid #424b6b',
197
+ fontSize: '13px',
198
+ fontFamily:
199
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace',
200
+ lineHeight: '1.5',
201
+ overflow: 'auto',
202
+ margin: 0,
203
+ whiteSpace: 'pre-wrap',
204
+ wordBreak: 'break-word'
205
+ }}
206
+ >
207
+ {`// Import components from @akinon/ui-react (main package)
208
+ import { Button, Card, Input, Modal } from '@akinon/ui-react';
209
+
210
+ // Or import from specific packages
211
+ import { Button } from '@akinon/ui-button';
212
+ import { Card } from '@akinon/ui-card';
213
+ import { Input } from '@akinon/ui-input';
214
+ import { Modal } from '@akinon/ui-modal';
215
+
216
+ // Usage in your component
217
+ function MyComponent() {
218
+ return (
219
+ <Card title="Example Card">
220
+ <Input placeholder="Enter text here" />
221
+ <Button type="primary">Submit</Button>
222
+ </Card>
223
+ );
224
+ }`}
225
+ </pre>
226
+
227
+ <h3 style={{ margin: '16px 0 8px 0' }}>Available Components:</h3>
228
+
229
+ <h4 style={{ margin: '24px 0 12px 0', fontSize: '16px' }}>
230
+ Data Display
231
+ </h4>
232
+ <div style={{ marginBottom: '20px' }}>
233
+ {[
234
+ { name: 'Avatar', pkg: '@akinon/ui-avatar' },
235
+ { name: 'Badge', pkg: '@akinon/ui-badge' },
236
+ { name: 'Carousel', pkg: '@akinon/ui-carousel' },
237
+ { name: 'Image', pkg: '@akinon/ui-image' },
238
+ { name: 'List', pkg: '@akinon/ui-list' },
239
+ { name: 'Popover', pkg: '@akinon/ui-popover' },
240
+ { name: 'Table', pkg: '@akinon/ui-table' },
241
+ { name: 'Tag', pkg: '@akinon/ui-tag' },
242
+ { name: 'Tooltip', pkg: '@akinon/ui-tooltip' },
243
+ { name: 'Tree', pkg: '@akinon/ui-tree' },
244
+ { name: 'Typography', pkg: '@akinon/ui-typography' }
245
+ ].map((component, index, array) => (
246
+ <div
247
+ key={component.name}
248
+ style={{
249
+ display: 'flex',
250
+ justifyContent: 'space-between',
251
+ alignItems: 'center',
252
+ padding: '8px 0',
253
+ borderBottom:
254
+ index < array.length - 1 ? '1px solid #4c5679' : 'none'
255
+ }}
256
+ >
257
+ <span>{component.name}</span>
258
+ <code
259
+ style={{
260
+ backgroundColor: '#1e1e1e',
261
+ color: '#d4d4d4',
262
+ padding: '4px 8px',
263
+ borderRadius: '4px',
264
+ border: '1px solid #424b6b',
265
+ fontSize: '12px',
266
+ fontFamily:
267
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace'
268
+ }}
269
+ >
270
+ {component.pkg}
271
+ </code>
272
+ </div>
273
+ ))}
274
+ </div>
275
+
276
+ <h4 style={{ margin: '24px 0 12px 0', fontSize: '16px' }}>
277
+ Feedback
278
+ </h4>
279
+ <div style={{ marginBottom: '20px' }}>
280
+ {[
281
+ { name: 'Alert', pkg: '@akinon/ui-alert' },
282
+ { name: 'Drawer', pkg: '@akinon/ui-drawer' },
283
+ { name: 'Message', pkg: '@akinon/ui-message' },
284
+ { name: 'Modal', pkg: '@akinon/ui-modal' },
285
+ { name: 'Notification', pkg: '@akinon/ui-notification' },
286
+ { name: 'Progress', pkg: '@akinon/ui-progress' },
287
+ { name: 'Result', pkg: '@akinon/ui-result' },
288
+ { name: 'Skeleton', pkg: '@akinon/ui-skeleton' },
289
+ { name: 'Spin', pkg: '@akinon/ui-spin' }
290
+ ].map((component, index, array) => (
291
+ <div
292
+ key={component.name}
293
+ style={{
294
+ display: 'flex',
295
+ justifyContent: 'space-between',
296
+ alignItems: 'center',
297
+ padding: '8px 0',
298
+ borderBottom:
299
+ index < array.length - 1 ? '1px solid #4c5679' : 'none'
300
+ }}
301
+ >
302
+ <span>{component.name}</span>
303
+ <code
304
+ style={{
305
+ backgroundColor: '#1e1e1e',
306
+ color: '#d4d4d4',
307
+ padding: '4px 8px',
308
+ borderRadius: '4px',
309
+ border: '1px solid #424b6b',
310
+ fontSize: '12px',
311
+ fontFamily:
312
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace'
313
+ }}
314
+ >
315
+ {component.pkg}
316
+ </code>
317
+ </div>
318
+ ))}
319
+ </div>
320
+
321
+ <h4 style={{ margin: '24px 0 12px 0', fontSize: '16px' }}>Form</h4>
322
+ <div style={{ marginBottom: '20px' }}>
323
+ {[
324
+ { name: 'AutoComplete', pkg: '@akinon/ui-auto-complete' },
325
+ { name: 'Button', pkg: '@akinon/ui-button' },
326
+ { name: 'Checkbox', pkg: '@akinon/ui-checkbox' },
327
+ { name: 'ColorPicker', pkg: '@akinon/ui-color-picker' },
328
+ { name: 'DatePicker', pkg: '@akinon/ui-date-picker' },
329
+ { name: 'Input', pkg: '@akinon/ui-input' },
330
+ { name: 'InputNumber', pkg: '@akinon/ui-input-number' },
331
+ { name: 'Radio', pkg: '@akinon/ui-radio' },
332
+ { name: 'Select', pkg: '@akinon/ui-select' },
333
+ { name: 'Slider', pkg: '@akinon/ui-slider' },
334
+ { name: 'Switch', pkg: '@akinon/ui-switch' },
335
+ { name: 'TimePicker', pkg: '@akinon/ui-time-picker' },
336
+ { name: 'Transfer', pkg: '@akinon/ui-transfer' },
337
+ { name: 'TreeSelect', pkg: '@akinon/ui-tree-select' },
338
+ { name: 'Upload', pkg: '@akinon/ui-upload' }
339
+ ].map((component, index, array) => (
340
+ <div
341
+ key={component.name}
342
+ style={{
343
+ display: 'flex',
344
+ justifyContent: 'space-between',
345
+ alignItems: 'center',
346
+ padding: '8px 0',
347
+ borderBottom:
348
+ index < array.length - 1 ? '1px solid #4c5679' : 'none'
349
+ }}
350
+ >
351
+ <span>{component.name}</span>
352
+ <code
353
+ style={{
354
+ backgroundColor: '#1e1e1e',
355
+ color: '#d4d4d4',
356
+ padding: '4px 8px',
357
+ borderRadius: '4px',
358
+ border: '1px solid #424b6b',
359
+ fontSize: '12px',
360
+ fontFamily:
361
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace'
362
+ }}
363
+ >
364
+ {component.pkg}
365
+ </code>
366
+ </div>
367
+ ))}
368
+ </div>
369
+
370
+ <h4 style={{ margin: '24px 0 12px 0', fontSize: '16px' }}>Layout</h4>
371
+ <div style={{ marginBottom: '20px' }}>
372
+ {[
373
+ { name: 'Card', pkg: '@akinon/ui-card' },
374
+ { name: 'Collapse', pkg: '@akinon/ui-collapse' },
375
+ { name: 'Divider', pkg: '@akinon/ui-divider' },
376
+ { name: 'Layout', pkg: '@akinon/ui-layout' },
377
+ { name: 'Space', pkg: '@akinon/ui-space' }
378
+ ].map((component, index, array) => (
379
+ <div
380
+ key={component.name}
381
+ style={{
382
+ display: 'flex',
383
+ justifyContent: 'space-between',
384
+ alignItems: 'center',
385
+ padding: '8px 0',
386
+ borderBottom:
387
+ index < array.length - 1 ? '1px solid #4c5679' : 'none'
388
+ }}
389
+ >
390
+ <span>{component.name}</span>
391
+ <code
392
+ style={{
393
+ backgroundColor: '#1e1e1e',
394
+ color: '#d4d4d4',
395
+ padding: '4px 8px',
396
+ borderRadius: '4px',
397
+ border: '1px solid #424b6b',
398
+ fontSize: '12px',
399
+ fontFamily:
400
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace'
401
+ }}
402
+ >
403
+ {component.pkg}
404
+ </code>
405
+ </div>
406
+ ))}
407
+ </div>
408
+
409
+ <h4 style={{ margin: '24px 0 12px 0', fontSize: '16px' }}>
410
+ Navigation
411
+ </h4>
412
+ <div style={{ marginBottom: '20px' }}>
413
+ {[
414
+ { name: 'Breadcrumb', pkg: '@akinon/ui-breadcrumb' },
415
+ { name: 'Dropdown', pkg: '@akinon/ui-dropdown' },
416
+ { name: 'Menu', pkg: '@akinon/ui-menu' },
417
+ { name: 'Pagination', pkg: '@akinon/ui-pagination' },
418
+ { name: 'Segmented', pkg: '@akinon/ui-segmented' },
419
+ { name: 'Steps', pkg: '@akinon/ui-steps' },
420
+ { name: 'Tour', pkg: '@akinon/ui-tour' }
421
+ ].map((component, index, array) => (
422
+ <div
423
+ key={component.name}
424
+ style={{
425
+ display: 'flex',
426
+ justifyContent: 'space-between',
427
+ alignItems: 'center',
428
+ padding: '8px 0',
429
+ borderBottom:
430
+ index < array.length - 1 ? '1px solid #4c5679' : 'none'
431
+ }}
432
+ >
433
+ <span>{component.name}</span>
434
+ <code
435
+ style={{
436
+ backgroundColor: '#1e1e1e',
437
+ color: '#d4d4d4',
438
+ padding: '4px 8px',
439
+ borderRadius: '4px',
440
+ border: '1px solid #424b6b',
441
+ fontSize: '12px',
442
+ fontFamily:
443
+ '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace'
444
+ }}
445
+ >
446
+ {component.pkg}
447
+ </code>
448
+ </div>
449
+ ))}
450
+ </div>
451
+
452
+ <p style={{ margin: '0', color: '#999', fontSize: '14px' }}>
453
+ <strong>Note:</strong> All components follow Ant Design API
454
+ conventions with Akinon-specific styling and theming. Check the
455
+ Storybook documentation for detailed usage examples and props.
456
+ </p>
457
+ </div>
458
+ </Card>
115
459
  </div>
116
460
  );
117
461
  };
package/src/main.tsx CHANGED
@@ -12,12 +12,14 @@ import type { ShellConfig } from './types';
12
12
  const config: ShellConfig = (
13
13
  window as unknown as { __SHELL_CONFIG__?: ShellConfig }
14
14
  ).__SHELL_CONFIG__ || {
15
- plugin: {
16
- url: 'http://localhost:4001',
17
- path: './src',
18
- name: 'Development App',
19
- type: 'fullpage'
20
- },
15
+ apps: [
16
+ {
17
+ url: 'http://localhost:4001',
18
+ path: './src',
19
+ name: 'Development App',
20
+ type: 'full_page'
21
+ }
22
+ ],
21
23
  shell: {
22
24
  port: 4000,
23
25
  theme: 'omnitron',
package/src/providers.tsx CHANGED
@@ -17,19 +17,17 @@ export const Providers: React.FC<ProvidersProps> = ({ config, children }) => {
17
17
  const navigate = useNavigate();
18
18
 
19
19
  // Convert shell config to apps format for AppShellProvider
20
- const apps: RegisteredApp[] = [
21
- {
22
- id: 1,
23
- url: config.plugin.url,
24
- slug: config.plugin.name.toLowerCase().replace(/\s+/g, '-'),
25
- path: '', // Empty path for development like playground
26
- type: config.plugin.type === 'fullpage' ? 'full_page' : 'plugin',
27
- name: config.plugin.name
28
- }
29
- ];
20
+ const apps: RegisteredApp[] = config.apps.map((app, index) => ({
21
+ id: index + 1,
22
+ url: app.url,
23
+ slug: app.name.toLowerCase().replace(/\s+/g, '-'),
24
+ path: '', // Empty path for development like playground
25
+ type: app.type,
26
+ name: app.navTitle || app.name // Use navTitle if provided, otherwise use name
27
+ }));
30
28
 
31
29
  const navigation: ShellNavigation = {
32
- navigate: ({ id, path, external }: any) => {
30
+ navigate: ({ id, path, external }) => {
33
31
  const app = apps.find(app => app.id === id);
34
32
  if (!app) return;
35
33
 
@@ -41,7 +39,8 @@ export const Providers: React.FC<ProvidersProps> = ({ config, children }) => {
41
39
  const data = {
42
40
  lng: 'en',
43
41
  username: 'Developer',
44
- searchParams: Object.fromEntries([...searchParams])
42
+ searchParams: Object.fromEntries([...searchParams]),
43
+ ...(config.data || {})
45
44
  };
46
45
 
47
46
  const defaultActions = {
@@ -83,7 +82,9 @@ export const Providers: React.FC<ProvidersProps> = ({ config, children }) => {
83
82
  'logMessages',
84
83
  JSON.stringify([...prevItems, newItem])
85
84
  );
86
- } catch {}
85
+ } catch {
86
+ console.error('Failed to log message');
87
+ }
87
88
  }
88
89
  };
89
90
 
package/src/routes.tsx CHANGED
@@ -16,24 +16,25 @@ export const Routes = () => {
16
16
  <Route key={app.id} path={`/external/${app.slug}`}>
17
17
  <Route index element={<PageExternal id={app.id} path={app.path} />} />
18
18
 
19
- {(configs.get(app.id) as any)?.menu?.map((sub: any) => {
20
- return (
21
- sub.path !== '/' && (
22
- <Route
23
- key={sub.path}
24
- path={`/external/${app.slug}${sub.path}`}
25
- element={
26
- <PageExternal
27
- id={app.id}
28
- path={
29
- app.subSlug ? `${app.subSlug}${sub.path}` : sub.path
30
- }
31
- />
32
- }
33
- />
34
- )
35
- );
36
- })}
19
+ {(configs.get(app.id) as unknown as { menu: unknown[] })?.menu?.map(
20
+ sub => {
21
+ const { path } = sub as { path: string };
22
+ return (
23
+ path !== '/' && (
24
+ <Route
25
+ key={path}
26
+ path={`/external/${app.slug}${path}`}
27
+ element={
28
+ <PageExternal
29
+ id={app.id}
30
+ path={app.subSlug ? `${app.subSlug}${path}` : path}
31
+ />
32
+ }
33
+ />
34
+ )
35
+ );
36
+ }
37
+ )}
37
38
  </Route>
38
39
  ))}
39
40