@djangocfg/layouts 1.2.8 → 1.2.10
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/package.json +5 -5
- package/src/layouts/AppLayout/AppLayout.tsx +67 -35
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/index.ts +6 -6
- package/src/layouts/AppLayout/layouts/{CfgLayout/CfgLayout.tsx → AdminLayout/AdminLayout.tsx} +39 -13
- package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/README.md +23 -23
- package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/index.ts +4 -4
- package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/types/index.ts +7 -7
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +2 -0
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +5 -1
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +24 -4
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +2 -0
- package/src/layouts/AppLayout/types/config.ts +4 -1
- package/src/layouts/AppLayout/types/layout.ts +7 -0
- package/src/layouts/AppLayout/types/navigation.ts +2 -0
- package/src/layouts/AppLayout/types/routes.ts +4 -1
- /package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/components/ParentSync.tsx +0 -0
- /package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/components/index.ts +0 -0
- /package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/hooks/index.ts +0 -0
- /package/src/layouts/AppLayout/layouts/{CfgLayout → AdminLayout}/hooks/useApp.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"check": "tsc --noEmit"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@djangocfg/api": "^1.2.
|
|
57
|
-
"@djangocfg/og-image": "^1.2.
|
|
58
|
-
"@djangocfg/ui": "^1.2.
|
|
56
|
+
"@djangocfg/api": "^1.2.10",
|
|
57
|
+
"@djangocfg/og-image": "^1.2.10",
|
|
58
|
+
"@djangocfg/ui": "^1.2.10",
|
|
59
59
|
"@hookform/resolvers": "^5.2.0",
|
|
60
60
|
"consola": "^3.4.2",
|
|
61
61
|
"lucide-react": "^0.468.0",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"vidstack": "0.6.15"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@djangocfg/typescript-config": "^1.2.
|
|
79
|
+
"@djangocfg/typescript-config": "^1.2.10",
|
|
80
80
|
"@types/node": "^24.7.2",
|
|
81
81
|
"@types/react": "19.2.2",
|
|
82
82
|
"@types/react-dom": "19.2.1",
|
|
@@ -32,7 +32,7 @@ import { Seo, PageProgress, ErrorBoundary } from './components';
|
|
|
32
32
|
import { PublicLayout } from './layouts/PublicLayout';
|
|
33
33
|
import { PrivateLayout } from './layouts/PrivateLayout';
|
|
34
34
|
import { AuthLayout } from './layouts/AuthLayout';
|
|
35
|
-
import {
|
|
35
|
+
import { AdminLayout } from './layouts/AdminLayout';
|
|
36
36
|
import { determineLayoutMode, getRedirectUrl } from './utils';
|
|
37
37
|
import { useAuth } from '../../auth';
|
|
38
38
|
import type { AppLayoutConfig } from './types';
|
|
@@ -97,15 +97,47 @@ function LayoutRouter({
|
|
|
97
97
|
setIsMounted(true);
|
|
98
98
|
}, []);
|
|
99
99
|
|
|
100
|
-
const isAdminMode = forceLayout === 'admin';
|
|
101
100
|
// If layout is disabled, render children directly (providers still active!)
|
|
102
|
-
if (disableLayout
|
|
101
|
+
if (disableLayout) {
|
|
103
102
|
return <>{children}</>;
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
// Check route type (synchronous - works with SSR)
|
|
107
106
|
const isAuthRoute = config.routes.detectors.isAuthRoute(router.pathname);
|
|
108
107
|
const isPrivateRoute = config.routes.detectors.isPrivateRoute(router.pathname);
|
|
108
|
+
const isAdminRoute = config.routes.detectors.isAdminRoute(router.pathname);
|
|
109
|
+
|
|
110
|
+
// Admin routes: Always show loading during SSR and initial client render
|
|
111
|
+
// This prevents hydration mismatch when isAuthenticated differs between server/client
|
|
112
|
+
if (isAdminRoute && !forceLayout) {
|
|
113
|
+
if (!isMounted || isLoading) {
|
|
114
|
+
return (
|
|
115
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
116
|
+
<div className="text-muted-foreground">Loading...</div>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// After mount: check authentication
|
|
122
|
+
if (!isAuthenticated) {
|
|
123
|
+
// Redirect to auth
|
|
124
|
+
return (
|
|
125
|
+
<AuthLayout
|
|
126
|
+
termsUrl={config.auth?.termsUrl}
|
|
127
|
+
privacyUrl={config.auth?.privacyUrl}
|
|
128
|
+
supportUrl={config.auth?.supportUrl}
|
|
129
|
+
enablePhoneAuth={config.auth?.enablePhoneAuth}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Admin routes use AdminLayout with ParentSync
|
|
135
|
+
return (
|
|
136
|
+
<AdminLayout enableParentSync={true}>
|
|
137
|
+
{children}
|
|
138
|
+
</AdminLayout>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
109
141
|
|
|
110
142
|
// Private routes: Always show loading during SSR and initial client render
|
|
111
143
|
// This prevents hydration mismatch when isAuthenticated differs between server/client
|
|
@@ -138,8 +170,8 @@ function LayoutRouter({
|
|
|
138
170
|
const getLayoutMode = (): 'public' | 'auth' | 'admin' => {
|
|
139
171
|
if (forceLayout === 'auth') return 'auth';
|
|
140
172
|
if (forceLayout === 'public') return 'public';
|
|
173
|
+
if (forceLayout === 'admin') return 'admin';
|
|
141
174
|
if (isAuthRoute) return 'auth';
|
|
142
|
-
if (isAdminMode) return 'admin';
|
|
143
175
|
return 'public';
|
|
144
176
|
};
|
|
145
177
|
|
|
@@ -148,8 +180,11 @@ function LayoutRouter({
|
|
|
148
180
|
// Render appropriate layout
|
|
149
181
|
switch (layoutMode) {
|
|
150
182
|
case 'admin':
|
|
151
|
-
return
|
|
152
|
-
|
|
183
|
+
return (
|
|
184
|
+
<AdminLayout enableParentSync={true}>
|
|
185
|
+
{children}
|
|
186
|
+
</AdminLayout>
|
|
187
|
+
);
|
|
153
188
|
// Public routes: render immediately (SSR enabled)
|
|
154
189
|
case 'public':
|
|
155
190
|
return <PublicLayout>{children}</PublicLayout>;
|
|
@@ -209,8 +244,31 @@ export function AppLayout({ children, config, disableLayout = false, forceLayout
|
|
|
209
244
|
const supportEmail = config.errors?.supportEmail;
|
|
210
245
|
const onError = config.errors?.onError;
|
|
211
246
|
|
|
212
|
-
|
|
213
|
-
|
|
247
|
+
const appContent = (
|
|
248
|
+
<AppContextProvider config={config} showPackageVersions={showPackageVersions}>
|
|
249
|
+
{/* SEO Meta Tags */}
|
|
250
|
+
<Seo
|
|
251
|
+
pageConfig={{
|
|
252
|
+
title: config.app.name,
|
|
253
|
+
description: config.app.description,
|
|
254
|
+
ogImage: {
|
|
255
|
+
title: config.app.name,
|
|
256
|
+
subtitle: config.app.description,
|
|
257
|
+
},
|
|
258
|
+
}}
|
|
259
|
+
icons={config.app.icons}
|
|
260
|
+
siteUrl={config.app.siteUrl}
|
|
261
|
+
/>
|
|
262
|
+
|
|
263
|
+
{/* Loading Progress Bar */}
|
|
264
|
+
<PageProgress />
|
|
265
|
+
|
|
266
|
+
{/* Smart Layout Router */}
|
|
267
|
+
<LayoutRouter disableLayout={disableLayout} forceLayout={forceLayout} config={config}>
|
|
268
|
+
{children}
|
|
269
|
+
</LayoutRouter>
|
|
270
|
+
</AppContextProvider>
|
|
271
|
+
);
|
|
214
272
|
|
|
215
273
|
const content = (
|
|
216
274
|
<>
|
|
@@ -222,33 +280,7 @@ export function AppLayout({ children, config, disableLayout = false, forceLayout
|
|
|
222
280
|
)}
|
|
223
281
|
|
|
224
282
|
<CoreProviders config={config}>
|
|
225
|
-
{
|
|
226
|
-
{/* Only enable ParentSync when in admin mode */}
|
|
227
|
-
<CfgLayout enableParentSync={isAdminMode}>
|
|
228
|
-
<AppContextProvider config={config} showPackageVersions={showPackageVersions}>
|
|
229
|
-
{/* SEO Meta Tags */}
|
|
230
|
-
<Seo
|
|
231
|
-
pageConfig={{
|
|
232
|
-
title: config.app.name,
|
|
233
|
-
description: config.app.description,
|
|
234
|
-
ogImage: {
|
|
235
|
-
title: config.app.name,
|
|
236
|
-
subtitle: config.app.description,
|
|
237
|
-
},
|
|
238
|
-
}}
|
|
239
|
-
icons={config.app.icons}
|
|
240
|
-
siteUrl={config.app.siteUrl}
|
|
241
|
-
/>
|
|
242
|
-
|
|
243
|
-
{/* Loading Progress Bar */}
|
|
244
|
-
<PageProgress />
|
|
245
|
-
|
|
246
|
-
{/* Smart Layout Router */}
|
|
247
|
-
<LayoutRouter disableLayout={disableLayout} forceLayout={forceLayout} config={config}>
|
|
248
|
-
{children}
|
|
249
|
-
</LayoutRouter>
|
|
250
|
-
</AppContextProvider>
|
|
251
|
-
</CfgLayout>
|
|
283
|
+
{appContent}
|
|
252
284
|
</CoreProviders>
|
|
253
285
|
</>
|
|
254
286
|
);
|
|
@@ -16,36 +16,36 @@ export interface PackageInfo {
|
|
|
16
16
|
/**
|
|
17
17
|
* Package versions registry
|
|
18
18
|
* Auto-synced from package.json files
|
|
19
|
-
* Last updated: 2025-10-
|
|
19
|
+
* Last updated: 2025-10-29T03:56:18.725Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.10"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.10"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.10"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.10"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.10"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.10"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.10"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -30,14 +30,14 @@ export { PublicLayout } from './layouts/PublicLayout';
|
|
|
30
30
|
export { PrivateLayout } from './layouts/PrivateLayout';
|
|
31
31
|
export { AuthLayout } from './layouts/AuthLayout';
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
export {
|
|
35
|
-
export { useCfgApp, useApp } from './layouts/
|
|
36
|
-
export { ParentSync, AuthStatusSync } from './layouts/
|
|
33
|
+
// AdminLayout - Django CFG iframe integration
|
|
34
|
+
export { AdminLayout } from './layouts/AdminLayout';
|
|
35
|
+
export { useCfgApp, useApp } from './layouts/AdminLayout';
|
|
36
|
+
export { ParentSync, AuthStatusSync } from './layouts/AdminLayout';
|
|
37
37
|
export type {
|
|
38
|
-
|
|
38
|
+
AdminLayoutConfig,
|
|
39
39
|
UseCfgAppReturn,
|
|
40
40
|
UseCfgAppOptions,
|
|
41
41
|
UseAppReturn,
|
|
42
42
|
UseAppOptions
|
|
43
|
-
} from './layouts/
|
|
43
|
+
} from './layouts/AdminLayout';
|
package/src/layouts/AppLayout/layouts/{CfgLayout/CfgLayout.tsx → AdminLayout/AdminLayout.tsx}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
|
-
//
|
|
2
|
+
// AdminLayout - Django CFG Layout with iframe Integration
|
|
3
3
|
// ============================================================================
|
|
4
4
|
// Universal layout component that handles:
|
|
5
5
|
// - iframe embedding detection
|
|
@@ -15,14 +15,16 @@
|
|
|
15
15
|
'use client';
|
|
16
16
|
|
|
17
17
|
import React, { ReactNode } from 'react';
|
|
18
|
+
import { ShieldAlert } from 'lucide-react';
|
|
18
19
|
import { ParentSync } from './components';
|
|
19
20
|
import { useCfgApp } from './hooks';
|
|
20
|
-
import type {
|
|
21
|
+
import type { AdminLayoutConfig } from './types';
|
|
21
22
|
import { api } from '@djangocfg/api';
|
|
23
|
+
import { useAuth } from '../../../../auth';
|
|
22
24
|
|
|
23
|
-
export interface
|
|
25
|
+
export interface AdminLayoutProps {
|
|
24
26
|
children: ReactNode;
|
|
25
|
-
config?:
|
|
27
|
+
config?: AdminLayoutConfig;
|
|
26
28
|
/**
|
|
27
29
|
* Whether to render ParentSync component
|
|
28
30
|
* Set to false if you want to handle sync manually
|
|
@@ -32,7 +34,7 @@ export interface CfgLayoutProps {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
37
|
+
* AdminLayout - Universal Layout Component for Django CFG
|
|
36
38
|
*
|
|
37
39
|
* Provides iframe integration features:
|
|
38
40
|
* - Auto-detects iframe embedding
|
|
@@ -44,16 +46,16 @@ export interface CfgLayoutProps {
|
|
|
44
46
|
* Usage:
|
|
45
47
|
* ```tsx
|
|
46
48
|
* // Wrap your app in _app.tsx - no config needed!
|
|
47
|
-
* <
|
|
49
|
+
* <AdminLayout>
|
|
48
50
|
* <AppLayout config={appLayoutConfig}>
|
|
49
51
|
* <Component {...pageProps} />
|
|
50
52
|
* </AppLayout>
|
|
51
|
-
* </
|
|
53
|
+
* </AdminLayout>
|
|
52
54
|
* ```
|
|
53
55
|
*
|
|
54
56
|
* Or with custom auth handler:
|
|
55
57
|
* ```tsx
|
|
56
|
-
* <
|
|
58
|
+
* <AdminLayout config={{
|
|
57
59
|
* onAuthTokenReceived: (authToken, refreshToken) => {
|
|
58
60
|
* // Custom logic before/after setting tokens
|
|
59
61
|
* console.log('Tokens received');
|
|
@@ -62,12 +64,12 @@ export interface CfgLayoutProps {
|
|
|
62
64
|
* <AppLayout config={appLayoutConfig}>
|
|
63
65
|
* <Component {...pageProps} />
|
|
64
66
|
* </AppLayout>
|
|
65
|
-
* </
|
|
67
|
+
* </AdminLayout>
|
|
66
68
|
* ```
|
|
67
69
|
*
|
|
68
70
|
* Use useCfgApp hook directly:
|
|
69
71
|
* ```tsx
|
|
70
|
-
* import { useCfgApp } from '@djangocfg/layouts/
|
|
72
|
+
* import { useCfgApp } from '@djangocfg/layouts/AdminLayout';
|
|
71
73
|
*
|
|
72
74
|
* function MyComponent() {
|
|
73
75
|
* const { isEmbedded, disableLayout, parentTheme } = useCfgApp();
|
|
@@ -75,11 +77,13 @@ export interface CfgLayoutProps {
|
|
|
75
77
|
* }
|
|
76
78
|
* ```
|
|
77
79
|
*/
|
|
78
|
-
export function
|
|
80
|
+
export function AdminLayout({
|
|
79
81
|
children,
|
|
80
82
|
config,
|
|
81
83
|
enableParentSync = true
|
|
82
|
-
}:
|
|
84
|
+
}: AdminLayoutProps) {
|
|
85
|
+
const { user } = useAuth();
|
|
86
|
+
|
|
83
87
|
// useCfgApp hook is called here to initialize iframe communication
|
|
84
88
|
// Automatically sets tokens in API client when received from parent
|
|
85
89
|
const { isEmbedded } = useCfgApp({
|
|
@@ -94,13 +98,35 @@ export function CfgLayout({
|
|
|
94
98
|
}
|
|
95
99
|
});
|
|
96
100
|
|
|
101
|
+
// Check if user has staff or superuser privileges
|
|
102
|
+
const hasAdminAccess = user?.is_staff || user?.is_superuser;
|
|
103
|
+
|
|
104
|
+
// Only render AdminLayout features for staff/superuser
|
|
105
|
+
if (!hasAdminAccess) {
|
|
106
|
+
return (
|
|
107
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
108
|
+
<div className="text-center space-y-4 p-8">
|
|
109
|
+
<div className="flex justify-center">
|
|
110
|
+
<ShieldAlert className="w-16 h-16 text-destructive" />
|
|
111
|
+
</div>
|
|
112
|
+
<h1 className="text-2xl font-bold text-foreground">Access Denied</h1>
|
|
113
|
+
<p className="text-muted-foreground max-w-md">
|
|
114
|
+
You don't have permission to access this admin panel.
|
|
115
|
+
<br />
|
|
116
|
+
Staff or superuser privileges are required.
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
return (
|
|
98
124
|
<>
|
|
99
125
|
{/* ParentSync handles theme sync and auth status reporting */}
|
|
100
126
|
{enableParentSync && <ParentSync />}
|
|
101
127
|
|
|
102
128
|
{/* Apply padding only when NOT in iframe */}
|
|
103
|
-
<div className=
|
|
129
|
+
<div className="p-6">
|
|
104
130
|
{children}
|
|
105
131
|
</div>
|
|
106
132
|
</>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AdminLayout - Django CFG Layout with iframe Integration
|
|
2
2
|
|
|
3
3
|
Universal layout component for Django CFG applications that handles iframe embedding, theme synchronization, and authentication communication.
|
|
4
4
|
|
|
5
|
-
**Note:**
|
|
5
|
+
**Note:** AdminLayout is now integrated into AppLayout. For most use cases, use `AppLayout` with `isCfgAdmin={true}` instead of using AdminLayout directly.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -38,28 +38,28 @@ function MyApp({ Component, pageProps }) {
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
**That's it!** Setting `isCfgAdmin={true}` automatically:
|
|
41
|
-
- Wraps your app with
|
|
41
|
+
- Wraps your app with AdminLayout
|
|
42
42
|
- Sets up iframe communication
|
|
43
43
|
- Handles auth token passing
|
|
44
44
|
- Syncs theme from parent window
|
|
45
45
|
|
|
46
46
|
## Direct Usage (Advanced)
|
|
47
47
|
|
|
48
|
-
If you need more control, you can use
|
|
48
|
+
If you need more control, you can use AdminLayout directly:
|
|
49
49
|
|
|
50
|
-
### 1. Wrap your app with
|
|
50
|
+
### 1. Wrap your app with AdminLayout (Zero Config!)
|
|
51
51
|
|
|
52
52
|
```tsx
|
|
53
53
|
// apps/admin/src/pages/_app.tsx
|
|
54
|
-
import {
|
|
54
|
+
import { AdminLayout, AppLayout } from '@djangocfg/layouts';
|
|
55
55
|
|
|
56
56
|
function MyApp({ Component, pageProps }) {
|
|
57
57
|
return (
|
|
58
|
-
<
|
|
58
|
+
<AdminLayout>
|
|
59
59
|
<AppLayout config={appLayoutConfig}>
|
|
60
60
|
<Component {...pageProps} />
|
|
61
61
|
</AppLayout>
|
|
62
|
-
</
|
|
62
|
+
</AdminLayout>
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
```
|
|
@@ -70,7 +70,7 @@ function MyApp({ Component, pageProps }) {
|
|
|
70
70
|
|
|
71
71
|
```tsx
|
|
72
72
|
// Only if you need additional logic when tokens are received
|
|
73
|
-
<
|
|
73
|
+
<AdminLayout
|
|
74
74
|
config={{
|
|
75
75
|
onAuthTokenReceived: (authToken, refreshToken) => {
|
|
76
76
|
// Tokens are already set in API client automatically
|
|
@@ -82,7 +82,7 @@ function MyApp({ Component, pageProps }) {
|
|
|
82
82
|
<AppLayout config={appLayoutConfig}>
|
|
83
83
|
<Component {...pageProps} />
|
|
84
84
|
</AppLayout>
|
|
85
|
-
</
|
|
85
|
+
</AdminLayout>
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
## Hook Usage
|
|
@@ -140,20 +140,20 @@ function MyApp({ Component, pageProps }) {
|
|
|
140
140
|
</AppLayout>
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
###
|
|
143
|
+
### AdminLayout Props
|
|
144
144
|
|
|
145
145
|
| Prop | Type | Default | Description |
|
|
146
146
|
|------|------|---------|-------------|
|
|
147
147
|
| `children` | `ReactNode` | Required | Your app content |
|
|
148
|
-
| `config` | `
|
|
148
|
+
| `config` | `AdminLayoutConfig` | `{}` | Configuration object |
|
|
149
149
|
| `enableParentSync` | `boolean` | `true` | Enable automatic theme/auth sync |
|
|
150
150
|
|
|
151
|
-
###
|
|
151
|
+
### AdminLayoutConfig
|
|
152
152
|
|
|
153
|
-
**All options are optional!**
|
|
153
|
+
**All options are optional!** AdminLayout works with zero configuration.
|
|
154
154
|
|
|
155
155
|
```typescript
|
|
156
|
-
interface
|
|
156
|
+
interface AdminLayoutConfig {
|
|
157
157
|
// Optional: Called when auth tokens are received from parent
|
|
158
158
|
// Note: Tokens are ALWAYS automatically set in API client
|
|
159
159
|
// Use this only if you need additional custom logic
|
|
@@ -230,9 +230,9 @@ function MyThemeSync() {
|
|
|
230
230
|
### Disable Auto Sync
|
|
231
231
|
|
|
232
232
|
```tsx
|
|
233
|
-
<
|
|
233
|
+
<AdminLayout enableParentSync={false}>
|
|
234
234
|
{/* Handle sync manually */}
|
|
235
|
-
</
|
|
235
|
+
</AdminLayout>
|
|
236
236
|
```
|
|
237
237
|
|
|
238
238
|
### URL Override
|
|
@@ -289,7 +289,7 @@ import { ParentSync } from '@djangocfg/layouts';
|
|
|
289
289
|
|
|
290
290
|
**Old approach:**
|
|
291
291
|
```tsx
|
|
292
|
-
import { AppLayout,
|
|
292
|
+
import { AppLayout, AdminLayout, useCfgApp } from '@djangocfg/layouts';
|
|
293
293
|
|
|
294
294
|
function AppLayoutWrapper() {
|
|
295
295
|
const { disableLayout, isEmbedded } = useCfgApp();
|
|
@@ -302,11 +302,11 @@ function AppLayoutWrapper() {
|
|
|
302
302
|
|
|
303
303
|
export default function App() {
|
|
304
304
|
return (
|
|
305
|
-
<
|
|
305
|
+
<AdminLayout>
|
|
306
306
|
<AppLayoutWrapper>
|
|
307
307
|
{children}
|
|
308
308
|
</AppLayoutWrapper>
|
|
309
|
-
</
|
|
309
|
+
</AdminLayout>
|
|
310
310
|
);
|
|
311
311
|
}
|
|
312
312
|
```
|
|
@@ -368,7 +368,7 @@ export default function App({ Component, pageProps }) {
|
|
|
368
368
|
|
|
369
369
|
**With custom auth logic:**
|
|
370
370
|
```tsx
|
|
371
|
-
<
|
|
371
|
+
<AdminLayout
|
|
372
372
|
config={{
|
|
373
373
|
onAuthTokenReceived: (token, refresh) => {
|
|
374
374
|
console.log('Tokens received from parent');
|
|
@@ -379,11 +379,11 @@ export default function App({ Component, pageProps }) {
|
|
|
379
379
|
<AppLayout config={config}>
|
|
380
380
|
<Component {...pageProps} />
|
|
381
381
|
</AppLayout>
|
|
382
|
-
</
|
|
382
|
+
</AdminLayout>
|
|
383
383
|
```
|
|
384
384
|
|
|
385
385
|
## Related Files
|
|
386
386
|
|
|
387
387
|
- Django Template: `src/django_cfg/templates/admin/index.html`
|
|
388
388
|
- Django Views: `src/django_cfg/apps/frontend/views.py`
|
|
389
|
-
- Example App: `src
|
|
389
|
+
- Example App: `src/django_admin/apps/admin/src/pages/_app.tsx`
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* AdminLayout - Django CFG Layout with iframe Integration
|
|
3
3
|
*
|
|
4
4
|
* Universal layout component for Django CFG applications
|
|
5
5
|
* Handles iframe embedding, theme sync, and auth communication
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Main component
|
|
9
|
-
export {
|
|
10
|
-
export type {
|
|
9
|
+
export { AdminLayout } from './AdminLayout';
|
|
10
|
+
export type { AdminLayoutProps } from './AdminLayout';
|
|
11
11
|
|
|
12
12
|
// Hooks
|
|
13
13
|
export { useCfgApp, useApp } from './hooks';
|
|
@@ -17,4 +17,4 @@ export type { UseCfgAppReturn, UseCfgAppOptions, UseAppReturn, UseAppOptions } f
|
|
|
17
17
|
export { ParentSync, AuthStatusSync } from './components';
|
|
18
18
|
|
|
19
19
|
// Types
|
|
20
|
-
export type {
|
|
20
|
+
export type { AdminLayoutConfig } from './types';
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Configuration for
|
|
5
|
-
* All options are optional -
|
|
4
|
+
* Configuration for AdminLayout
|
|
5
|
+
* All options are optional - AdminLayout works out of the box with zero config
|
|
6
6
|
*/
|
|
7
|
-
export interface
|
|
7
|
+
export interface AdminLayoutConfig {
|
|
8
8
|
/**
|
|
9
9
|
* Optional handler called when auth tokens are received from parent window
|
|
10
10
|
*
|
|
@@ -13,7 +13,7 @@ export interface CfgLayoutConfig {
|
|
|
13
13
|
*
|
|
14
14
|
* @example
|
|
15
15
|
* ```tsx
|
|
16
|
-
* <
|
|
16
|
+
* <AdminLayout config={{
|
|
17
17
|
* onAuthTokenReceived: (authToken, refreshToken) => {
|
|
18
18
|
* console.log('Tokens received and set in API client');
|
|
19
19
|
* // Additional custom logic here
|
|
@@ -37,9 +37,9 @@ export interface CfgLayoutConfig {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Props for
|
|
40
|
+
* Props for AdminLayout component
|
|
41
41
|
*/
|
|
42
|
-
export interface
|
|
42
|
+
export interface AdminLayoutProps {
|
|
43
43
|
children: ReactNode;
|
|
44
|
-
config?:
|
|
44
|
+
config?: AdminLayoutConfig;
|
|
45
45
|
}
|
|
@@ -14,6 +14,7 @@ import { useAppContext } from '../../../context';
|
|
|
14
14
|
interface DashboardContentProps {
|
|
15
15
|
children: React.ReactNode;
|
|
16
16
|
className?: string;
|
|
17
|
+
isAdmin?: boolean;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const paddingVariants = {
|
|
@@ -37,6 +38,7 @@ const paddingVariants = {
|
|
|
37
38
|
export function DashboardContent({
|
|
38
39
|
children,
|
|
39
40
|
className,
|
|
41
|
+
isAdmin = false,
|
|
40
42
|
}: DashboardContentProps) {
|
|
41
43
|
const { config } = useAppContext();
|
|
42
44
|
const { privateLayout } = config;
|
|
@@ -20,6 +20,10 @@ import { useAppContext } from '../../../context';
|
|
|
20
20
|
import { useNavigation } from '../../../hooks';
|
|
21
21
|
import { UserMenu } from '../../../components';
|
|
22
22
|
|
|
23
|
+
export interface DashboardHeaderProps {
|
|
24
|
+
isAdmin?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
/**
|
|
24
28
|
* Dashboard Header Component
|
|
25
29
|
*
|
|
@@ -34,7 +38,7 @@ import { UserMenu } from '../../../components';
|
|
|
34
38
|
*
|
|
35
39
|
* All data from context!
|
|
36
40
|
*/
|
|
37
|
-
export function DashboardHeader() {
|
|
41
|
+
export function DashboardHeader({ isAdmin = false }: DashboardHeaderProps) {
|
|
38
42
|
const { config } = useAppContext();
|
|
39
43
|
const { getPageTitle } = useNavigation();
|
|
40
44
|
|
|
@@ -31,6 +31,10 @@ import { useAppContext } from '../../../context';
|
|
|
31
31
|
import { useNavigation } from '../../../hooks';
|
|
32
32
|
import { PackageVersions } from '../../../components';
|
|
33
33
|
|
|
34
|
+
export interface DashboardSidebarProps {
|
|
35
|
+
isAdmin?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
/**
|
|
35
39
|
* Dashboard Sidebar Component
|
|
36
40
|
*
|
|
@@ -44,12 +48,28 @@ import { PackageVersions } from '../../../components';
|
|
|
44
48
|
*
|
|
45
49
|
* All data from context!
|
|
46
50
|
*/
|
|
47
|
-
export function DashboardSidebar() {
|
|
51
|
+
export function DashboardSidebar({ isAdmin = false }: DashboardSidebarProps) {
|
|
48
52
|
const { config, showPackageVersions } = useAppContext();
|
|
49
53
|
const { currentPath } = useNavigation();
|
|
50
54
|
const { state, isMobile } = useSidebar();
|
|
51
55
|
|
|
52
|
-
const { app, privateLayout } = config;
|
|
56
|
+
const { app, privateLayout, adminLayout } = config;
|
|
57
|
+
|
|
58
|
+
// Admin layout: use adminLayout.menuSections converted to menuGroups, fallback to privateLayout
|
|
59
|
+
const menuGroups = isAdmin && adminLayout
|
|
60
|
+
? adminLayout.menuSections.map((section, idx) => ({
|
|
61
|
+
label: section.title,
|
|
62
|
+
order: idx + 1,
|
|
63
|
+
items: section.items.map(item => ({
|
|
64
|
+
path: item.path,
|
|
65
|
+
label: item.label,
|
|
66
|
+
icon: item.icon || (() => null),
|
|
67
|
+
badge: item.badge,
|
|
68
|
+
})),
|
|
69
|
+
}))
|
|
70
|
+
: privateLayout.menuGroups;
|
|
71
|
+
|
|
72
|
+
const homeHref = privateLayout.homeHref;
|
|
53
73
|
|
|
54
74
|
const isActiveRoute = (path: string) => {
|
|
55
75
|
// Only exact match - no prefix matching
|
|
@@ -73,7 +93,7 @@ export function DashboardSidebar() {
|
|
|
73
93
|
transition: 'padding 200ms ease-in-out'
|
|
74
94
|
}}
|
|
75
95
|
>
|
|
76
|
-
<Link href={
|
|
96
|
+
<Link href={homeHref}>
|
|
77
97
|
<div className="flex items-center gap-3">
|
|
78
98
|
{app.logoPath ? (
|
|
79
99
|
<img
|
|
@@ -99,7 +119,7 @@ export function DashboardSidebar() {
|
|
|
99
119
|
</SidebarHeader>
|
|
100
120
|
|
|
101
121
|
<SidebarContent>
|
|
102
|
-
{
|
|
122
|
+
{menuGroups.map((group) => (
|
|
103
123
|
<SidebarGroup key={group.label}>
|
|
104
124
|
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
|
|
105
125
|
<SidebarGroupContent>
|
|
@@ -3,5 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { DashboardHeader } from './DashboardHeader';
|
|
6
|
+
export type { DashboardHeaderProps } from './DashboardHeader';
|
|
6
7
|
export { DashboardSidebar } from './DashboardSidebar';
|
|
8
|
+
export type { DashboardSidebarProps } from './DashboardSidebar';
|
|
7
9
|
export { DashboardContent } from './DashboardContent';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ReactNode } from 'react';
|
|
6
|
-
import type { PublicLayoutConfig, PrivateLayoutConfig } from './layout';
|
|
6
|
+
import type { PublicLayoutConfig, PrivateLayoutConfig, AdminLayoutConfig } from './layout';
|
|
7
7
|
import type { RouteConfig } from './routes';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -38,6 +38,9 @@ export interface AppLayoutConfig {
|
|
|
38
38
|
/** Private layout configuration */
|
|
39
39
|
privateLayout: PrivateLayoutConfig;
|
|
40
40
|
|
|
41
|
+
/** Admin layout configuration (optional) */
|
|
42
|
+
adminLayout?: AdminLayoutConfig;
|
|
43
|
+
|
|
41
44
|
/** Error handling configuration */
|
|
42
45
|
errors?: {
|
|
43
46
|
/** Enable automatic error boundary (default: true) */
|
|
@@ -29,6 +29,9 @@ export interface RouteDetectors {
|
|
|
29
29
|
/** Check if route is private/protected */
|
|
30
30
|
isPrivateRoute: (path: string) => boolean;
|
|
31
31
|
|
|
32
|
+
/** Check if route is admin */
|
|
33
|
+
isAdminRoute: (path: string) => boolean;
|
|
34
|
+
|
|
32
35
|
/** Check if route is auth page */
|
|
33
36
|
isAuthRoute: (path: string) => boolean;
|
|
34
37
|
|
|
@@ -42,4 +45,4 @@ export interface RouteDetectors {
|
|
|
42
45
|
/**
|
|
43
46
|
* Layout mode based on route
|
|
44
47
|
*/
|
|
45
|
-
export type LayoutMode = 'public' | 'private' | 'auth';
|
|
48
|
+
export type LayoutMode = 'public' | 'private' | 'admin' | 'auth';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|