@djangocfg/layouts 2.1.427 → 2.1.429

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.
@@ -61,12 +61,14 @@ function ApiKeyCard() {
61
61
  const masked = apiKey ? isMasked(apiKey) : false;
62
62
  const displayKey = apiKey ?? '—';
63
63
 
64
- // Title-row state badge (read-only "Active" indicator).
64
+ // Title-row state badge (read-only "Active" indicator). A `div` wrapper —
65
+ // `Badge` renders a block `<div>`, which is invalid inside a `<span>`/`<p>`.
66
+ // SettingRow renders `description` inside a block, so this is valid.
65
67
  const titleDescription = (
66
- <span className="inline-flex items-center gap-2">
68
+ <div className="flex flex-wrap items-center gap-2">
67
69
  {apiKey && <Badge variant="secondary" className="text-xs">Active</Badge>}
68
70
  <span>{labels.description}</span>
69
- </span>
71
+ </div>
70
72
  );
71
73
 
72
74
  // Action buttons (arm/regenerate/test) for the title row.
@@ -87,31 +89,17 @@ function ApiKeyCard() {
87
89
  </Button>
88
90
  </div>
89
91
  ) : (
90
- <div className="flex items-center gap-2">
91
- <Button
92
- variant="outline"
93
- size="sm"
94
- onClick={arm}
95
- disabled={!apiKey || isRegenerating}
96
- >
97
- <RefreshCw className="mr-2 h-4 w-4" />
98
- {labels.regenerate}
99
- </Button>
100
-
101
- {/* Test button only when we have a fresh (full) key */}
102
- {isFresh && (
103
- <Button
104
- variant="secondary"
105
- size="sm"
106
- onClick={testKey}
107
- disabled={isTesting}
108
- >
109
- {isTesting
110
- ? <><Loader2 className="mr-2 h-4 w-4 animate-spin" />{labels.testing}</>
111
- : <><FlaskConical className="mr-2 h-4 w-4" />{labels.test}</>}
112
- </Button>
113
- )}
114
- </div>
92
+ // Title row keeps a single action (Regenerate). The Test action moves next
93
+ // to the fresh key below, so this row stays uncluttered.
94
+ <Button
95
+ variant="outline"
96
+ size="sm"
97
+ onClick={arm}
98
+ disabled={!apiKey || isRegenerating}
99
+ >
100
+ <RefreshCw className="mr-2 h-4 w-4" />
101
+ {labels.regenerate}
102
+ </Button>
115
103
  );
116
104
 
117
105
  return (
@@ -157,8 +145,23 @@ function ApiKeyCard() {
157
145
  </span>
158
146
  </div>
159
147
 
160
- {/* Copy only when the key is fresh (full key after regenerate) */}
161
- {isFresh && <CopyButton value={apiKey} />}
148
+ {/* Copy + Test only when the key is fresh (full key after regenerate) */}
149
+ {isFresh && (
150
+ <>
151
+ <CopyButton value={apiKey} />
152
+ <Button
153
+ variant="secondary"
154
+ size="sm"
155
+ onClick={testKey}
156
+ disabled={isTesting}
157
+ className="shrink-0"
158
+ >
159
+ {isTesting
160
+ ? <><Loader2 className="mr-2 h-4 w-4 animate-spin" />{labels.testing}</>
161
+ : <><FlaskConical className="mr-2 h-4 w-4" />{labels.test}</>}
162
+ </Button>
163
+ </>
164
+ )}
162
165
  </div>
163
166
  </SettingRow>
164
167
  )}
@@ -27,7 +27,6 @@ export * from './AppLayout';
27
27
  export * from './PublicLayout';
28
28
  export * from './PrivateLayout';
29
29
  export * from './AuthLayout';
30
- export * from './AdminLayout';
31
30
 
32
31
  // Additional layouts
33
32
  export * from './SettingsLayout';
@@ -11,15 +11,20 @@ import { createConsola } from 'consola';
11
11
  * - 3: log, info
12
12
  * - 4: debug
13
13
  * - 5: trace, verbose
14
+ *
15
+ * Level is driven by the global runtime controller in @djangocfg/api so admins
16
+ * get verbose logs (even in prod) and regular users keep a clean console.
14
17
  */
15
- import { isDev as isDevelopment } from '@djangocfg/ui-core/lib';
16
- const isStaticBuild = process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';
17
- const showLogs = isDevelopment || isStaticBuild;
18
+ import { getLogLevel, onLogLevelChange } from '@djangocfg/api';
18
19
 
19
20
  export const logger = createConsola({
20
- level: showLogs ? 4 : 1, // dev: debug, production: errors only
21
+ level: getLogLevel(),
21
22
  }).withTag('layouts');
22
23
 
24
+ onLogLevelChange((level) => {
25
+ logger.level = level;
26
+ });
27
+
23
28
  // ─────────────────────────────────────────────────────────────────────────
24
29
  // Module-specific loggers
25
30
  // ─────────────────────────────────────────────────────────────────────────
@@ -1,57 +0,0 @@
1
- /**
2
- * Admin Layout
3
- *
4
- * Layout for admin dashboard pages
5
- * Import and use directly with props - no complex configs needed!
6
- *
7
- * @example
8
- * ```tsx
9
- * import { AdminLayout } from '@djangocfg/layouts';
10
- *
11
- * <AdminLayout
12
- * sidebar={{
13
- * items: [
14
- * { label: 'Overview', href: '/admin', icon: 'LayoutDashboard' },
15
- * { label: 'Users', href: '/admin/users', icon: 'Users' }
16
- * ]
17
- * }}
18
- * header={{
19
- * title: 'Admin Dashboard',
20
- * groups: [
21
- * {
22
- * title: 'Admin',
23
- * items: [
24
- * { label: 'Profile', href: '/profile' },
25
- * { label: 'Settings', href: '/settings' }
26
- * ]
27
- * }
28
- * ],
29
- * authPath: '/auth'
30
- * }}
31
- * >
32
- * {children}
33
- * </AdminLayout>
34
- * ```
35
- */
36
-
37
- 'use client';
38
-
39
- import { ReactNode } from 'react';
40
-
41
- import { PrivateLayout } from '../PrivateLayout';
42
- import type { PrivateLayoutProps } from '../PrivateLayout';
43
-
44
- export interface AdminLayoutProps extends PrivateLayoutProps {
45
- children: ReactNode;
46
- }
47
-
48
- /**
49
- * Admin Layout Component
50
- *
51
- * Wrapper around PrivateLayout with admin-specific defaults
52
- * Same API as PrivateLayout - just a convenience export
53
- */
54
- export function AdminLayout(props: AdminLayoutProps) {
55
- return <PrivateLayout {...props} />;
56
- }
57
-
@@ -1,7 +0,0 @@
1
- /**
2
- * Admin Layout exports
3
- */
4
-
5
- export { AdminLayout } from './AdminLayout';
6
- export type { AdminLayoutProps } from './AdminLayout';
7
-
@@ -1,513 +0,0 @@
1
- /**
2
- * AppLayout - Smart Layout Router with All Providers
3
- *
4
- * Automatically detects route type and applies the correct layout
5
- * Includes all necessary providers: Theme, Auth, Analytics, Centrifugo, Error Tracking
6
- * Simple props-based configuration - no complex configs needed!
7
- *
8
- * @example
9
- * ```tsx
10
- * import { AppLayout } from '@djangocfg/layouts';
11
- *
12
- * <AppLayout
13
- * publicLayout={{
14
- * component: PublicLayout,
15
- * enabledPath: ['/', '/about', '/contact']
16
- * }}
17
- * privateLayout={{
18
- * component: DashboardLayout,
19
- * enabledPath: '/dashboard'
20
- * }}
21
- * adminLayout={{
22
- * component: AdminLayout,
23
- * enabledPath: '/admin'
24
- * }}
25
- * // Paths that render without any layout wrapper (fullscreen pages)
26
- * noLayoutPaths={['/private/terminal', '/embed']}
27
- * >
28
- * {children}
29
- * </AppLayout>
30
- * ```
31
- */
32
-
33
- 'use client';
34
-
35
- import React, { ReactNode, memo, useMemo } from 'react';
36
-
37
- import { ClientOnly, Suspense } from '../../components/core';
38
- import { usePathnameWithoutLocale } from '../../hooks';
39
- import { matchesPath } from '../../utils/pathMatcher';
40
- import { BaseApp } from './BaseApp';
41
- import { useLayoutI18nOptional } from './LayoutI18nProvider';
42
- import { ForcedThemeProvider, ThemeOverride, resolveForcedTheme } from '@djangocfg/ui-core/theme';
43
-
44
- import type {
45
- ThemeConfig,
46
- AnalyticsConfig,
47
- CentrifugoConfig,
48
- ErrorTrackingConfig,
49
- ErrorBoundaryConfig,
50
- SWRConfigOptions,
51
- DebugConfig,
52
- I18nLayoutConfig,
53
- } from '../types';
54
- export type { I18nLayoutConfig } from '../types';
55
- import type { AuthConfig } from '@djangocfg/api/auth';
56
- import type { MonitorConfig } from '@djangocfg/monitor';
57
- import type { ThemeOverrideRule } from '@djangocfg/ui-core/theme';
58
-
59
- import type { FloatingNavbarConfig } from '../PublicLayout/navbars/FloatingNavbar';
60
- import type { DefaultFooterConfig } from '../PublicLayout/footers/DefaultFooter/types';
61
-
62
- export type LayoutMode = 'public' | 'private' | 'admin';
63
-
64
- /** `<main>` spacing in `PublicLayout` — offset from navbar / padding before footer. */
65
- export type PublicMainTopSpacing = 'auto' | 'none';
66
-
67
- /** Bottom padding of `<main>` above the footer. `compact` = less than `auto`; `none` = no extra gap. */
68
- export type PublicMainBottomSpacing = 'auto' | 'none' | 'compact';
69
-
70
- /** Shared marketing chrome defaults for `FloatingNavbar` / `DefaultFooter`, merged into your public layout. */
71
- export interface AppLayoutPublicChrome {
72
- navbar?: Partial<FloatingNavbarConfig>;
73
- footer?: Partial<DefaultFooterConfig>;
74
- /**
75
- * Passed through to `PublicLayout` as `contentTopSpacing` / `contentBottomSpacing`.
76
- * Use `bottomSpacing: 'none'` when the page (or footer) should sit flush with no default gap.
77
- */
78
- main?: {
79
- topSpacing?: PublicMainTopSpacing;
80
- bottomSpacing?: PublicMainBottomSpacing;
81
- };
82
- /**
83
- * Full-viewport background layer rendered behind the navbar and all page content.
84
- * Pass a `fixed inset-0 -z-10 pointer-events-none` element — it covers the whole
85
- * viewport (including the sticky navbar area) without affecting layout flow.
86
- *
87
- * Set per-page via `AppLayout publicChrome` or directly on `PublicSiteLayout`.
88
- */
89
- backgroundSlot?: ReactNode;
90
- }
91
-
92
- function mergePartialNavbar(
93
- base?: Partial<FloatingNavbarConfig>,
94
- overlay?: Partial<FloatingNavbarConfig>
95
- ): Partial<FloatingNavbarConfig> | undefined {
96
- if (!base && !overlay) return undefined;
97
- const merged: Partial<FloatingNavbarConfig> = { ...base, ...overlay };
98
- if (base?.shell || overlay?.shell) {
99
- merged.shell = { ...base?.shell, ...overlay?.shell };
100
- }
101
- return merged;
102
- }
103
-
104
- function mergePartialFooter(
105
- base?: Partial<DefaultFooterConfig>,
106
- overlay?: Partial<DefaultFooterConfig>
107
- ): Partial<DefaultFooterConfig> | undefined {
108
- if (!base && !overlay) return undefined;
109
- const merged: Partial<DefaultFooterConfig> = { ...base, ...overlay };
110
- if (base?.shell || overlay?.shell) merged.shell = { ...base?.shell, ...overlay?.shell };
111
- if (base?.brand || overlay?.brand) merged.brand = { ...base?.brand, ...overlay?.brand };
112
- if (base?.menus || overlay?.menus) merged.menus = { ...base?.menus, ...overlay?.menus };
113
- if (base?.meta || overlay?.meta) merged.meta = { ...base?.meta, ...overlay?.meta };
114
- if (base?.social || overlay?.social) merged.social = { ...base?.social, ...overlay?.social };
115
- if (base?.controls || overlay?.controls) {
116
- merged.controls = { ...base?.controls, ...overlay?.controls };
117
- }
118
- return merged;
119
- }
120
-
121
- /**
122
- * Deep-merge public chrome: root `publicChrome` first, then `layouts.publicChrome` overlays.
123
- */
124
- export function mergeAppLayoutPublicChrome(
125
- root?: AppLayoutPublicChrome,
126
- fromLayouts?: AppLayoutPublicChrome
127
- ): AppLayoutPublicChrome | undefined {
128
- if (!root && !fromLayouts) return undefined;
129
- const navbar = mergePartialNavbar(root?.navbar, fromLayouts?.navbar);
130
- const footer = mergePartialFooter(root?.footer, fromLayouts?.footer);
131
- const main =
132
- root?.main || fromLayouts?.main
133
- ? { ...root?.main, ...fromLayouts?.main }
134
- : undefined;
135
- // backgroundSlot: layouts-level overrides root (more specific wins)
136
- const backgroundSlot = fromLayouts?.backgroundSlot ?? root?.backgroundSlot;
137
- if (!navbar && !footer && !main && !backgroundSlot) return undefined;
138
- return { navbar, footer, ...(main ? { main } : {}), ...(backgroundSlot ? { backgroundSlot } : {}) };
139
- }
140
-
141
- /**
142
- * Determine layout mode from pathname and enabledPath props
143
- */
144
- function determineLayoutMode(
145
- pathname: string,
146
- adminLayout?: { component: any; enabledPath?: string | string[] },
147
- privateLayout?: { component: any; enabledPath?: string | string[] },
148
- publicLayout?: { component: any; enabledPath?: string | string[] }
149
- ): LayoutMode {
150
- // Check in order: admin -> private -> public
151
- if (adminLayout && matchesPath(pathname, adminLayout.enabledPath)) return 'admin';
152
- if (privateLayout && matchesPath(pathname, privateLayout.enabledPath)) return 'private';
153
- if (publicLayout && matchesPath(pathname, publicLayout.enabledPath)) return 'public';
154
-
155
- // Default: if no enabledPath specified, use public as fallback
156
- return 'public';
157
- }
158
-
159
- /**
160
- * Props passed to every layout component (`public` / `private` / `admin`).
161
- * Use `publicChrome` to pass defaults for `FloatingNavbar` / `DefaultFooter` from `AppLayout`.
162
- *
163
- * Locale plumbing is no longer threaded as a prop — layouts read it from
164
- * `useLayoutI18nOptional()` (mounted in `BaseApp`).
165
- */
166
- export interface AppLayoutLayoutComponentProps {
167
- children: ReactNode;
168
- publicChrome?: AppLayoutPublicChrome;
169
- }
170
-
171
- /** Layout configuration with component and enabled paths */
172
- interface LayoutConfig {
173
- component: React.ComponentType<AppLayoutLayoutComponentProps>;
174
- enabledPath?: string | string[];
175
- }
176
-
177
- export interface AppLayoutLayoutsConfig {
178
- public?: LayoutConfig;
179
- private?: LayoutConfig;
180
- admin?: LayoutConfig;
181
- noLayoutPaths?: string | string[];
182
- authPath?: string;
183
- /** Merged over root `publicChrome` on `AppLayout` (see `mergeAppLayoutPublicChrome`). */
184
- publicChrome?: AppLayoutPublicChrome;
185
- /**
186
- * Per-route theme overrides. Each rule uses the same matcher as `enabledPath`
187
- * (string, string[], or glob with `*` / `**`). When the pathname matches a
188
- * rule, the forced theme is applied via `next-themes`; when navigation leaves
189
- * the matched path, the user's previous theme choice is restored.
190
- *
191
- * Rules are evaluated top-to-bottom — first match wins.
192
- *
193
- * @example
194
- * ```ts
195
- * themeOverrides: [
196
- * { path: '/', theme: 'dark' }, // marketing landing always dark
197
- * { path: ['/legal', '/legal/**'], theme: 'light' }, // legal always light
198
- * ]
199
- * ```
200
- */
201
- themeOverrides?: ThemeOverrideRule[];
202
- }
203
-
204
- export interface AppLayoutBaseAppConfig {
205
- project?: string;
206
- theme?: ThemeConfig;
207
- auth?: AuthConfig;
208
- analytics?: AnalyticsConfig;
209
- centrifugo?: CentrifugoConfig;
210
- errorTracking?: ErrorTrackingConfig;
211
- swr?: SWRConfigOptions;
212
- errorBoundary?: ErrorBoundaryConfig;
213
- monitor?: MonitorConfig;
214
- debug?: DebugConfig;
215
- }
216
-
217
- export interface AppLayoutProps {
218
- children: ReactNode;
219
-
220
- /** Compact routing/layout config */
221
- layouts?: AppLayoutLayoutsConfig;
222
- /** Compact providers/base-app config */
223
- baseApp?: AppLayoutBaseAppConfig;
224
-
225
- /** Project name — used as default for monitor.project and debug panel title */
226
- project?: string;
227
-
228
- /** Public layout component with enabled paths */
229
- publicLayout?: LayoutConfig;
230
-
231
- /** Private layout component with enabled paths */
232
- privateLayout?: LayoutConfig;
233
-
234
- /** Admin layout component with enabled paths */
235
- adminLayout?: LayoutConfig;
236
-
237
- /**
238
- * Paths that render without any layout wrapper (fullscreen pages)
239
- * Providers (auth, theme, etc.) are still applied, only layout is skipped
240
- * Useful for: fullscreen terminal, embed pages, print views
241
- */
242
- noLayoutPaths?: string | string[];
243
-
244
- /**
245
- * Auth path prefix. When set, all routes starting with this path
246
- * (and their localized variants like /ru/auth, /en/auth) render without
247
- * any layout wrapper — fullscreen, no navbar/sidebar.
248
- * @default '/auth'
249
- * @example authPath="/auth" → skips layout for /auth, /auth/*, /ru/auth, /ru/auth/*, etc.
250
- */
251
- authPath?: string;
252
-
253
- /** Theme configuration */
254
- theme?: ThemeConfig;
255
-
256
- /** Auth configuration */
257
- auth?: AuthConfig;
258
-
259
- /** Analytics configuration */
260
- analytics?: AnalyticsConfig;
261
-
262
- /** Centrifugo configuration */
263
- centrifugo?: CentrifugoConfig;
264
-
265
- /** Error tracking configuration */
266
- errorTracking?: ErrorTrackingConfig;
267
-
268
- /** SWR configuration */
269
- swr?: SWRConfigOptions;
270
-
271
- /** Error boundary configuration */
272
- errorBoundary?: ErrorBoundaryConfig;
273
-
274
- /** i18n configuration for locale switching (applies to all layouts) */
275
- i18n?: I18nLayoutConfig;
276
-
277
- /** Base layer for `publicChrome`; `layouts.publicChrome` overlays this. */
278
- publicChrome?: AppLayoutPublicChrome;
279
-
280
- /**
281
- * Per-route theme overrides. Shortcut for `layouts.themeOverrides`.
282
- * See `AppLayoutLayoutsConfig.themeOverrides` for the full docs.
283
- */
284
- themeOverrides?: ThemeOverrideRule[];
285
-
286
- /** Monitor configuration — initialises window.monitor + auto-captures JS errors & console */
287
- monitor?: MonitorConfig;
288
-
289
- debug?: DebugConfig;
290
- }
291
-
292
- interface AppLayoutContentProps {
293
- children: ReactNode;
294
- publicLayout?: LayoutConfig;
295
- privateLayout?: LayoutConfig;
296
- adminLayout?: LayoutConfig;
297
- noLayoutPaths?: string | string[];
298
- authPath?: string;
299
- publicChrome?: AppLayoutPublicChrome;
300
- themeOverrides?: ThemeOverrideRule[];
301
- }
302
-
303
- /**
304
- * AppLayout Content - Renders layout with all providers
305
- *
306
- * SSR is only enabled for publicLayout.
307
- * Private and admin layouts are wrapped in ClientOnly to avoid hydration mismatch.
308
- */
309
- function AppLayoutContentRaw({
310
- children,
311
- publicLayout,
312
- privateLayout,
313
- adminLayout,
314
- noLayoutPaths,
315
- authPath = '/auth',
316
- publicChrome,
317
- themeOverrides,
318
- }: AppLayoutContentProps) {
319
- // Use pathname without locale prefix for route matching. The current
320
- // locale comes from the LayoutI18nProvider mounted by BaseApp — passing
321
- // it explicitly avoids the regex misfiring on `/ui`, `/ko`, etc.
322
- const i18n = useLayoutI18nOptional();
323
- const pathname = usePathnameWithoutLocale(i18n?.locale);
324
-
325
- // Merge authPath into noLayoutPaths — auth pages are always fullscreen
326
- const effectiveNoLayoutPaths = useMemo(() => {
327
- const base = noLayoutPaths
328
- ? (Array.isArray(noLayoutPaths) ? noLayoutPaths : [noLayoutPaths])
329
- : []
330
- return [...base, authPath]
331
- }, [noLayoutPaths, authPath])
332
-
333
- // Check if current path should skip layout
334
- const shouldSkipLayout = useMemo(
335
- () => matchesPath(pathname, effectiveNoLayoutPaths),
336
- [pathname, effectiveNoLayoutPaths]
337
- );
338
-
339
- const layoutMode = useMemo(
340
- () => determineLayoutMode(
341
- pathname,
342
- adminLayout,
343
- privateLayout,
344
- publicLayout
345
- ),
346
- [pathname, adminLayout, privateLayout, publicLayout]
347
- );
348
-
349
- // Prepare everything above the JSX — no inline conditionals in return().
350
- const hasThemeOverrides = Boolean(themeOverrides && themeOverrides.length > 0);
351
- const forcedTheme = hasThemeOverrides
352
- ? resolveForcedTheme(pathname, themeOverrides)
353
- : null;
354
- const themeOverrideElement = hasThemeOverrides
355
- ? <ThemeOverride pathname={pathname} rules={themeOverrides!} />
356
- : null;
357
-
358
- // Memoize layout element so it doesn't re-render on every pathname change
359
- // that doesn't affect layout mode (e.g. /dashboard/a → /dashboard/b).
360
- const layoutElement = useMemo(() => {
361
- // Skip layout for noLayoutPaths (fullscreen pages)
362
- if (shouldSkipLayout) {
363
- return children;
364
- }
365
-
366
- switch (layoutMode) {
367
- case 'admin':
368
- if (!adminLayout && privateLayout) {
369
- return (
370
- <ClientOnly>
371
- <Suspense>
372
- <privateLayout.component publicChrome={publicChrome}>
373
- {children}
374
- </privateLayout.component>
375
- </Suspense>
376
- </ClientOnly>
377
- );
378
- }
379
- if (!adminLayout) {
380
- return children;
381
- }
382
- return (
383
- <ClientOnly>
384
- <Suspense>
385
- <adminLayout.component publicChrome={publicChrome}>
386
- {children}
387
- </adminLayout.component>
388
- </Suspense>
389
- </ClientOnly>
390
- );
391
-
392
- case 'private':
393
- if (!privateLayout) {
394
- if (publicLayout) {
395
- return (
396
- <publicLayout.component publicChrome={publicChrome}>
397
- {children}
398
- </publicLayout.component>
399
- );
400
- }
401
- return children;
402
- }
403
- return (
404
- <ClientOnly>
405
- <Suspense>
406
- <privateLayout.component publicChrome={publicChrome}>
407
- {children}
408
- </privateLayout.component>
409
- </Suspense>
410
- </ClientOnly>
411
- );
412
-
413
- case 'public':
414
- default:
415
- // Public layout renders with SSR (no ClientOnly wrapper)
416
- if (!publicLayout) {
417
- return children;
418
- }
419
- return (
420
- <publicLayout.component publicChrome={publicChrome}>
421
- {children}
422
- </publicLayout.component>
423
- );
424
- }
425
- }, [shouldSkipLayout, layoutMode, publicLayout, privateLayout, adminLayout, publicChrome, children]);
426
-
427
- // No providers here - all providers now in BaseApp
428
- return (
429
- <ForcedThemeProvider value={forcedTheme}>
430
- {themeOverrideElement}
431
- {layoutElement}
432
- </ForcedThemeProvider>
433
- );
434
- }
435
-
436
- /**
437
- * Memoised layout content wrapper. Re-renders only when layout config props
438
- * change (publicLayout, privateLayout, adminLayout, noLayoutPaths, etc.).
439
- * The `children` prop is compared by reference — the consumer should wrap
440
- * page content in React.memo or use stable element references to avoid
441
- * unnecessary layout re-mounts on every parent render.
442
- */
443
- const AppLayoutContent = memo(AppLayoutContentRaw, (prev, next) => {
444
- return (
445
- prev.children === next.children &&
446
- prev.publicLayout === next.publicLayout &&
447
- prev.privateLayout === next.privateLayout &&
448
- prev.adminLayout === next.adminLayout &&
449
- prev.noLayoutPaths === next.noLayoutPaths &&
450
- prev.authPath === next.authPath &&
451
- prev.publicChrome === next.publicChrome &&
452
- prev.themeOverrides === next.themeOverrides
453
- );
454
- });
455
-
456
- /**
457
- * AppLayout - Main Component with All Providers
458
- */
459
- export function AppLayout(props: AppLayoutProps) {
460
- const layoutsConfig = props.layouts;
461
- const baseAppConfig = props.baseApp;
462
-
463
- const publicLayout = layoutsConfig?.public ?? props.publicLayout;
464
- const privateLayout = layoutsConfig?.private ?? props.privateLayout;
465
- const adminLayout = layoutsConfig?.admin ?? props.adminLayout;
466
- const noLayoutPaths = layoutsConfig?.noLayoutPaths ?? props.noLayoutPaths;
467
- const authPath = layoutsConfig?.authPath ?? props.authPath;
468
- const publicChrome = mergeAppLayoutPublicChrome(props.publicChrome, layoutsConfig?.publicChrome);
469
- const themeOverrides = layoutsConfig?.themeOverrides ?? props.themeOverrides;
470
-
471
- const {
472
- i18n,
473
- children,
474
- } = props;
475
-
476
- const project = baseAppConfig?.project ?? props.project;
477
- const theme = baseAppConfig?.theme ?? props.theme;
478
- const auth = baseAppConfig?.auth ?? props.auth;
479
- const analytics = baseAppConfig?.analytics ?? props.analytics;
480
- const centrifugo = baseAppConfig?.centrifugo ?? props.centrifugo;
481
- const errorTracking = baseAppConfig?.errorTracking ?? props.errorTracking;
482
- const errorBoundary = baseAppConfig?.errorBoundary ?? props.errorBoundary;
483
- const swr = baseAppConfig?.swr ?? props.swr;
484
- const monitor = baseAppConfig?.monitor ?? props.monitor;
485
- const debug = baseAppConfig?.debug ?? props.debug;
486
-
487
- return (
488
- <BaseApp
489
- project={project}
490
- theme={theme}
491
- auth={auth}
492
- analytics={analytics}
493
- centrifugo={centrifugo}
494
- errorTracking={errorTracking}
495
- errorBoundary={errorBoundary}
496
- swr={swr}
497
- monitor={monitor}
498
- debug={debug}
499
- i18n={i18n}
500
- >
501
- <AppLayoutContent
502
- children={children}
503
- publicLayout={publicLayout}
504
- privateLayout={privateLayout}
505
- adminLayout={adminLayout}
506
- noLayoutPaths={noLayoutPaths}
507
- authPath={authPath}
508
- publicChrome={publicChrome}
509
- themeOverrides={themeOverrides}
510
- />
511
- </BaseApp>
512
- );
513
- }