@djangocfg/layouts 1.4.30 → 2.0.1
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 +277 -18
- package/package.json +15 -24
- package/src/auth/context/AuthContext.tsx +5 -5
- package/src/auth/hooks/useAuthGuard.ts +1 -1
- package/src/auth/hooks/useAutoAuth.ts +8 -7
- package/src/components/ErrorBoundary.tsx +78 -0
- package/src/components/JsonLd.tsx +31 -0
- package/src/components/LucideIcon.tsx +91 -0
- package/src/components/PageProgress.tsx +127 -0
- package/src/components/Suspense.tsx +29 -0
- package/src/{layouts/AppLayout/components → components}/UpdateNotifier/UpdateNotifier.tsx +56 -49
- package/src/components/index.ts +10 -0
- package/src/index.ts +25 -7
- package/src/layouts/AdminLayout/AdminLayout.tsx +46 -0
- package/src/layouts/AdminLayout/index.ts +7 -0
- package/src/layouts/AppLayout/AppLayout.tsx +278 -326
- package/src/layouts/AppLayout/index.ts +2 -39
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthContext.tsx +3 -2
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/AuthHelp.tsx +1 -0
- package/src/layouts/AuthLayout/AuthLayout.tsx +61 -0
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/IdentifierForm.tsx +47 -34
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/OTPForm.tsx +2 -3
- package/src/layouts/AuthLayout/index.ts +24 -0
- package/src/layouts/{AppLayout/layouts/AuthLayout → AuthLayout}/types.ts +1 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +144 -0
- package/src/layouts/PrivateLayout/components/PrivateContent.tsx +32 -0
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +57 -0
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +141 -0
- package/src/layouts/PrivateLayout/components/index.ts +8 -0
- package/src/layouts/PrivateLayout/index.ts +7 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +15 -7
- package/src/layouts/PublicLayout/PublicLayout.tsx +121 -0
- package/src/layouts/PublicLayout/components/PublicFooter.tsx +190 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +117 -0
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +101 -0
- package/src/layouts/PublicLayout/components/index.ts +8 -0
- package/src/layouts/PublicLayout/index.ts +7 -0
- package/src/layouts/_components/UserMenu.tsx +160 -0
- package/src/layouts/_components/index.ts +7 -0
- package/src/layouts/index.ts +15 -8
- package/src/snippets/Analytics/AnalyticsProvider.tsx +8 -4
- package/src/snippets/Analytics/useAnalytics.ts +11 -21
- package/src/snippets/Chat/ChatWidget.tsx +4 -4
- package/src/snippets/ContactForm/ContactFormProvider.tsx +32 -19
- package/src/snippets/ContactForm/ContactPage.tsx +2 -4
- package/src/snippets/ContactForm/types.ts +3 -2
- package/src/snippets/index.ts +0 -1
- package/src/layouts/AppLayout/README.md +0 -204
- package/src/layouts/AppLayout/SUMMARY.md +0 -240
- package/src/layouts/AppLayout/USAGE.md +0 -312
- package/src/layouts/AppLayout/components/ErrorBoundary.tsx +0 -112
- package/src/layouts/AppLayout/components/PageProgress.tsx +0 -123
- package/src/layouts/AppLayout/components/Seo.tsx +0 -171
- package/src/layouts/AppLayout/components/UserMenu.tsx +0 -385
- package/src/layouts/AppLayout/components/index.ts +0 -11
- package/src/layouts/AppLayout/context/AppContext.tsx +0 -151
- package/src/layouts/AppLayout/context/index.ts +0 -5
- package/src/layouts/AppLayout/hooks/index.ts +0 -8
- package/src/layouts/AppLayout/hooks/useLayoutMode.ts +0 -26
- package/src/layouts/AppLayout/hooks/useNavigation.ts +0 -51
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +0 -224
- package/src/layouts/AppLayout/layouts/AdminLayout/README.md +0 -409
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +0 -98
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +0 -149
- package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +0 -146
- package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +0 -3
- package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +0 -48
- package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +0 -2
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/index.ts +0 -6
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +0 -279
- package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +0 -24
- package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +0 -1
- package/src/layouts/AppLayout/layouts/AdminLayout/types/index.ts +0 -45
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +0 -41
- package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +0 -15
- package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +0 -82
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +0 -62
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +0 -89
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +0 -181
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +0 -9
- package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +0 -5
- package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +0 -44
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +0 -242
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileDrawer.tsx +0 -150
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +0 -169
- package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +0 -5
- package/src/layouts/AppLayout/layouts/index.ts +0 -7
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +0 -80
- package/src/layouts/AppLayout/providers/index.ts +0 -5
- package/src/layouts/AppLayout/types/config.ts +0 -79
- package/src/layouts/AppLayout/types/index.ts +0 -11
- package/src/layouts/AppLayout/types/layout.ts +0 -54
- package/src/layouts/AppLayout/types/navigation.ts +0 -43
- package/src/layouts/AppLayout/types/page.ts +0 -80
- package/src/layouts/AppLayout/types/routes.ts +0 -43
- package/src/layouts/AppLayout/utils/index.ts +0 -5
- package/src/layouts/AppLayout/utils/routeDetection.ts +0 -31
- package/src/layouts/ErrorLayout/ErrorLayout.tsx +0 -173
- package/src/layouts/ErrorLayout/errorConfig.tsx +0 -152
- package/src/layouts/ErrorLayout/index.ts +0 -8
- package/src/layouts/SimpleLayout/SimpleLayout.tsx +0 -72
- package/src/layouts/SimpleLayout/index.ts +0 -3
- package/src/snippets/VideoPlayer/README.md +0 -238
- package/src/snippets/VideoPlayer/VideoControls.tsx +0 -137
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +0 -248
- package/src/snippets/VideoPlayer/index.ts +0 -8
- package/src/snippets/VideoPlayer/types.ts +0 -61
- package/src/types/index.ts +0 -2
- package/src/types/pageConfig.ts +0 -100
- /package/src/{validation → components/ErrorsTracker}/README.md +0 -0
- /package/src/{validation → components/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/components/ErrorToast.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/hooks.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/index.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
- /package/src/{validation → components/ErrorsTracker}/types.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/utils/curl-generator.ts +0 -0
- /package/src/{validation → components/ErrorsTracker}/utils/formatters.ts +0 -0
- /package/src/{layouts/AppLayout/components → components}/UpdateNotifier/index.ts +0 -0
|
@@ -1,358 +1,310 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
|
-
* AppLayout -
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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,
|
|
29
|
-
import {
|
|
30
|
-
import
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
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 {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
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
|
|
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
|
-
*
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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 -
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
|
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
|
-
{
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
<
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
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';
|