@djangocfg/layouts 1.4.29 → 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 +8 -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,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App Context
|
|
3
|
-
*
|
|
4
|
-
* Unified context for entire application layout system
|
|
5
|
-
* Provides centralized access to configuration, state, and utilities
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
'use client';
|
|
9
|
-
|
|
10
|
-
import React, { createContext, useContext, useState, ReactNode, useMemo } from 'react';
|
|
11
|
-
import { useRouter } from 'next/router';
|
|
12
|
-
import type { AppLayoutConfig, LayoutMode, RouteDetectors } from '../types';
|
|
13
|
-
|
|
14
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
-
// Context Types
|
|
16
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
17
|
-
|
|
18
|
-
interface AppContextValue {
|
|
19
|
-
// Configuration
|
|
20
|
-
config: AppLayoutConfig;
|
|
21
|
-
|
|
22
|
-
// Route detection
|
|
23
|
-
routes: RouteDetectors;
|
|
24
|
-
currentPath: string;
|
|
25
|
-
layoutMode: LayoutMode;
|
|
26
|
-
|
|
27
|
-
// Mobile drawer state
|
|
28
|
-
mobileDrawerOpen: boolean;
|
|
29
|
-
openMobileDrawer: () => void;
|
|
30
|
-
closeMobileDrawer: () => void;
|
|
31
|
-
toggleMobileDrawer: () => void;
|
|
32
|
-
|
|
33
|
-
// User menu state (desktop dropdown)
|
|
34
|
-
userMenuOpen: boolean;
|
|
35
|
-
openUserMenu: () => void;
|
|
36
|
-
closeUserMenu: () => void;
|
|
37
|
-
toggleUserMenu: () => void;
|
|
38
|
-
|
|
39
|
-
// Sidebar state (dashboard)
|
|
40
|
-
sidebarCollapsed: boolean;
|
|
41
|
-
collapseSidebar: () => void;
|
|
42
|
-
expandSidebar: () => void;
|
|
43
|
-
toggleSidebar: () => void;
|
|
44
|
-
|
|
45
|
-
// Features
|
|
46
|
-
showUpdateNotifier?: boolean;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
50
|
-
// Context Creation
|
|
51
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
52
|
-
|
|
53
|
-
const AppContext = createContext<AppContextValue | null>(null);
|
|
54
|
-
|
|
55
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
56
|
-
// Provider Component
|
|
57
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
58
|
-
|
|
59
|
-
export interface AppContextProviderProps {
|
|
60
|
-
children: ReactNode;
|
|
61
|
-
config: AppLayoutConfig;
|
|
62
|
-
showUpdateNotifier?: boolean;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* AppContext Provider
|
|
67
|
-
*
|
|
68
|
-
* Provides unified application context to all child components
|
|
69
|
-
* Manages layout state and exposes configuration
|
|
70
|
-
*/
|
|
71
|
-
export function AppContextProvider({ children, config, showUpdateNotifier }: AppContextProviderProps) {
|
|
72
|
-
const router = useRouter();
|
|
73
|
-
|
|
74
|
-
// UI state
|
|
75
|
-
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
|
76
|
-
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
|
77
|
-
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
78
|
-
|
|
79
|
-
// Determine current layout mode
|
|
80
|
-
const layoutMode = useMemo((): LayoutMode => {
|
|
81
|
-
const { isAuthRoute, isPrivateRoute } = config.routes.detectors;
|
|
82
|
-
|
|
83
|
-
if (isAuthRoute(router.pathname)) return 'auth';
|
|
84
|
-
if (isPrivateRoute(router.pathname)) return 'private';
|
|
85
|
-
return 'public';
|
|
86
|
-
}, [router.pathname, config.routes.detectors]);
|
|
87
|
-
|
|
88
|
-
// Mobile drawer handlers
|
|
89
|
-
const openMobileDrawer = () => setMobileDrawerOpen(true);
|
|
90
|
-
const closeMobileDrawer = () => setMobileDrawerOpen(false);
|
|
91
|
-
const toggleMobileDrawer = () => setMobileDrawerOpen(prev => !prev);
|
|
92
|
-
|
|
93
|
-
// User menu handlers
|
|
94
|
-
const openUserMenu = () => setUserMenuOpen(true);
|
|
95
|
-
const closeUserMenu = () => setUserMenuOpen(false);
|
|
96
|
-
const toggleUserMenu = () => setUserMenuOpen(prev => !prev);
|
|
97
|
-
|
|
98
|
-
// Sidebar handlers
|
|
99
|
-
const collapseSidebar = () => setSidebarCollapsed(true);
|
|
100
|
-
const expandSidebar = () => setSidebarCollapsed(false);
|
|
101
|
-
const toggleSidebar = () => setSidebarCollapsed(prev => !prev);
|
|
102
|
-
|
|
103
|
-
const value: AppContextValue = {
|
|
104
|
-
config,
|
|
105
|
-
routes: config.routes.detectors,
|
|
106
|
-
currentPath: router.pathname,
|
|
107
|
-
layoutMode,
|
|
108
|
-
mobileDrawerOpen,
|
|
109
|
-
openMobileDrawer,
|
|
110
|
-
closeMobileDrawer,
|
|
111
|
-
toggleMobileDrawer,
|
|
112
|
-
userMenuOpen,
|
|
113
|
-
openUserMenu,
|
|
114
|
-
closeUserMenu,
|
|
115
|
-
toggleUserMenu,
|
|
116
|
-
sidebarCollapsed,
|
|
117
|
-
collapseSidebar,
|
|
118
|
-
expandSidebar,
|
|
119
|
-
toggleSidebar,
|
|
120
|
-
showUpdateNotifier,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
127
|
-
// Hook
|
|
128
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Hook to access AppContext
|
|
132
|
-
*
|
|
133
|
-
* @throws {Error} If used outside AppContextProvider
|
|
134
|
-
*
|
|
135
|
-
* @example
|
|
136
|
-
* ```tsx
|
|
137
|
-
* const { config, layoutMode, toggleMobileDrawer } = useAppContext();
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
export function useAppContext(): AppContextValue {
|
|
141
|
-
const context = useContext(AppContext);
|
|
142
|
-
|
|
143
|
-
if (!context) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
'useAppContext must be used within AppContextProvider. ' +
|
|
146
|
-
'Make sure your component is wrapped with <AppLayout>.'
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return context;
|
|
151
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useLayoutMode Hook
|
|
3
|
-
*
|
|
4
|
-
* Returns current layout mode
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { useAppContext } from '../context';
|
|
8
|
-
import type { LayoutMode } from '../types';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get current layout mode
|
|
12
|
-
*
|
|
13
|
-
* @returns Current layout mode ('public' | 'private' | 'auth')
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* const mode = useLayoutMode();
|
|
18
|
-
* if (mode === 'private') {
|
|
19
|
-
* return <DashboardContent />;
|
|
20
|
-
* }
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export function useLayoutMode(): LayoutMode {
|
|
24
|
-
const { layoutMode } = useAppContext();
|
|
25
|
-
return layoutMode;
|
|
26
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useNavigation Hook
|
|
3
|
-
*
|
|
4
|
-
* Navigation utilities
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use client';
|
|
8
|
-
|
|
9
|
-
import { useRouter } from 'next/navigation';
|
|
10
|
-
import { useAppContext } from '../context';
|
|
11
|
-
|
|
12
|
-
export interface UseNavigationReturn {
|
|
13
|
-
/** Current pathname */
|
|
14
|
-
currentPath: string;
|
|
15
|
-
|
|
16
|
-
/** Check if path is active */
|
|
17
|
-
isActive: (path: string) => boolean;
|
|
18
|
-
|
|
19
|
-
/** Get page title for current route */
|
|
20
|
-
getPageTitle: () => string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Navigation utilities hook
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```tsx
|
|
28
|
-
* const { isActive, getPageTitle } = useNavigation();
|
|
29
|
-
* const title = getPageTitle();
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export function useNavigation(): UseNavigationReturn {
|
|
33
|
-
const router = useRouter();
|
|
34
|
-
const { routes, currentPath } = useAppContext();
|
|
35
|
-
|
|
36
|
-
const isActive = (path: string): boolean => {
|
|
37
|
-
if (currentPath === path) return true;
|
|
38
|
-
if (path !== '/' && currentPath.startsWith(path)) return true;
|
|
39
|
-
return false;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const getPageTitle = (): string => {
|
|
43
|
-
return routes.getPageTitle(currentPath);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
currentPath,
|
|
48
|
-
isActive,
|
|
49
|
-
getPageTitle,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
// ============================================================================
|
|
3
|
-
// AdminLayout - Django CFG Layout with iframe Integration
|
|
4
|
-
// ============================================================================
|
|
5
|
-
// Universal layout component that handles:
|
|
6
|
-
// - iframe embedding detection
|
|
7
|
-
// - Parent ↔ iframe communication (postMessage)
|
|
8
|
-
// - Theme synchronization from Django Unfold
|
|
9
|
-
// - Auth token passing from parent window (automatically sets in API client)
|
|
10
|
-
// - Auth status reporting to parent window
|
|
11
|
-
// - Automatic layout disable in iframe mode
|
|
12
|
-
//
|
|
13
|
-
// This is a lightweight wrapper that can be used with any layout system
|
|
14
|
-
// (AppLayout, custom layouts, etc.)
|
|
15
|
-
|
|
16
|
-
'use client';
|
|
17
|
-
|
|
18
|
-
import React, { ReactNode } from 'react';
|
|
19
|
-
import { ShieldAlert } from 'lucide-react';
|
|
20
|
-
import { ParentSync, PagePreloader } from './components';
|
|
21
|
-
import { CfgAppProvider, useCfgAppContext } from './context';
|
|
22
|
-
import type { AdminLayoutConfig } from './types';
|
|
23
|
-
import { api } from '@djangocfg/api';
|
|
24
|
-
import { useAuth } from '../../../../auth';
|
|
25
|
-
import { consola } from 'consola';
|
|
26
|
-
|
|
27
|
-
export interface AdminLayoutProps {
|
|
28
|
-
children: ReactNode;
|
|
29
|
-
config?: AdminLayoutConfig;
|
|
30
|
-
/**
|
|
31
|
-
* Whether to render ParentSync component
|
|
32
|
-
* Set to false if you want to handle sync manually
|
|
33
|
-
* @default true
|
|
34
|
-
*/
|
|
35
|
-
enableParentSync?: boolean;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* AdminLayout - Universal Layout Component for Django CFG
|
|
40
|
-
*
|
|
41
|
-
* Provides iframe integration features:
|
|
42
|
-
* - Auto-detects iframe embedding
|
|
43
|
-
* - Syncs theme from parent window (Django Unfold)
|
|
44
|
-
* - Receives auth tokens from parent window and automatically sets them in API client
|
|
45
|
-
* - Sends auth status to parent window
|
|
46
|
-
* - Provides useApp hook data via context
|
|
47
|
-
*
|
|
48
|
-
* Usage:
|
|
49
|
-
* ```tsx
|
|
50
|
-
* // Wrap your app in _app.tsx - no config needed!
|
|
51
|
-
* <AdminLayout>
|
|
52
|
-
* <AppLayout config={appLayoutConfig}>
|
|
53
|
-
* <Component {...pageProps} />
|
|
54
|
-
* </AppLayout>
|
|
55
|
-
* </AdminLayout>
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* Or with custom auth handler:
|
|
59
|
-
* ```tsx
|
|
60
|
-
* <AdminLayout config={{
|
|
61
|
-
* onAuthTokenReceived: (authToken, refreshToken) => {
|
|
62
|
-
* // Custom logic before/after setting tokens
|
|
63
|
-
* console.log('Tokens received');
|
|
64
|
-
* }
|
|
65
|
-
* }}>
|
|
66
|
-
* <AppLayout config={appLayoutConfig}>
|
|
67
|
-
* <Component {...pageProps} />
|
|
68
|
-
* </AppLayout>
|
|
69
|
-
* </AdminLayout>
|
|
70
|
-
* ```
|
|
71
|
-
*
|
|
72
|
-
* Use useCfgApp hook directly:
|
|
73
|
-
* ```tsx
|
|
74
|
-
* import { useCfgApp } from '@djangocfg/layouts/AdminLayout';
|
|
75
|
-
*
|
|
76
|
-
* function MyComponent() {
|
|
77
|
-
* const { isEmbedded, disableLayout, parentTheme } = useCfgApp();
|
|
78
|
-
* // ...
|
|
79
|
-
* }
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
export function AdminLayout({
|
|
83
|
-
children,
|
|
84
|
-
config,
|
|
85
|
-
enableParentSync = true
|
|
86
|
-
}: AdminLayoutProps) {
|
|
87
|
-
return (
|
|
88
|
-
<AdminLayoutClientWithProvider config={config} enableParentSync={enableParentSync}>{children}</AdminLayoutClientWithProvider>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Wrapper component that provides CfgAppProvider with auth callback
|
|
93
|
-
function AdminLayoutClientWithProvider({
|
|
94
|
-
children,
|
|
95
|
-
config,
|
|
96
|
-
enableParentSync
|
|
97
|
-
}: AdminLayoutProps) {
|
|
98
|
-
const { loadCurrentProfile } = useAuth();
|
|
99
|
-
|
|
100
|
-
// Use refs to prevent re-renders and maintain state across renders
|
|
101
|
-
const profileLoadedRef = React.useRef(false);
|
|
102
|
-
const tokensReceivedRef = React.useRef(false);
|
|
103
|
-
const loadCurrentProfileRef = React.useRef(loadCurrentProfile);
|
|
104
|
-
|
|
105
|
-
// Update ref when loadCurrentProfile changes (but don't trigger re-render)
|
|
106
|
-
React.useEffect(() => {
|
|
107
|
-
loadCurrentProfileRef.current = loadCurrentProfile;
|
|
108
|
-
}, [loadCurrentProfile]);
|
|
109
|
-
|
|
110
|
-
// Create a STABLE callback that NEVER changes (no dependencies)
|
|
111
|
-
// Use refs to access latest values without recreating the callback
|
|
112
|
-
const handleAuthToken = React.useCallback(async (authToken: string, refreshToken?: string) => {
|
|
113
|
-
consola.info('[AdminLayout] handleAuthToken called', {
|
|
114
|
-
tokensReceived: tokensReceivedRef.current,
|
|
115
|
-
profileLoaded: profileLoadedRef.current
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Prevent duplicate token processing
|
|
119
|
-
if (tokensReceivedRef.current) {
|
|
120
|
-
consola.warn('[AdminLayout] Tokens already received and processed, ignoring duplicate');
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Mark tokens as received IMMEDIATELY to prevent race conditions
|
|
125
|
-
tokensReceivedRef.current = true;
|
|
126
|
-
|
|
127
|
-
consola.start('[AdminLayout] First time receiving tokens, processing...');
|
|
128
|
-
consola.debug('[AdminLayout] Tokens:', {
|
|
129
|
-
authToken: authToken.substring(0, 20) + '...',
|
|
130
|
-
refreshToken: refreshToken ? refreshToken.substring(0, 20) + '...' : 'null'
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Always set tokens in API client
|
|
134
|
-
api.setToken(authToken, refreshToken);
|
|
135
|
-
consola.success('[AdminLayout] Tokens set in API client');
|
|
136
|
-
|
|
137
|
-
// Load user profile after setting tokens - ONLY ONCE per session
|
|
138
|
-
if (!profileLoadedRef.current) {
|
|
139
|
-
consola.start('[AdminLayout] Loading user profile (first time)...');
|
|
140
|
-
try {
|
|
141
|
-
await loadCurrentProfileRef.current('AdminLayout.onAuthTokenReceived');
|
|
142
|
-
profileLoadedRef.current = true;
|
|
143
|
-
consola.success('[AdminLayout] User profile loaded successfully');
|
|
144
|
-
} catch (error) {
|
|
145
|
-
consola.error('[AdminLayout] Failed to load profile:', error);
|
|
146
|
-
// Reset flags on error so user can retry
|
|
147
|
-
tokensReceivedRef.current = false;
|
|
148
|
-
profileLoadedRef.current = false;
|
|
149
|
-
}
|
|
150
|
-
} else {
|
|
151
|
-
consola.info('[AdminLayout] Profile already loaded, skipping duplicate call');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Call custom handler if provided
|
|
155
|
-
if (config?.onAuthTokenReceived) {
|
|
156
|
-
consola.info('[AdminLayout] Calling custom onAuthTokenReceived handler');
|
|
157
|
-
config.onAuthTokenReceived(authToken, refreshToken);
|
|
158
|
-
}
|
|
159
|
-
}, []); // ← EMPTY DEPS - callback NEVER changes
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<CfgAppProvider options={{
|
|
163
|
-
onAuthTokenReceived: handleAuthToken
|
|
164
|
-
}}>
|
|
165
|
-
<AdminLayoutClient config={config} enableParentSync={enableParentSync}>{children}</AdminLayoutClient>
|
|
166
|
-
</CfgAppProvider>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function AdminLayoutClient({
|
|
171
|
-
children,
|
|
172
|
-
config,
|
|
173
|
-
enableParentSync = true
|
|
174
|
-
}: AdminLayoutProps) {
|
|
175
|
-
const { isAdminUser, user, isLoading, isAuthenticated } = useAuth();
|
|
176
|
-
// console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading, 'isAuthenticated:', isAuthenticated);
|
|
177
|
-
|
|
178
|
-
// Get embedding state from context (provided by CfgAppProvider wrapper)
|
|
179
|
-
const { isEmbedded } = useCfgAppContext();
|
|
180
|
-
|
|
181
|
-
// console.log('[AdminLayout] isEmbedded:', isEmbedded);
|
|
182
|
-
|
|
183
|
-
// Show loading while auth is initializing (waiting for tokens from parent or profile loading)
|
|
184
|
-
// OR if user object is not loaded yet (null/undefined)
|
|
185
|
-
// OR if authentication status is not yet determined
|
|
186
|
-
if (isLoading || !user || !isAuthenticated) {
|
|
187
|
-
// console.log('[AdminLayout] Showing loading state - isLoading:', isLoading, 'user:', user, 'isAuthenticated:', isAuthenticated);
|
|
188
|
-
return <PagePreloader text="Authenticating..." size="lg" />;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Only show "Access Denied" when we are CERTAIN user doesn't have permissions
|
|
192
|
-
// This prevents showing the message during auth/loading states
|
|
193
|
-
if (!isAdminUser) {
|
|
194
|
-
// Only show this when user is fully authenticated but lacks permissions
|
|
195
|
-
// console.log('[AdminLayout] Authenticated user lacks admin permissions');
|
|
196
|
-
return (
|
|
197
|
-
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
198
|
-
<div className="text-center space-y-4 p-8">
|
|
199
|
-
<div className="flex justify-center">
|
|
200
|
-
<ShieldAlert className="w-16 h-16 text-destructive" />
|
|
201
|
-
</div>
|
|
202
|
-
<h1 className="text-2xl font-bold text-foreground">Access Denied</h1>
|
|
203
|
-
<p className="text-muted-foreground max-w-md">
|
|
204
|
-
You don't have permission to access this admin panel.
|
|
205
|
-
<br />
|
|
206
|
-
Staff or superuser privileges are required.
|
|
207
|
-
</p>
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<>
|
|
215
|
-
{/* ParentSync handles theme sync and auth status reporting */}
|
|
216
|
-
{enableParentSync && <ParentSync />}
|
|
217
|
-
|
|
218
|
-
{/* Apply padding only when NOT in iframe */}
|
|
219
|
-
<div>
|
|
220
|
-
{children}
|
|
221
|
-
</div>
|
|
222
|
-
</>
|
|
223
|
-
);
|
|
224
|
-
}
|