@djangocfg/layouts 1.2.33 → 1.2.35
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 +10 -11
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +45 -59
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.example.tsx +98 -0
- package/src/layouts/AppLayout/layouts/AdminLayout/components/PagePreloader.tsx +149 -0
- package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +2 -2
- package/src/layouts/AppLayout/layouts/AdminLayout/components/index.ts +2 -0
- package/src/layouts/AppLayout/layouts/AdminLayout/context/CfgAppContext.tsx +48 -0
- package/src/layouts/AppLayout/layouts/AdminLayout/context/index.ts +2 -0
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +20 -12
- package/src/layouts/AppLayout/layouts/AdminLayout/index.ts +4 -0
- package/src/layouts/AppLayout/layouts/AdminLayout/lottie/energizing.json +1 -0
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +24 -55
- package/src/layouts/UILayout/config/components/tools.config.tsx +31 -1
- package/src/validation/README.md +88 -2
- package/src/validation/REFACTORING.md +162 -0
- package/src/validation/ValidationErrorButtons.tsx +80 -0
- package/src/validation/ValidationErrorToast.tsx +7 -77
- package/src/validation/curl-generator.ts +118 -0
- package/src/validation/index.ts +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.35",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"check": "tsc --noEmit"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@djangocfg/api": "^1.2.
|
|
67
|
-
"@djangocfg/og-image": "^1.2.
|
|
68
|
-
"@djangocfg/ui": "^1.2.
|
|
66
|
+
"@djangocfg/api": "^1.2.35",
|
|
67
|
+
"@djangocfg/og-image": "^1.2.35",
|
|
68
|
+
"@djangocfg/ui": "^1.2.35",
|
|
69
69
|
"@hookform/resolvers": "^5.2.0",
|
|
70
70
|
"consola": "^3.4.2",
|
|
71
71
|
"lucide-react": "^0.468.0",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"vidstack": "0.6.15"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@djangocfg/typescript-config": "^1.2.
|
|
89
|
+
"@djangocfg/typescript-config": "^1.2.35",
|
|
90
90
|
"@types/node": "^24.7.2",
|
|
91
91
|
"@types/react": "19.2.2",
|
|
92
92
|
"@types/react-dom": "19.2.1",
|
|
@@ -26,18 +26,25 @@
|
|
|
26
26
|
|
|
27
27
|
import React, { ReactNode, useEffect, useState } from 'react';
|
|
28
28
|
import { useRouter } from 'next/router';
|
|
29
|
+
import dynamic from 'next/dynamic';
|
|
29
30
|
import { AppContextProvider } from './context';
|
|
30
31
|
import { CoreProviders } from './providers';
|
|
31
32
|
import { Seo, PageProgress, ErrorBoundary } from './components';
|
|
32
33
|
import { PublicLayout } from './layouts/PublicLayout';
|
|
33
34
|
import { PrivateLayout } from './layouts/PrivateLayout';
|
|
34
35
|
import { AuthLayout } from './layouts/AuthLayout';
|
|
35
|
-
import {
|
|
36
|
+
import { PagePreloader } from './layouts/AdminLayout/components';
|
|
36
37
|
import { determineLayoutMode, getRedirectUrl } from './utils';
|
|
37
38
|
import { useAuth } from '../../auth';
|
|
38
39
|
import type { AppLayoutConfig } from './types';
|
|
39
40
|
import type { ValidationErrorConfig } from '../../validation';
|
|
40
41
|
|
|
42
|
+
// Dynamic import for AdminLayout to prevent SSR hydration issues
|
|
43
|
+
const AdminLayout = dynamic(
|
|
44
|
+
() => import('./layouts/AdminLayout').then((mod) => ({ default: mod.AdminLayout })),
|
|
45
|
+
{ ssr: false }
|
|
46
|
+
);
|
|
47
|
+
|
|
41
48
|
export interface AppLayoutProps {
|
|
42
49
|
children: ReactNode;
|
|
43
50
|
config: AppLayoutConfig;
|
|
@@ -131,11 +138,7 @@ function LayoutRouter({
|
|
|
131
138
|
|
|
132
139
|
// Standalone mode: show loading during initialization
|
|
133
140
|
if (!isMounted || isLoading) {
|
|
134
|
-
return
|
|
135
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
136
|
-
<div className="text-muted-foreground">Loading...</div>
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
141
|
+
return <PagePreloader text="Loading admin..." />;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
// After mount: check authentication
|
|
@@ -163,11 +166,7 @@ function LayoutRouter({
|
|
|
163
166
|
// This prevents hydration mismatch when isAuthenticated differs between server/client
|
|
164
167
|
if (isPrivateRoute && !forceLayout) {
|
|
165
168
|
if (!isMounted || isLoading) {
|
|
166
|
-
return
|
|
167
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
168
|
-
<div className="text-muted-foreground">Loading...</div>
|
|
169
|
-
</div>
|
|
170
|
-
);
|
|
169
|
+
return <PagePreloader />;
|
|
171
170
|
}
|
|
172
171
|
|
|
173
172
|
// After mount: check authentication
|
|
@@ -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-11-
|
|
19
|
+
* Last updated: 2025-11-12T09:15:11.957Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.35"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.35"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.35"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.35"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.35"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.35"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.35"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
import React, { ReactNode } from 'react';
|
|
18
18
|
import { ShieldAlert } from 'lucide-react';
|
|
19
|
-
import { ParentSync } from './components';
|
|
20
|
-
import {
|
|
19
|
+
import { ParentSync, PagePreloader } from './components';
|
|
20
|
+
import { CfgAppProvider, useCfgAppContext } from './context';
|
|
21
21
|
import type { AdminLayoutConfig } from './types';
|
|
22
22
|
import { api } from '@djangocfg/api';
|
|
23
23
|
import { useAuth } from '../../../../auth';
|
|
24
|
+
import { consola } from 'consola';
|
|
24
25
|
|
|
25
26
|
export interface AdminLayoutProps {
|
|
26
27
|
children: ReactNode;
|
|
@@ -82,41 +83,18 @@ export function AdminLayout({
|
|
|
82
83
|
config,
|
|
83
84
|
enableParentSync = true
|
|
84
85
|
}: AdminLayoutProps) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Track mount state to prevent hydration mismatch
|
|
89
|
-
React.useEffect(() => {
|
|
90
|
-
setIsMounted(true);
|
|
91
|
-
}, []);
|
|
92
|
-
|
|
93
|
-
// Minimalist loading component
|
|
94
|
-
const LoadingState = () => (
|
|
95
|
-
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
96
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
97
|
-
<div className="w-2 h-2 bg-current rounded-full animate-pulse" style={{ animationDelay: '0ms' }} />
|
|
98
|
-
<div className="w-2 h-2 bg-current rounded-full animate-pulse" style={{ animationDelay: '150ms' }} />
|
|
99
|
-
<div className="w-2 h-2 bg-current rounded-full animate-pulse" style={{ animationDelay: '300ms' }} />
|
|
100
|
-
<span className="ml-2">Loading...</span>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
86
|
+
return (
|
|
87
|
+
<AdminLayoutClientWithProvider config={config} enableParentSync={enableParentSync}>{children}</AdminLayoutClientWithProvider>
|
|
103
88
|
);
|
|
104
|
-
|
|
105
|
-
// During SSR and initial render, show loading to prevent hydration mismatch
|
|
106
|
-
if (!isMounted) {
|
|
107
|
-
return <LoadingState />;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return <AdminLayoutClient config={config} enableParentSync={enableParentSync}>{children}</AdminLayoutClient>;
|
|
111
89
|
}
|
|
112
90
|
|
|
113
|
-
|
|
91
|
+
// Wrapper component that provides CfgAppProvider with auth callback
|
|
92
|
+
function AdminLayoutClientWithProvider({
|
|
114
93
|
children,
|
|
115
94
|
config,
|
|
116
|
-
enableParentSync
|
|
95
|
+
enableParentSync
|
|
117
96
|
}: AdminLayoutProps) {
|
|
118
|
-
const {
|
|
119
|
-
// console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading, 'isAuthenticated:', isAuthenticated);
|
|
97
|
+
const { loadCurrentProfile } = useAuth();
|
|
120
98
|
|
|
121
99
|
// Use refs to prevent re-renders and maintain state across renders
|
|
122
100
|
const profileLoadedRef = React.useRef(false);
|
|
@@ -131,74 +109,82 @@ function AdminLayoutClient({
|
|
|
131
109
|
// Create a STABLE callback that NEVER changes (no dependencies)
|
|
132
110
|
// Use refs to access latest values without recreating the callback
|
|
133
111
|
const handleAuthToken = React.useCallback(async (authToken: string, refreshToken?: string) => {
|
|
134
|
-
|
|
112
|
+
consola.info('[AdminLayout] handleAuthToken called', {
|
|
113
|
+
tokensReceived: tokensReceivedRef.current,
|
|
114
|
+
profileLoaded: profileLoadedRef.current
|
|
115
|
+
});
|
|
135
116
|
|
|
136
117
|
// Prevent duplicate token processing
|
|
137
118
|
if (tokensReceivedRef.current) {
|
|
138
|
-
|
|
119
|
+
consola.warn('[AdminLayout] Tokens already received and processed, ignoring duplicate');
|
|
139
120
|
return;
|
|
140
121
|
}
|
|
141
122
|
|
|
142
123
|
// Mark tokens as received IMMEDIATELY to prevent race conditions
|
|
143
124
|
tokensReceivedRef.current = true;
|
|
144
125
|
|
|
145
|
-
|
|
146
|
-
|
|
126
|
+
consola.start('[AdminLayout] First time receiving tokens, processing...');
|
|
127
|
+
consola.debug('[AdminLayout] Tokens:', {
|
|
128
|
+
authToken: authToken.substring(0, 20) + '...',
|
|
129
|
+
refreshToken: refreshToken ? refreshToken.substring(0, 20) + '...' : 'null'
|
|
130
|
+
});
|
|
147
131
|
|
|
148
132
|
// Always set tokens in API client
|
|
149
133
|
api.setToken(authToken, refreshToken);
|
|
150
|
-
|
|
134
|
+
consola.success('[AdminLayout] Tokens set in API client');
|
|
151
135
|
|
|
152
136
|
// Load user profile after setting tokens - ONLY ONCE per session
|
|
153
137
|
if (!profileLoadedRef.current) {
|
|
154
|
-
|
|
138
|
+
consola.start('[AdminLayout] Loading user profile (first time)...');
|
|
155
139
|
try {
|
|
156
140
|
await loadCurrentProfileRef.current('AdminLayout.onAuthTokenReceived');
|
|
157
141
|
profileLoadedRef.current = true;
|
|
158
|
-
|
|
142
|
+
consola.success('[AdminLayout] User profile loaded successfully');
|
|
159
143
|
} catch (error) {
|
|
160
|
-
|
|
144
|
+
consola.error('[AdminLayout] Failed to load profile:', error);
|
|
161
145
|
// Reset flags on error so user can retry
|
|
162
146
|
tokensReceivedRef.current = false;
|
|
163
147
|
profileLoadedRef.current = false;
|
|
164
148
|
}
|
|
165
149
|
} else {
|
|
166
|
-
|
|
150
|
+
consola.info('[AdminLayout] Profile already loaded, skipping duplicate call');
|
|
167
151
|
}
|
|
168
152
|
|
|
169
153
|
// Call custom handler if provided
|
|
170
154
|
if (config?.onAuthTokenReceived) {
|
|
171
|
-
|
|
155
|
+
consola.info('[AdminLayout] Calling custom onAuthTokenReceived handler');
|
|
172
156
|
config.onAuthTokenReceived(authToken, refreshToken);
|
|
173
157
|
}
|
|
174
158
|
}, []); // ← EMPTY DEPS - callback NEVER changes
|
|
175
159
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
160
|
+
return (
|
|
161
|
+
<CfgAppProvider options={{
|
|
162
|
+
onAuthTokenReceived: handleAuthToken
|
|
163
|
+
}}>
|
|
164
|
+
<AdminLayoutClient config={config} enableParentSync={enableParentSync}>{children}</AdminLayoutClient>
|
|
165
|
+
</CfgAppProvider>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function AdminLayoutClient({
|
|
170
|
+
children,
|
|
171
|
+
config,
|
|
172
|
+
enableParentSync = true
|
|
173
|
+
}: AdminLayoutProps) {
|
|
174
|
+
const { isAdminUser, user, isLoading, isAuthenticated } = useAuth();
|
|
175
|
+
// console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading, 'isAuthenticated:', isAuthenticated);
|
|
176
|
+
|
|
177
|
+
// Get embedding state from context (provided by CfgAppProvider wrapper)
|
|
178
|
+
const { isEmbedded } = useCfgAppContext();
|
|
181
179
|
|
|
182
180
|
// console.log('[AdminLayout] isEmbedded:', isEmbedded);
|
|
183
181
|
|
|
184
|
-
// Minimalist loading component
|
|
185
|
-
const LoadingState = () => (
|
|
186
|
-
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
187
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
188
|
-
<div className="w-2 h-2 bg-current rounded-full animate-pulse" style={{ animationDelay: '0ms' }} />
|
|
189
|
-
<div className="w-2 h-2 bg-current rounded-full animate-pulse" style={{ animationDelay: '150ms' }} />
|
|
190
|
-
<div className="w-2 h-2 bg-current rounded-full animate-pulse" style={{ animationDelay: '300ms' }} />
|
|
191
|
-
<span className="ml-2">Loading...</span>
|
|
192
|
-
</div>
|
|
193
|
-
</div>
|
|
194
|
-
);
|
|
195
|
-
|
|
196
182
|
// Show loading while auth is initializing (waiting for tokens from parent or profile loading)
|
|
197
183
|
// OR if user object is not loaded yet (null/undefined)
|
|
198
184
|
// OR if authentication status is not yet determined
|
|
199
185
|
if (isLoading || !user || !isAuthenticated) {
|
|
200
186
|
// console.log('[AdminLayout] Showing loading state - isLoading:', isLoading, 'user:', user, 'isAuthenticated:', isAuthenticated);
|
|
201
|
-
return <
|
|
187
|
+
return <PagePreloader text="Authenticating..." size="lg" />;
|
|
202
188
|
}
|
|
203
189
|
|
|
204
190
|
// Only show "Access Denied" when we are CERTAIN user doesn't have permissions
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PagePreloader - Usage Examples
|
|
3
|
+
*
|
|
4
|
+
* This file contains examples of how to use the PagePreloader component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PagePreloader, PagePreloaderDark } from './PagePreloader';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Example 1: Basic Usage
|
|
11
|
+
// ============================================================================
|
|
12
|
+
export function Example1_Basic() {
|
|
13
|
+
return <PagePreloader />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Example 2: Custom Text
|
|
18
|
+
// ============================================================================
|
|
19
|
+
export function Example2_CustomText() {
|
|
20
|
+
return <PagePreloader text="Loading your dashboard..." />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Example 3: Different Sizes
|
|
25
|
+
// ============================================================================
|
|
26
|
+
export function Example3_DifferentSizes() {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<PagePreloader size="sm" text="Small" />
|
|
30
|
+
<PagePreloader size="md" text="Medium" />
|
|
31
|
+
<PagePreloader size="lg" text="Large" />
|
|
32
|
+
<PagePreloader size="xl" text="Extra Large" />
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Example 4: Dark Variant
|
|
39
|
+
// ============================================================================
|
|
40
|
+
export function Example4_Dark() {
|
|
41
|
+
return <PagePreloaderDark text="Loading..." />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Example 5: No Text
|
|
46
|
+
// ============================================================================
|
|
47
|
+
export function Example5_NoText() {
|
|
48
|
+
return <PagePreloader showText={false} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Example 6: No Backdrop
|
|
53
|
+
// ============================================================================
|
|
54
|
+
export function Example6_NoBackdrop() {
|
|
55
|
+
return <PagePreloader backdrop={false} />;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Example 7: Custom Backdrop Opacity
|
|
60
|
+
// ============================================================================
|
|
61
|
+
export function Example7_CustomBackdropOpacity() {
|
|
62
|
+
return <PagePreloader backdropOpacity={50} text="50% opacity backdrop" />;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Example 8: Conditional Loading
|
|
67
|
+
// ============================================================================
|
|
68
|
+
export function Example8_ConditionalLoading({ isLoading }: { isLoading: boolean }) {
|
|
69
|
+
if (!isLoading) return null;
|
|
70
|
+
return <PagePreloader text="Loading data..." />;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// Example 9: With Custom Styling
|
|
75
|
+
// ============================================================================
|
|
76
|
+
export function Example9_CustomStyling() {
|
|
77
|
+
return (
|
|
78
|
+
<PagePreloader
|
|
79
|
+
className="bg-gradient-to-br from-blue-500 to-purple-600"
|
|
80
|
+
text="Loading..."
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Example 10: In AdminLayout
|
|
87
|
+
// ============================================================================
|
|
88
|
+
export function Example10_InAdminLayout() {
|
|
89
|
+
// Usage in AdminLayout component
|
|
90
|
+
const isLoading = true; // Replace with actual loading state
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
{isLoading && <PagePreloader text="Loading application..." />}
|
|
95
|
+
{/* Rest of your app */}
|
|
96
|
+
</>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PagePreloader Component
|
|
3
|
+
*
|
|
4
|
+
* Full-page loading indicator with Lottie animation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { LottiePlayer } from '@djangocfg/ui/tools';
|
|
11
|
+
import energizingAnimation from '../lottie/energizing.json';
|
|
12
|
+
|
|
13
|
+
export interface PagePreloaderProps {
|
|
14
|
+
/**
|
|
15
|
+
* Custom loading text
|
|
16
|
+
* @default 'Loading...'
|
|
17
|
+
*/
|
|
18
|
+
text?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Show loading text
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
showText?: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Animation size
|
|
28
|
+
* @default 'lg'
|
|
29
|
+
*/
|
|
30
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether to show backdrop
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
backdrop?: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Backdrop opacity (0-100)
|
|
40
|
+
* @default 80
|
|
41
|
+
*/
|
|
42
|
+
backdropOpacity?: number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Additional CSS classes
|
|
46
|
+
*/
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* PagePreloader - Full-page loading indicator with energizing animation
|
|
52
|
+
*
|
|
53
|
+
* Features:
|
|
54
|
+
* - Lottie animation with energizing effect
|
|
55
|
+
* - Customizable text and size
|
|
56
|
+
* - Optional backdrop
|
|
57
|
+
* - Smooth fade-in animation
|
|
58
|
+
*
|
|
59
|
+
* Usage:
|
|
60
|
+
* ```tsx
|
|
61
|
+
* // Basic usage
|
|
62
|
+
* <PagePreloader />
|
|
63
|
+
*
|
|
64
|
+
* // With custom text
|
|
65
|
+
* <PagePreloader text="Loading your data..." />
|
|
66
|
+
*
|
|
67
|
+
* // Different size
|
|
68
|
+
* <PagePreloader size="xl" />
|
|
69
|
+
*
|
|
70
|
+
* // Without backdrop
|
|
71
|
+
* <PagePreloader backdrop={false} />
|
|
72
|
+
*
|
|
73
|
+
* // Custom styling
|
|
74
|
+
* <PagePreloader className="bg-gradient-to-br from-blue-500 to-purple-600" />
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function PagePreloader({
|
|
78
|
+
text = 'Loading...',
|
|
79
|
+
showText = true,
|
|
80
|
+
size = 'lg',
|
|
81
|
+
backdrop = true,
|
|
82
|
+
backdropOpacity = 80,
|
|
83
|
+
className,
|
|
84
|
+
}: PagePreloaderProps) {
|
|
85
|
+
// Generate backdrop opacity classes based on opacity value with explicit light/dark variants
|
|
86
|
+
const getBackdropClasses = () => {
|
|
87
|
+
if (!backdrop) return 'bg-transparent';
|
|
88
|
+
|
|
89
|
+
// Use explicit light/dark classes for reliable theme support
|
|
90
|
+
if (backdropOpacity >= 90) {
|
|
91
|
+
return 'bg-white dark:bg-gray-950'; // Full opacity
|
|
92
|
+
} else if (backdropOpacity >= 70) {
|
|
93
|
+
return 'bg-white/90 dark:bg-gray-950/90'; // 90% opacity
|
|
94
|
+
} else if (backdropOpacity >= 50) {
|
|
95
|
+
return 'bg-white/80 dark:bg-gray-950/80'; // 80% opacity
|
|
96
|
+
} else {
|
|
97
|
+
return 'bg-white/60 dark:bg-gray-950/60'; // 60% opacity
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
className={`fixed inset-0 z-50 flex items-center justify-center ${getBackdropClasses()} ${className || ''}`}
|
|
104
|
+
>
|
|
105
|
+
<div className="flex flex-col items-center gap-6 animate-in fade-in duration-300">
|
|
106
|
+
{/* Lottie Animation */}
|
|
107
|
+
<div className="relative">
|
|
108
|
+
<LottiePlayer
|
|
109
|
+
src={energizingAnimation}
|
|
110
|
+
size={size}
|
|
111
|
+
autoplay
|
|
112
|
+
loop
|
|
113
|
+
speed={1}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Loading Text */}
|
|
118
|
+
{showText && (
|
|
119
|
+
<div className="flex flex-col items-center gap-2">
|
|
120
|
+
<p className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
|
121
|
+
{text}
|
|
122
|
+
</p>
|
|
123
|
+
<div className="flex gap-1">
|
|
124
|
+
<span className="h-2 w-2 animate-bounce rounded-full bg-blue-600 dark:bg-blue-400" style={{ animationDelay: '0ms' }} />
|
|
125
|
+
<span className="h-2 w-2 animate-bounce rounded-full bg-blue-600 dark:bg-blue-400" style={{ animationDelay: '150ms' }} />
|
|
126
|
+
<span className="h-2 w-2 animate-bounce rounded-full bg-blue-600 dark:bg-blue-400" style={{ animationDelay: '300ms' }} />
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* PagePreloaderDark - Dark variant of PagePreloader
|
|
137
|
+
*
|
|
138
|
+
* Same as PagePreloader but forces dark backdrop
|
|
139
|
+
* Note: Regular PagePreloader now uses semantic theme colors,
|
|
140
|
+
* so this is only needed for specific dark-only use cases
|
|
141
|
+
*/
|
|
142
|
+
export function PagePreloaderDark(props: Omit<PagePreloaderProps, 'className'>) {
|
|
143
|
+
return (
|
|
144
|
+
<PagePreloader
|
|
145
|
+
{...props}
|
|
146
|
+
className="bg-gray-950/95"
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -12,7 +12,7 @@ import { useEffect, useState } from 'react';
|
|
|
12
12
|
import { useRouter } from 'next/router';
|
|
13
13
|
import { useAuth } from '../../../../../auth';
|
|
14
14
|
import { useThemeContext } from '@djangocfg/ui';
|
|
15
|
-
import {
|
|
15
|
+
import { useCfgAppContext } from '../context';
|
|
16
16
|
import { authLogger } from '../../../../../utils/logger';
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -52,7 +52,7 @@ function ParentSyncClient() {
|
|
|
52
52
|
const router = useRouter();
|
|
53
53
|
const auth = useAuth();
|
|
54
54
|
const { setTheme } = useThemeContext();
|
|
55
|
-
const { isEmbedded, isMounted, parentTheme } =
|
|
55
|
+
const { isEmbedded, isMounted, parentTheme } = useCfgAppContext();
|
|
56
56
|
|
|
57
57
|
// 1. Sync theme from parent → iframe
|
|
58
58
|
useEffect(() => {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// CfgApp Context - Shared App State
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Provides useCfgApp state to child components without duplicate hook calls
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import React, { createContext, useContext, ReactNode } from 'react';
|
|
9
|
+
import { UseCfgAppReturn, useCfgApp, UseCfgAppOptions } from '../hooks/useApp';
|
|
10
|
+
|
|
11
|
+
const CfgAppContext = createContext<UseCfgAppReturn | null>(null);
|
|
12
|
+
|
|
13
|
+
export interface CfgAppProviderProps {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
options?: UseCfgAppOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Provider for CfgApp state
|
|
20
|
+
*
|
|
21
|
+
* Should be used once at the top level (e.g., AdminLayout)
|
|
22
|
+
* to avoid multiple useCfgApp() calls which cause duplicate
|
|
23
|
+
* message listeners and iframe-ready signals
|
|
24
|
+
*/
|
|
25
|
+
export function CfgAppProvider({ children, options }: CfgAppProviderProps) {
|
|
26
|
+
const cfgApp = useCfgApp(options);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<CfgAppContext.Provider value={cfgApp}>
|
|
30
|
+
{children}
|
|
31
|
+
</CfgAppContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Hook to consume CfgApp state
|
|
37
|
+
*
|
|
38
|
+
* @throws Error if used outside CfgAppProvider
|
|
39
|
+
*/
|
|
40
|
+
export function useCfgAppContext(): UseCfgAppReturn {
|
|
41
|
+
const context = useContext(CfgAppContext);
|
|
42
|
+
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error('useCfgAppContext must be used within CfgAppProvider');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return context;
|
|
48
|
+
}
|