@djangocfg/layouts 1.4.30 → 2.0.2

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 (119) hide show
  1. package/README.md +277 -18
  2. package/package.json +15 -24
  3. package/src/auth/context/AuthContext.tsx +5 -5
  4. package/src/auth/hooks/useAuthGuard.ts +1 -1
  5. package/src/auth/hooks/useAutoAuth.ts +8 -7
  6. package/src/components/ErrorBoundary.tsx +78 -0
  7. package/src/components/JsonLd.tsx +31 -0
  8. package/src/components/LucideIcon.tsx +91 -0
  9. package/src/components/PageProgress.tsx +127 -0
  10. package/src/components/Suspense.tsx +29 -0
  11. package/src/{layouts/AppLayout/components → components}/UpdateNotifier/UpdateNotifier.tsx +56 -49
  12. package/src/components/index.ts +10 -0
  13. package/src/index.ts +25 -7
  14. package/src/layouts/AdminLayout/AdminLayout.tsx +46 -0
  15. package/src/layouts/AdminLayout/index.ts +7 -0
  16. package/src/layouts/AppLayout/AppLayout.tsx +278 -326
  17. package/src/layouts/AppLayout/index.ts +2 -39
  18. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthContext.tsx +3 -2
  19. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthHelp.tsx +1 -0
  20. package/src/layouts/AuthLayout/AuthLayout.tsx +61 -0
  21. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/IdentifierForm.tsx +47 -34
  22. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/OTPForm.tsx +2 -3
  23. package/src/layouts/AuthLayout/index.ts +24 -0
  24. package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/types.ts +1 -0
  25. package/src/layouts/PrivateLayout/PrivateLayout.tsx +144 -0
  26. package/src/layouts/PrivateLayout/components/PrivateContent.tsx +32 -0
  27. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +57 -0
  28. package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +141 -0
  29. package/src/layouts/PrivateLayout/components/index.ts +8 -0
  30. package/src/layouts/PrivateLayout/index.ts +7 -0
  31. package/src/layouts/ProfileLayout/ProfileLayout.tsx +15 -7
  32. package/src/layouts/PublicLayout/PublicLayout.tsx +121 -0
  33. package/src/layouts/PublicLayout/components/PublicFooter.tsx +190 -0
  34. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +117 -0
  35. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +101 -0
  36. package/src/layouts/PublicLayout/components/index.ts +8 -0
  37. package/src/layouts/PublicLayout/index.ts +7 -0
  38. package/src/layouts/_components/UserMenu.tsx +160 -0
  39. package/src/layouts/_components/index.ts +7 -0
  40. package/src/layouts/index.ts +15 -8
  41. package/src/snippets/Analytics/AnalyticsProvider.tsx +8 -4
  42. package/src/snippets/Analytics/useAnalytics.ts +11 -21
  43. package/src/snippets/Chat/ChatWidget.tsx +4 -4
  44. package/src/snippets/ContactForm/ContactFormProvider.tsx +32 -19
  45. package/src/snippets/ContactForm/ContactPage.tsx +2 -4
  46. package/src/snippets/ContactForm/types.ts +3 -2
  47. package/src/snippets/index.ts +0 -1
  48. package/src/layouts/AppLayout/README.md +0 -204
  49. package/src/layouts/AppLayout/SUMMARY.md +0 -240
  50. package/src/layouts/AppLayout/USAGE.md +0 -312
  51. package/src/layouts/AppLayout/components/ErrorBoundary.tsx +0 -112
  52. package/src/layouts/AppLayout/components/PageProgress.tsx +0 -123
  53. package/src/layouts/AppLayout/components/Seo.tsx +0 -171
  54. package/src/layouts/AppLayout/components/UserMenu.tsx +0 -385
  55. package/src/layouts/AppLayout/components/index.ts +0 -11
  56. package/src/layouts/AppLayout/context/AppContext.tsx +0 -151
  57. package/src/layouts/AppLayout/context/index.ts +0 -5
  58. package/src/layouts/AppLayout/hooks/index.ts +0 -8
  59. package/src/layouts/AppLayout/hooks/useLayoutMode.ts +0 -26
  60. package/src/layouts/AppLayout/hooks/useNavigation.ts +0 -51
  61. package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +0 -224
  62. package/src/layouts/AppLayout/layouts/AdminLayout/README.md +0 -409
  63. package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +0 -98
  64. package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +0 -149
  65. package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +0 -146
  66. package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +0 -3
  67. package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +0 -48
  68. package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +0 -2
  69. package/src/layouts/AppLayout/layouts/AdminLayout/hooks/index.ts +0 -6
  70. package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +0 -279
  71. package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +0 -24
  72. package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +0 -1
  73. package/src/layouts/AppLayout/layouts/AdminLayout/types/index.ts +0 -45
  74. package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +0 -41
  75. package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +0 -15
  76. package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +0 -82
  77. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +0 -62
  78. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +0 -89
  79. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +0 -181
  80. package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +0 -9
  81. package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +0 -5
  82. package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +0 -44
  83. package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +0 -242
  84. package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileDrawer.tsx +0 -150
  85. package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +0 -169
  86. package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +0 -5
  87. package/src/layouts/AppLayout/layouts/index.ts +0 -7
  88. package/src/layouts/AppLayout/providers/CoreProviders.tsx +0 -80
  89. package/src/layouts/AppLayout/providers/index.ts +0 -5
  90. package/src/layouts/AppLayout/types/config.ts +0 -79
  91. package/src/layouts/AppLayout/types/index.ts +0 -11
  92. package/src/layouts/AppLayout/types/layout.ts +0 -54
  93. package/src/layouts/AppLayout/types/navigation.ts +0 -43
  94. package/src/layouts/AppLayout/types/page.ts +0 -80
  95. package/src/layouts/AppLayout/types/routes.ts +0 -43
  96. package/src/layouts/AppLayout/utils/index.ts +0 -5
  97. package/src/layouts/AppLayout/utils/routeDetection.ts +0 -31
  98. package/src/layouts/ErrorLayout/ErrorLayout.tsx +0 -173
  99. package/src/layouts/ErrorLayout/errorConfig.tsx +0 -152
  100. package/src/layouts/ErrorLayout/index.ts +0 -8
  101. package/src/layouts/SimpleLayout/SimpleLayout.tsx +0 -72
  102. package/src/layouts/SimpleLayout/index.ts +0 -3
  103. package/src/snippets/VideoPlayer/README.md +0 -238
  104. package/src/snippets/VideoPlayer/VideoControls.tsx +0 -137
  105. package/src/snippets/VideoPlayer/VideoPlayer.tsx +0 -248
  106. package/src/snippets/VideoPlayer/index.ts +0 -8
  107. package/src/snippets/VideoPlayer/types.ts +0 -61
  108. package/src/types/index.ts +0 -2
  109. package/src/types/pageConfig.ts +0 -100
  110. /package/src/{validation → components/ErrorsTracker}/README.md +0 -0
  111. /package/src/{validation → components/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
  112. /package/src/{validation → components/ErrorsTracker}/components/ErrorToast.tsx +0 -0
  113. /package/src/{validation → components/ErrorsTracker}/hooks.ts +0 -0
  114. /package/src/{validation → components/ErrorsTracker}/index.ts +0 -0
  115. /package/src/{validation → components/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
  116. /package/src/{validation → components/ErrorsTracker}/types.ts +0 -0
  117. /package/src/{validation → components/ErrorsTracker}/utils/curl-generator.ts +0 -0
  118. /package/src/{validation → components/ErrorsTracker}/utils/formatters.ts +0 -0
  119. /package/src/{layouts/AppLayout/components → components}/UpdateNotifier/index.ts +0 -0
@@ -1,358 +1,310 @@
1
- // @ts-nocheck
2
1
  /**
3
- * AppLayout - Unified Application Layout System
4
- *
5
- * Single component that handles all layout logic:
6
- * - Auto-detects route type (public/private/auth)
7
- * - Applies correct layout automatically
8
- * - Manages all state through context
9
- * - Zero prop drilling
10
- * - Optional Django CFG admin mode with iframe integration
11
- *
12
- * Usage in _app.tsx:
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
13
9
  * ```tsx
14
- * // Standard usage
15
- * <AppLayout config={appLayoutConfig}>
16
- * <Component {...pageProps} />
17
- * </AppLayout>
18
- *
19
- * // Django CFG admin mode (with iframe integration)
20
- * <AppLayout config={appLayoutConfig} isCfgAdmin={true}>
21
- * <Component {...pageProps} />
10
+ * import { AppLayout } from '@djangocfg/layouts';
11
+ *
12
+ * <AppLayout
13
+ * publicLayout={{
14
+ * logo: '/logo.svg',
15
+ * siteName: 'My App',
16
+ * navigation: [{ label: 'Home', href: '/' }]
17
+ * }}
18
+ * privateLayout={{
19
+ * enabledPath: ['/dashboard', '/profile'],
20
+ * sidebar: { items: [{ label: 'Dashboard', href: '/dashboard' }] }
21
+ * }}
22
+ * authLayout={{
23
+ * enabledPath: '/auth',
24
+ * logo: '/logo.svg',
25
+ * siteName: 'My App'
26
+ * }}
27
+ * adminLayout={{
28
+ * enabledPath: '/admin',
29
+ * sidebar: { items: [{ label: 'Admin', href: '/admin' }] }
30
+ * }}
31
+ * >
32
+ * {children}
22
33
  * </AppLayout>
23
34
  * ```
24
35
  */
25
36
 
26
37
  'use client';
27
38
 
28
- import React, { ReactNode, useEffect, useState } from 'react';
29
- import { useRouter } from 'next/router';
30
- import dynamic from 'next/dynamic';
31
- import { AppContextProvider } from './context';
32
- import { CoreProviders } from './providers';
33
- import { Seo, PageProgress, ErrorBoundary, UpdateNotifier } from './components';
39
+ import React, { ReactNode, useMemo } from 'react';
40
+ import { usePathname } from 'next/navigation';
41
+ import { ThemeProvider, Toaster } from '@djangocfg/ui';
42
+ import { CentrifugoProvider } from '@djangocfg/centrifugo';
43
+ import { ErrorBoundary } from '../../components/ErrorBoundary';
44
+ import { AuthProvider, type AuthConfig } from '../../auth/context';
45
+ import { ErrorTrackingProvider, type ValidationErrorConfig, type CORSErrorConfig, type NetworkErrorConfig } from '../../components/ErrorsTracker';
34
46
  import { AnalyticsProvider } from '../../snippets/Analytics';
35
- import { PublicLayout } from './layouts/PublicLayout';
36
- import { PrivateLayout } from './layouts/PrivateLayout';
37
- import { AuthLayout } from './layouts/AuthLayout';
38
- import { PagePreloader } from './layouts/AdminLayout/components';
39
- import { determineLayoutMode, getRedirectUrl } from './utils';
40
- import { useAuth } from '../../auth';
41
- import type { AppLayoutConfig, PageWithLayout, LayoutMode } from './types';
42
- import type { ValidationErrorConfig, CORSErrorConfig, NetworkErrorConfig } from '../../validation';
43
- import { determinePageConfig } from '../../types/pageConfig';
44
- import packageJson from '../../../package.json';
45
-
46
- // Dynamic import for AdminLayout to prevent SSR hydration issues
47
- const AdminLayout = dynamic(
48
- () => import('./layouts/AdminLayout').then((mod) => ({ default: mod.AdminLayout })),
49
- { ssr: false }
50
- );
47
+ import { PageProgress } from '../../components/PageProgress';
48
+ import { UpdateNotifier } from '../../components/UpdateNotifier';
49
+ import { Suspense } from '../../components';
51
50
 
52
- export interface AppLayoutProps {
53
- children: ReactNode;
54
- config: AppLayoutConfig;
55
- /**
56
- * Next.js page component (for reading pageConfig and layout preferences)
57
- * Pass Component from _app.tsx to enable smart layout detection
58
- * @example component={Component}
59
- */
60
- component?: PageWithLayout | any;
61
- /**
62
- * Next.js page props (for reading dynamic pageConfig from SSR)
63
- * @example pageProps={pageProps}
64
- */
65
- pageProps?: Record<string, any>;
66
- /**
67
- * Font family to apply globally
68
- * Accepts Next.js font object or CSS font-family string
69
- * @example fontFamily={manrope.style.fontFamily}
70
- * @example fontFamily="Inter, sans-serif"
71
- */
72
- fontFamily?: string;
73
- /**
74
- * Show update notifier (checks npm for new versions)
75
- * @default true
76
- * @example showUpdateNotifier={false}
77
- */
78
- showUpdateNotifier?: boolean;
79
- /**
80
- * Validation error tracking configuration
81
- * @default { enabled: true, showToast: true, maxErrors: 50 }
82
- */
83
- validation?: Partial<ValidationErrorConfig>;
84
- /**
85
- * CORS error tracking configuration
86
- * @default { enabled: true, showToast: true }
87
- */
88
- cors?: Partial<CORSErrorConfig>;
89
- /**
90
- * Network error tracking configuration
91
- * @default { enabled: false }
92
- */
93
- network?: Partial<NetworkErrorConfig>;
94
- }
51
+ export type LayoutMode = 'public' | 'private' | 'admin';
95
52
 
96
53
  /**
97
- * Layout Router Component
98
- *
99
- * Smart layout detection with priority:
100
- * 1. component.getLayout (custom layout function)
101
- * 2. component.layoutMode ('none' | 'public' | 'private' | 'auth' | 'admin')
102
- * 3. Automatic route-based detection
54
+ * Check if pathname matches enabledPath
103
55
  */
104
- function LayoutRouter({
105
- children,
106
- component,
107
- config
108
- }: {
109
- children: ReactNode;
110
- component?: PageWithLayout | any;
111
- config: AppLayoutConfig;
112
- }) {
113
- const router = useRouter();
114
- const { isAuthenticated, isLoading } = useAuth();
115
- const [isMounted, setIsMounted] = useState(false);
116
-
117
- // SSR/Hydration protection
118
- useEffect(() => {
119
- setIsMounted(true);
120
- }, []);
121
-
122
- // Priority 1: Check if page has custom getLayout function
123
- const hasCustomLayout = component && typeof component.getLayout === 'function';
124
- if (hasCustomLayout) {
125
- // Use custom layout - render children directly (getLayout applied in _app.tsx)
126
- return <>{children}</>;
127
- }
128
-
129
- // Priority 2: Check component.layoutMode
130
- const componentLayoutMode = component?.layoutMode;
131
-
132
- // If layoutMode is 'none', render children directly
133
- if (componentLayoutMode === 'none') {
134
- return <>{children}</>;
135
- }
136
-
137
- // Check route type (synchronous - works with SSR)
138
- const isAuthRoute = config.routes.detectors.isAuthRoute(router.pathname);
139
- const isPrivateRoute = config.routes.detectors.isPrivateRoute(router.pathname);
140
- const isAdminRoute = config.routes.detectors.isAdminRoute(router.pathname);
141
-
142
- // Admin routes: Always show loading during SSR and initial client render
143
- // This prevents hydration mismatch when isAuthenticated differs between server/client
144
- if ((isAdminRoute && !componentLayoutMode) || componentLayoutMode === 'admin') {
145
- // In embedded mode (iframe), render AdminLayout immediately to receive postMessage
146
- const isEmbedded = typeof window !== 'undefined' && window !== window.parent;
147
-
148
- if (isEmbedded) {
149
- // Skip loading checks - AdminLayout will handle auth via postMessage
150
- return (
151
- <AdminLayout enableParentSync={true}>
152
- {children}
153
- </AdminLayout>
154
- );
155
- }
156
-
157
- // Standalone mode: show loading during initialization
158
- if (!isMounted || isLoading) {
159
- return <PagePreloader text="Loading admin..." />;
160
- }
161
-
162
- // After mount: check authentication
163
- if (!isAuthenticated) {
164
- // Redirect to auth
165
- return (
166
- <AuthLayout
167
- termsUrl={config.auth?.termsUrl}
168
- privacyUrl={config.auth?.privacyUrl}
169
- supportUrl={config.auth?.supportUrl}
170
- enablePhoneAuth={config.auth?.enablePhoneAuth}
171
- />
172
- );
173
- }
174
-
175
- // Admin routes use AdminLayout with ParentSync
176
- return (
177
- <AdminLayout enableParentSync={true}>
178
- {children}
179
- </AdminLayout>
180
- );
56
+ function matchesPath(pathname: string, enabledPath?: string | string[]): boolean {
57
+ if (!enabledPath) return false;
58
+
59
+ if (typeof enabledPath === 'string') {
60
+ return pathname === enabledPath || pathname.startsWith(enabledPath + '/');
181
61
  }
62
+
63
+ // Array of paths
64
+ return enabledPath.some(path => pathname === path || pathname.startsWith(path + '/'));
65
+ }
182
66
 
183
- // Private routes: Always show loading during SSR and initial client render
184
- // This prevents hydration mismatch when isAuthenticated differs between server/client
185
- if ((isPrivateRoute && !componentLayoutMode) || componentLayoutMode === 'private') {
186
- if (!isMounted || isLoading) {
187
- return <PagePreloader />;
188
- }
189
-
190
- // After mount: check authentication
191
- if (!isAuthenticated) {
192
- // Redirect to auth (handled in PrivateLayout)
193
- return (
194
- <AuthLayout
195
- termsUrl={config.auth?.termsUrl}
196
- privacyUrl={config.auth?.privacyUrl}
197
- supportUrl={config.auth?.supportUrl}
198
- enablePhoneAuth={config.auth?.enablePhoneAuth}
199
- />
200
- );
201
- }
202
-
203
- return <PrivateLayout>{children}</PrivateLayout>;
204
- }
67
+ /**
68
+ * Determine layout mode from pathname and enabledPath props
69
+ */
70
+ function determineLayoutMode(
71
+ pathname: string,
72
+ adminLayout?: { component: any; enabledPath?: string | string[] },
73
+ privateLayout?: { component: any; enabledPath?: string | string[] },
74
+ publicLayout?: { component: any; enabledPath?: string | string[] }
75
+ ): LayoutMode {
76
+ // Check in order: admin -> private -> public
77
+ if (adminLayout && matchesPath(pathname, adminLayout.enabledPath)) return 'admin';
78
+ if (privateLayout && matchesPath(pathname, privateLayout.enabledPath)) return 'private';
79
+ if (publicLayout && matchesPath(pathname, publicLayout.enabledPath)) return 'public';
80
+
81
+ // Default: if no enabledPath specified, use public as fallback
82
+ return 'public';
83
+ }
205
84
 
206
- // Determine layout mode for non-private/admin routes
207
- const getLayoutMode = (): 'public' | 'auth' | 'admin' => {
208
- // Priority: componentLayoutMode > auto-detect
209
- if (componentLayoutMode === 'auth') return 'auth';
210
- if (componentLayoutMode === 'public') return 'public';
211
- if (componentLayoutMode === 'admin') return 'admin';
212
- if (isAuthRoute) return 'auth';
213
- return 'public';
85
+ export interface AppLayoutProps {
86
+ children: ReactNode;
87
+
88
+ /** Public layout component with enabled paths */
89
+ publicLayout?: {
90
+ component: React.ComponentType<{ children: ReactNode }>;
91
+ enabledPath?: string | string[];
92
+ };
93
+
94
+ /** Private layout component with enabled paths */
95
+ privateLayout?: {
96
+ component: React.ComponentType<{ children: ReactNode }>;
97
+ enabledPath?: string | string[];
98
+ };
99
+
100
+ /** Admin layout component with enabled paths */
101
+ adminLayout?: {
102
+ component: React.ComponentType<{ children: ReactNode }>;
103
+ enabledPath?: string | string[];
104
+ };
105
+
106
+ /** Theme configuration */
107
+ theme?: {
108
+ defaultTheme?: 'light' | 'dark' | 'system';
109
+ storageKey?: string;
110
+ };
111
+
112
+ /** Auth configuration */
113
+ auth?: AuthConfig;
114
+
115
+ /** Analytics configuration */
116
+ analytics?: {
117
+ googleTrackingId?: string;
118
+ };
119
+
120
+ /** Centrifugo configuration */
121
+ centrifugo?: {
122
+ enabled?: boolean;
123
+ autoConnect?: boolean;
124
+ url?: string;
125
+ };
126
+
127
+ /** Error tracking configuration */
128
+ errorTracking?: {
129
+ validation?: Partial<ValidationErrorConfig>;
130
+ cors?: Partial<CORSErrorConfig>;
131
+ network?: Partial<NetworkErrorConfig>;
132
+ onError?: (error: any) => boolean | void;
133
+ };
134
+
135
+ /** Error boundary configuration */
136
+ errorBoundary?: {
137
+ enabled?: boolean;
138
+ supportEmail?: string;
139
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
140
+ };
141
+
142
+ /** Update notifier configuration */
143
+ updateNotifier?: {
144
+ enabled?: boolean;
214
145
  };
215
-
216
- const layoutMode = getLayoutMode();
217
-
218
- // Render appropriate layout
219
- switch (layoutMode) {
220
- case 'admin':
221
- return (
222
- <AdminLayout enableParentSync={true}>
223
- {children}
224
- </AdminLayout>
225
- );
226
- // Public routes: render immediately (SSR enabled)
227
- case 'public':
228
- return <PublicLayout>{children}</PublicLayout>;
229
-
230
- // Auth routes: render inside AuthLayout
231
- case 'auth':
232
- return (
233
- <AuthLayout
234
- termsUrl={config.auth?.termsUrl}
235
- privacyUrl={config.auth?.privacyUrl}
236
- supportUrl={config.auth?.supportUrl}
237
- enablePhoneAuth={config.auth?.enablePhoneAuth}
238
- >
239
- {children}
240
- </AuthLayout>
241
- );
242
-
243
- default:
244
- return <PublicLayout>{children}</PublicLayout>;
245
- }
246
146
  }
247
147
 
248
148
  /**
249
- * AppLayout - Main Component
250
- *
251
- * Single entry point for all layout logic with smart layout detection
252
- * Wrap your app once in _app.tsx
253
- *
254
- * @example
255
- * ```tsx
256
- * // Smart auto-detection (recommended)
257
- * <AppLayout config={appLayoutConfig} component={Component} pageProps={pageProps}>
258
- * {Component.getLayout ? Component.getLayout(<Component {...pageProps} />) : <Component {...pageProps} />}
259
- * </AppLayout>
260
- *
261
- * // With custom font
262
- * <AppLayout config={appLayoutConfig} component={Component} fontFamily={inter.style.fontFamily}>
263
- * <Component {...pageProps} />
264
- * </AppLayout>
265
- *
266
- * // Page with custom layout (in page file)
267
- * const Page: PageWithLayout = () => <div>Content</div>;
268
- * Page.getLayout = (page) => <CustomLayout>{page}</CustomLayout>;
269
- *
270
- * // Page with forced layout mode (in page file)
271
- * const DashboardPage: PageWithLayout = () => <div>Dashboard</div>;
272
- * DashboardPage.layoutMode = 'private';
273
- *
274
- * // Page without layout (in page file)
275
- * const LandingPage: PageWithLayout = () => <FullPageDesign />;
276
- * LandingPage.layoutMode = 'none';
277
- * ```
149
+ * AppLayout Content - Renders layout with all providers
278
150
  */
279
- export function AppLayout({ children, config, component, pageProps, fontFamily, showUpdateNotifier, validation, cors, network }: AppLayoutProps) {
280
- const router = useRouter();
281
-
282
- // Check if ErrorBoundary is enabled (default: true)
283
- const enableErrorBoundary = config.errors?.enableErrorBoundary !== false;
284
- const supportEmail = config.errors?.supportEmail;
285
- const onError = config.errors?.onError;
286
-
287
- // Determine final pageConfig (merges static + dynamic from SSR)
288
- const finalPageConfig = component && pageProps
289
- ? determinePageConfig(
290
- component,
291
- pageProps,
292
- config.app.name,
293
- config.app.description
294
- )
295
- : {
296
- title: config.app.name,
297
- description: config.app.description,
298
- ogImage: {
299
- title: config.app.name,
300
- subtitle: config.app.description,
301
- },
302
- };
303
-
304
- const appContent = (
305
- <AppContextProvider config={config} showUpdateNotifier={showUpdateNotifier}>
306
- <AnalyticsProvider>
307
- {/* SEO Meta Tags */}
308
- <Seo
309
- pageConfig={finalPageConfig}
310
- icons={config.app.icons}
311
- siteUrl={config.app.siteUrl}
312
- />
313
-
314
- {/* Update Notifier */}
315
- <UpdateNotifier enabled={showUpdateNotifier} currentVersion={packageJson.version} />
316
-
317
- {/* Loading Progress Bar */}
318
- <PageProgress />
319
-
320
- {/* Smart Layout Router */}
321
- <LayoutRouter component={component} config={config}>
322
- {children}
323
- </LayoutRouter>
324
- </AnalyticsProvider>
325
- </AppContextProvider>
151
+ function AppLayoutContent({
152
+ children,
153
+ publicLayout,
154
+ privateLayout,
155
+ adminLayout,
156
+ theme,
157
+ auth,
158
+ analytics,
159
+ centrifugo,
160
+ errorTracking,
161
+ errorBoundary,
162
+ updateNotifier,
163
+ }: AppLayoutProps) {
164
+ const pathname = usePathname();
165
+
166
+ const layoutMode = useMemo(
167
+ () => determineLayoutMode(
168
+ pathname,
169
+ adminLayout,
170
+ privateLayout,
171
+ publicLayout
172
+ ),
173
+ [pathname, adminLayout, privateLayout, publicLayout]
326
174
  );
327
-
328
- const content = (
175
+
176
+ const enableErrorBoundary = errorBoundary?.enabled !== false;
177
+
178
+ // Render appropriate layout based on mode
179
+ const renderLayout = () => {
180
+ switch (layoutMode) {
181
+ case 'admin':
182
+ if (!adminLayout && privateLayout) {
183
+ // Fallback to private layout if no admin layout provided
184
+ return (
185
+ <Suspense>
186
+ <privateLayout.component>{children}</privateLayout.component>
187
+ </Suspense>
188
+ );
189
+ }
190
+ if (!adminLayout) {
191
+ return children;
192
+ }
193
+ return (
194
+ <Suspense>
195
+ <adminLayout.component>{children}</adminLayout.component>
196
+ </Suspense>
197
+ );
198
+
199
+ case 'private':
200
+ if (!privateLayout) {
201
+ // Fallback to public if no private layout provided
202
+ if (publicLayout) {
203
+ return <publicLayout.component>{children}</publicLayout.component>;
204
+ }
205
+ return children;
206
+ }
207
+ return (
208
+ <Suspense>
209
+ <privateLayout.component>{children}</privateLayout.component>
210
+ </Suspense>
211
+ );
212
+
213
+ case 'public':
214
+ default:
215
+ if (!publicLayout) {
216
+ return children;
217
+ }
218
+ return <publicLayout.component>{children}</publicLayout.component>;
219
+ }
220
+ };
221
+
222
+ const layoutContent = renderLayout();
223
+
224
+ // Wrap with providers (order matters!)
225
+ let content: ReactNode = layoutContent;
226
+
227
+ // Analytics Provider (if enabled)
228
+ if (analytics?.googleTrackingId) {
229
+ content = <AnalyticsProvider trackingId={analytics.googleTrackingId}>{content}</AnalyticsProvider>;
230
+ }
231
+
232
+ // Centrifugo Provider (if enabled)
233
+ if (centrifugo?.enabled !== false) {
234
+ // Auto-detect URL from env if not provided
235
+ // NEXT_PUBLIC_* variables are automatically available in client components
236
+ const centrifugoUrl = centrifugo?.url || process.env.NEXT_PUBLIC_CENTRIFUGO_URL;
237
+
238
+ content = (
239
+ <CentrifugoProvider
240
+ enabled={centrifugo?.enabled}
241
+ autoConnect={centrifugo?.autoConnect}
242
+ url={centrifugoUrl}
243
+ >
244
+ {content}
245
+ </CentrifugoProvider>
246
+ );
247
+ }
248
+
249
+ // Global toast notifications (always rendered)
250
+ content = (
329
251
  <>
330
- {/* Global Font Styles */}
331
- {fontFamily && (
332
- <style dangerouslySetInnerHTML={{
333
- __html: `html { font-family: ${fontFamily}; }`
334
- }} />
335
- )}
336
-
337
- <CoreProviders config={config} validation={validation} cors={cors} network={network}>
338
- {appContent}
339
- </CoreProviders>
252
+ {content}
253
+ <PageProgress />
254
+ <UpdateNotifier
255
+ enabled={updateNotifier?.enabled !== false}
256
+ />
257
+ <Toaster />
340
258
  </>
341
259
  );
342
-
260
+
343
261
  // Wrap with ErrorBoundary if enabled
344
262
  if (enableErrorBoundary) {
345
263
  return (
346
- <React.Fragment key={router.pathname}>
347
- <ErrorBoundary
348
- supportEmail={supportEmail}
349
- onError={onError}
350
- >
351
- {content}
352
- </ErrorBoundary>
353
- </React.Fragment>
264
+ <ErrorBoundary
265
+ supportEmail={errorBoundary?.supportEmail}
266
+ onError={errorBoundary?.onError}
267
+ >
268
+ {content}
269
+ </ErrorBoundary>
354
270
  );
355
271
  }
356
-
272
+
357
273
  return content;
358
274
  }
275
+
276
+ /**
277
+ * AppLayout - Main Component with All Providers
278
+ */
279
+ export function AppLayout(props: AppLayoutProps) {
280
+ const { theme, auth, errorTracking } = props;
281
+
282
+ // Convert 'system' to actual theme based on system preference
283
+ const getResolvedTheme = (): 'light' | 'dark' => {
284
+ if (theme?.defaultTheme === 'system') {
285
+ if (typeof window !== 'undefined') {
286
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
287
+ }
288
+ return 'light'; // Default fallback for SSR
289
+ }
290
+ return theme?.defaultTheme || 'light';
291
+ };
292
+
293
+ return (
294
+ <ThemeProvider
295
+ defaultTheme={getResolvedTheme()}
296
+ storageKey={theme?.storageKey}
297
+ >
298
+ <AuthProvider config={auth}>
299
+ <ErrorTrackingProvider
300
+ validation={errorTracking?.validation}
301
+ cors={errorTracking?.cors}
302
+ network={errorTracking?.network}
303
+ onError={errorTracking?.onError}
304
+ >
305
+ <AppLayoutContent {...props} />
306
+ </ErrorTrackingProvider>
307
+ </AuthProvider>
308
+ </ThemeProvider>
309
+ );
310
+ }
@@ -1,44 +1,7 @@
1
1
  /**
2
- * AppLayout - Unified Application Layout System
3
- *
4
- * Smart layout system with automatic detection
2
+ * AppLayout exports
5
3
  */
6
4
 
7
- // Main component
8
5
  export { AppLayout } from './AppLayout';
6
+ export type { AppLayoutProps, LayoutMode } from './AppLayout';
9
7
 
10
- // Types
11
- export type {
12
- AppLayoutConfig,
13
- PublicLayoutConfig,
14
- PrivateLayoutConfig,
15
- RouteConfig,
16
- RouteDetectors,
17
- LayoutMode,
18
- NavigationItem,
19
- NavigationSection,
20
- DashboardMenuItem,
21
- DashboardMenuGroup,
22
- PageWithLayout,
23
- } from './types';
24
-
25
- // Context and hooks
26
- export { useAppContext, AppContextProvider } from './context';
27
- export { useLayoutMode, useNavigation } from './hooks';
28
-
29
- // Layouts (for custom usage if needed)
30
- export { PublicLayout } from './layouts/PublicLayout';
31
- export { PrivateLayout } from './layouts/PrivateLayout';
32
- export { AuthLayout } from './layouts/AuthLayout';
33
-
34
- // AdminLayout - Django CFG iframe integration
35
- export { AdminLayout } from './layouts/AdminLayout';
36
- export { useCfgApp, useApp } from './layouts/AdminLayout';
37
- export { ParentSync, AuthStatusSync } from './layouts/AdminLayout';
38
- export type {
39
- AdminLayoutConfig,
40
- UseCfgAppReturn,
41
- UseCfgAppOptions,
42
- UseAppReturn,
43
- UseAppOptions
44
- } from './layouts/AdminLayout';