@djangocfg/layouts 1.2.24 → 1.2.25

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "1.2.24",
3
+ "version": "1.2.25",
4
4
  "description": "Layout system and components for Unrealon applications",
5
5
  "author": {
6
6
  "name": "DjangoCFG",
@@ -20,6 +20,16 @@
20
20
  "import": "./src/auth/index.ts",
21
21
  "default": "./src/auth/index.ts"
22
22
  },
23
+ "./auth/context": {
24
+ "types": "./src/auth/context/index.ts",
25
+ "import": "./src/auth/context/index.ts",
26
+ "default": "./src/auth/context/index.ts"
27
+ },
28
+ "./auth/hooks": {
29
+ "types": "./src/auth/hooks/index.ts",
30
+ "import": "./src/auth/hooks/index.ts",
31
+ "default": "./src/auth/hooks/index.ts"
32
+ },
23
33
  "./layouts": {
24
34
  "types": "./src/layouts/index.ts",
25
35
  "import": "./src/layouts/index.ts",
@@ -53,9 +63,9 @@
53
63
  "check": "tsc --noEmit"
54
64
  },
55
65
  "peerDependencies": {
56
- "@djangocfg/api": "^1.2.24",
57
- "@djangocfg/og-image": "^1.2.24",
58
- "@djangocfg/ui": "^1.2.24",
66
+ "@djangocfg/api": "^1.2.25",
67
+ "@djangocfg/og-image": "^1.2.25",
68
+ "@djangocfg/ui": "^1.2.25",
59
69
  "@hookform/resolvers": "^5.2.0",
60
70
  "consola": "^3.4.2",
61
71
  "lucide-react": "^0.468.0",
@@ -76,7 +86,7 @@
76
86
  "vidstack": "0.6.15"
77
87
  },
78
88
  "devDependencies": {
79
- "@djangocfg/typescript-config": "^1.2.24",
89
+ "@djangocfg/typescript-config": "^1.2.25",
80
90
  "@types/node": "^24.7.2",
81
91
  "@types/react": "19.2.2",
82
92
  "@types/react-dom": "19.2.1",
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Accounts Context
3
+ *
4
+ * Manages user authentication and profile operations using generated SWR hooks
5
+ *
6
+ * Features:
7
+ * - OTP-based authentication
8
+ * - User profile management
9
+ * - Avatar upload
10
+ * - Profile updates
11
+ * - localStorage cache with TTL (1 hour)
12
+ */
13
+
14
+ "use client";
15
+
16
+ import { createContext, useContext, ReactNode, useState, useCallback } from 'react';
17
+ import { getCachedProfile, setCachedProfile, clearProfileCache } from '../hooks/useProfileCache';
18
+ import {
19
+ api,
20
+ usePartialUpdateAccountsProfilePartialUpdate,
21
+ useUpdateAccountsProfileUpdateUpdate,
22
+ useCreateAccountsProfileAvatarCreate,
23
+ useCreateAccountsOtpRequestCreate,
24
+ useCreateAccountsOtpVerifyCreate,
25
+ useCreateAccountsTokenRefreshCreate,
26
+ getAccountsProfileRetrieve,
27
+ PatchedUserProfileUpdateRequestSchema,
28
+ type User,
29
+ type UserProfileUpdateRequest,
30
+ type PatchedUserProfileUpdateRequest,
31
+ type OTPRequestRequest,
32
+ type OTPVerifyRequest,
33
+ type OTPRequestResponse,
34
+ type OTPVerifyResponse,
35
+ type TokenRefresh,
36
+ type API,
37
+ } from '@djangocfg/api';
38
+
39
+ // Re-export schemas for external use
40
+ export { PatchedUserProfileUpdateRequestSchema };
41
+ export type { PatchedUserProfileUpdateRequest };
42
+
43
+ // ─────────────────────────────────────────────────────────────────────────
44
+ // Context Type
45
+ // ─────────────────────────────────────────────────────────────────────────
46
+
47
+ export interface AccountsContextValue {
48
+ // Current user profile
49
+ profile?: User;
50
+ isLoadingProfile: boolean;
51
+ profileError: Error | null;
52
+
53
+ // Profile operations
54
+ updateProfile: (data: UserProfileUpdateRequest) => Promise<User>;
55
+ partialUpdateProfile: (data: PatchedUserProfileUpdateRequest) => Promise<User>;
56
+ uploadAvatar: (formData: FormData) => Promise<User>;
57
+ refreshProfile: () => Promise<User | undefined>;
58
+
59
+ // Authentication
60
+ requestOTP: (data: OTPRequestRequest) => Promise<OTPRequestResponse>;
61
+ verifyOTP: (data: OTPVerifyRequest) => Promise<OTPVerifyResponse>;
62
+ refreshToken: (refresh: string) => Promise<TokenRefresh>;
63
+ logout: () => void;
64
+ }
65
+
66
+ // ─────────────────────────────────────────────────────────────────────────
67
+ // Context
68
+ // ─────────────────────────────────────────────────────────────────────────
69
+
70
+ const AccountsContext = createContext<AccountsContextValue | undefined>(undefined);
71
+
72
+ // ─────────────────────────────────────────────────────────────────────────
73
+ // Provider Component
74
+ // ─────────────────────────────────────────────────────────────────────────
75
+
76
+ interface AccountsProviderProps {
77
+ children: ReactNode;
78
+ }
79
+
80
+ export function AccountsProvider({ children }: AccountsProviderProps) {
81
+ // State management with localStorage cache
82
+ const [profile, setProfile] = useState<User | undefined>(() => {
83
+ // Initialize from cache on mount
84
+ const cached = getCachedProfile();
85
+ return cached || undefined;
86
+ });
87
+ const [isLoadingProfile, setIsLoadingProfile] = useState(false);
88
+ const [profileError, setProfileError] = useState<Error | null>(null);
89
+
90
+ // Mutation hooks
91
+ const updateMutation = useUpdateAccountsProfileUpdateUpdate();
92
+ const partialUpdateMutation = usePartialUpdateAccountsProfilePartialUpdate();
93
+ const avatarMutation = useCreateAccountsProfileAvatarCreate();
94
+ const otpRequestMutation = useCreateAccountsOtpRequestCreate();
95
+ const otpVerifyMutation = useCreateAccountsOtpVerifyCreate();
96
+ const tokenRefreshMutation = useCreateAccountsTokenRefreshCreate();
97
+
98
+ // Refresh profile - fetch and cache
99
+ const refreshProfile = useCallback(async (): Promise<User | undefined> => {
100
+ setIsLoadingProfile(true);
101
+ setProfileError(null);
102
+ try {
103
+ const result = await getAccountsProfileRetrieve(api as unknown as API);
104
+ setProfile(result);
105
+ // Save to cache with 1 hour TTL
106
+ setCachedProfile(result);
107
+ return result;
108
+ } catch (error) {
109
+ const err = error instanceof Error ? error : new Error('Failed to fetch profile');
110
+ setProfileError(err);
111
+ throw err;
112
+ } finally {
113
+ setIsLoadingProfile(false);
114
+ }
115
+ }, []);
116
+
117
+ // Update profile (full)
118
+ const updateProfile = async (data: UserProfileUpdateRequest): Promise<User> => {
119
+ const result = await updateMutation(data, api as unknown as API);
120
+ await refreshProfile();
121
+ return result as User;
122
+ };
123
+
124
+ // Partial update profile
125
+ const partialUpdateProfile = async (data: PatchedUserProfileUpdateRequest): Promise<User> => {
126
+ const result = await partialUpdateMutation(data, api as unknown as API);
127
+ await refreshProfile();
128
+ return result as User;
129
+ };
130
+
131
+ // Upload avatar
132
+ const uploadAvatar = async (formData: FormData): Promise<User> => {
133
+ const result = await avatarMutation(formData, api as unknown as API);
134
+ await refreshProfile();
135
+ return result as User;
136
+ };
137
+
138
+ // Request OTP
139
+ const requestOTP = async (data: OTPRequestRequest): Promise<OTPRequestResponse> => {
140
+ const result = await otpRequestMutation(data, api as unknown as API);
141
+ return result as OTPRequestResponse;
142
+ };
143
+
144
+ // Verify OTP
145
+ const verifyOTP = async (data: OTPVerifyRequest): Promise<OTPVerifyResponse> => {
146
+ const result = await otpVerifyMutation(data, api as unknown as API);
147
+
148
+ // Automatically save tokens after successful verification
149
+ if (result.access && result.refresh) {
150
+ api.setToken(result.access, result.refresh);
151
+ // Refresh profile to load user data with new token
152
+ await refreshProfile();
153
+ }
154
+
155
+ return result as OTPVerifyResponse;
156
+ };
157
+
158
+ // Refresh token
159
+ const refreshToken = async (refresh: string): Promise<TokenRefresh> => {
160
+ const result = await tokenRefreshMutation({ refresh }, api as unknown as API);
161
+
162
+ // Automatically save new access token
163
+ if (result.access) {
164
+ api.setToken(result.access, refresh);
165
+ }
166
+
167
+ return result as TokenRefresh;
168
+ };
169
+
170
+ // Logout - clear tokens, profile state, and cache
171
+ const logout = useCallback(() => {
172
+ api.clearTokens();
173
+ setProfile(undefined);
174
+ setProfileError(null);
175
+ clearProfileCache();
176
+ }, []);
177
+
178
+ const value: AccountsContextValue = {
179
+ profile,
180
+ isLoadingProfile,
181
+ profileError,
182
+ updateProfile,
183
+ partialUpdateProfile,
184
+ uploadAvatar,
185
+ refreshProfile,
186
+ requestOTP,
187
+ verifyOTP,
188
+ refreshToken,
189
+ logout,
190
+ };
191
+
192
+ return (
193
+ <AccountsContext.Provider value={value}>
194
+ {children}
195
+ </AccountsContext.Provider>
196
+ );
197
+ }
198
+
199
+ // ─────────────────────────────────────────────────────────────────────────
200
+ // Hook
201
+ // ─────────────────────────────────────────────────────────────────────────
202
+
203
+ export function useAccountsContext(): AccountsContextValue {
204
+ const context = useContext(AccountsContext);
205
+ if (!context) {
206
+ throw new Error('useAccountsContext must be used within AccountsProvider');
207
+ }
208
+ return context;
209
+ }
@@ -4,8 +4,9 @@ import React, {
4
4
  } from 'react';
5
5
 
6
6
  import { api, Enums } from '@djangocfg/api';
7
- import { useAccountsContext, AccountsProvider } from '@djangocfg/api/cfg/contexts';
7
+ import { useAccountsContext, AccountsProvider } from './AccountsContext';
8
8
  import { useLocalStorage } from '@djangocfg/ui/hooks';
9
+ import { getCachedProfile, clearProfileCache } from '../hooks/useProfileCache';
9
10
 
10
11
  import { authLogger } from '../../utils/logger';
11
12
  import type { AuthConfig, AuthContextType, AuthProviderProps, UserProfile } from './types';
@@ -74,6 +75,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
74
75
  const clearAuthState = useCallback((caller: string) => {
75
76
  authLogger.info('clearAuthState >> caller', caller);
76
77
  api.clearTokens();
78
+ clearProfileCache(); // Clear profile cache from localStorage
77
79
  // Note: user is now managed by AccountsContext, will auto-update
78
80
  setInitialized(true);
79
81
  setIsLoading(false);
@@ -148,9 +150,18 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
148
150
  const hasTokens = hasValidTokens();
149
151
  authLogger.info('Has tokens:', hasTokens);
150
152
 
151
- // If profile is already loaded (from SWR cache), we're initialized
152
- if (user) {
153
- authLogger.info('Profile already loaded from cache, skipping initialization');
153
+ // Check if profile is already loaded from cache (AccountsContext initialization)
154
+ if (userRef.current) {
155
+ authLogger.info('Profile already loaded from AccountsContext cache, skipping API request');
156
+ setInitialized(true);
157
+ setIsLoading(false);
158
+ return;
159
+ }
160
+
161
+ // Check if cache exists in localStorage (before userRef updates)
162
+ const cachedProfile = getCachedProfile();
163
+ if (cachedProfile) {
164
+ authLogger.info('Profile found in localStorage cache, skipping API request');
154
165
  setInitialized(true);
155
166
  setIsLoading(false);
156
167
  return;
@@ -159,6 +170,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
159
170
  if (hasTokens) {
160
171
  setIsLoading(true);
161
172
  try {
173
+ authLogger.info('No cached profile found, loading from API...');
162
174
  await loadCurrentProfile();
163
175
  } catch (error) {
164
176
  authLogger.error('Failed to load profile during initialization:', error);
@@ -173,7 +185,8 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
173
185
  };
174
186
 
175
187
  initializeAuth();
176
- }, [initialized, user, loadCurrentProfile, clearAuthState]);
188
+ // eslint-disable-next-line react-hooks/exhaustive-deps
189
+ }, [initialized]);
177
190
 
178
191
  // Redirect logic - only for unauthenticated users on protected pages
179
192
  useEffect(() => {
@@ -1,2 +1,4 @@
1
1
  export { AuthProvider, useAuth } from './AuthContext';
2
- export type { AuthConfig, AuthContextType, AuthProviderProps, UserProfile } from './types';
2
+ export type { AuthConfig, AuthContextType, AuthProviderProps, UserProfile } from './types';
3
+ export { AccountsProvider, useAccountsContext, PatchedUserProfileUpdateRequestSchema } from './AccountsContext';
4
+ export type { AccountsContextValue, PatchedUserProfileUpdateRequest } from './AccountsContext';
@@ -3,4 +3,12 @@ export { useAuthGuard } from './useAuthGuard';
3
3
  export { useSessionStorage } from './useSessionStorage';
4
4
  export { useLocalStorage } from './useLocalStorage';
5
5
  export { useAuthForm } from './useAuthForm';
6
- export { useAutoAuth } from './useAutoAuth';
6
+ export { useAutoAuth } from './useAutoAuth';
7
+ export {
8
+ getCachedProfile,
9
+ setCachedProfile,
10
+ clearProfileCache,
11
+ hasValidCache,
12
+ getCacheMetadata,
13
+ type ProfileCacheOptions
14
+ } from './useProfileCache';
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Profile Cache Hook
3
+ *
4
+ * Provides localStorage-based caching for user profile with:
5
+ * - TTL (Time To Live) - default 1 hour
6
+ * - Version control for migrations
7
+ * - Automatic invalidation on expiry
8
+ */
9
+
10
+ import type { User } from '@djangocfg/api';
11
+ import { profileLogger } from '../../utils/logger';
12
+
13
+ // Cache configuration
14
+ const CACHE_KEY = 'user_profile_cache';
15
+ const CACHE_VERSION = 1;
16
+ const DEFAULT_TTL = 3600000; // 1 hour in milliseconds
17
+
18
+ export interface ProfileCacheOptions {
19
+ /** Time to live in milliseconds (default: 1 hour) */
20
+ ttl?: number;
21
+ }
22
+
23
+ interface CachedProfile {
24
+ version: number;
25
+ data: User;
26
+ timestamp: number;
27
+ ttl: number;
28
+ }
29
+
30
+ /**
31
+ * Get cached profile from localStorage
32
+ * @returns User profile if valid cache exists, null otherwise
33
+ */
34
+ export function getCachedProfile(): User | null {
35
+ try {
36
+ if (typeof window === 'undefined') return null;
37
+
38
+ const cached = localStorage.getItem(CACHE_KEY);
39
+ if (!cached) return null;
40
+
41
+ const cachedData: CachedProfile = JSON.parse(cached);
42
+
43
+ // Version check
44
+ if (cachedData.version !== CACHE_VERSION) {
45
+ profileLogger.warn('Cache version mismatch, clearing cache');
46
+ clearProfileCache();
47
+ return null;
48
+ }
49
+
50
+ // TTL check
51
+ const now = Date.now();
52
+ const age = now - cachedData.timestamp;
53
+ if (age > cachedData.ttl) {
54
+ profileLogger.info('Cache expired, clearing');
55
+ clearProfileCache();
56
+ return null;
57
+ }
58
+
59
+ profileLogger.debug('Cache hit, age:', Math.round(age / 1000), 'seconds');
60
+ return cachedData.data;
61
+ } catch (error) {
62
+ profileLogger.error('Error reading cache:', error);
63
+ clearProfileCache();
64
+ return null;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Save profile to localStorage cache
70
+ * @param profile - User profile to cache
71
+ * @param options - Cache options (TTL)
72
+ */
73
+ export function setCachedProfile(profile: User, options?: ProfileCacheOptions): void {
74
+ try {
75
+ if (typeof window === 'undefined') return;
76
+
77
+ const cachedData: CachedProfile = {
78
+ version: CACHE_VERSION,
79
+ data: profile,
80
+ timestamp: Date.now(),
81
+ ttl: options?.ttl || DEFAULT_TTL,
82
+ };
83
+
84
+ localStorage.setItem(CACHE_KEY, JSON.stringify(cachedData));
85
+ profileLogger.debug('Profile cached, TTL:', cachedData.ttl / 1000, 'seconds');
86
+ } catch (error) {
87
+ profileLogger.error('Error writing cache:', error);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Clear profile cache from localStorage
93
+ */
94
+ export function clearProfileCache(): void {
95
+ try {
96
+ if (typeof window === 'undefined') return;
97
+ localStorage.removeItem(CACHE_KEY);
98
+ profileLogger.debug('Cache cleared');
99
+ } catch (error) {
100
+ profileLogger.error('Error clearing cache:', error);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Check if cached profile is valid (exists and not expired)
106
+ */
107
+ export function hasValidCache(): boolean {
108
+ return getCachedProfile() !== null;
109
+ }
110
+
111
+ /**
112
+ * Get cache metadata (age, TTL, etc.)
113
+ */
114
+ export function getCacheMetadata(): {
115
+ exists: boolean;
116
+ age?: number;
117
+ ttl?: number;
118
+ expiresIn?: number;
119
+ } | null {
120
+ try {
121
+ if (typeof window === 'undefined') return null;
122
+
123
+ const cached = localStorage.getItem(CACHE_KEY);
124
+ if (!cached) return { exists: false };
125
+
126
+ const cachedData: CachedProfile = JSON.parse(cached);
127
+ const now = Date.now();
128
+ const age = now - cachedData.timestamp;
129
+ const expiresIn = cachedData.ttl - age;
130
+
131
+ return {
132
+ exists: true,
133
+ age,
134
+ ttl: cachedData.ttl,
135
+ expiresIn: Math.max(0, expiresIn),
136
+ };
137
+ } catch (error) {
138
+ profileLogger.error('Error reading metadata:', error);
139
+ return null;
140
+ }
141
+ }
@@ -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-04T07:41:20.959Z
19
+ * Last updated: 2025-11-05T19:45:52.509Z
20
20
  */
21
21
  const PACKAGE_VERSIONS: PackageInfo[] = [
22
22
  {
23
23
  "name": "@djangocfg/ui",
24
- "version": "1.2.24"
24
+ "version": "1.2.25"
25
25
  },
26
26
  {
27
27
  "name": "@djangocfg/api",
28
- "version": "1.2.24"
28
+ "version": "1.2.25"
29
29
  },
30
30
  {
31
31
  "name": "@djangocfg/layouts",
32
- "version": "1.2.24"
32
+ "version": "1.2.25"
33
33
  },
34
34
  {
35
35
  "name": "@djangocfg/markdown",
36
- "version": "1.2.24"
36
+ "version": "1.2.25"
37
37
  },
38
38
  {
39
39
  "name": "@djangocfg/og-image",
40
- "version": "1.2.24"
40
+ "version": "1.2.25"
41
41
  },
42
42
  {
43
43
  "name": "@djangocfg/eslint-config",
44
- "version": "1.2.24"
44
+ "version": "1.2.25"
45
45
  },
46
46
  {
47
47
  "name": "@djangocfg/typescript-config",
48
- "version": "1.2.24"
48
+ "version": "1.2.25"
49
49
  }
50
50
  ];
51
51
 
@@ -82,15 +82,42 @@ export function AdminLayout({
82
82
  config,
83
83
  enableParentSync = true
84
84
  }: AdminLayoutProps) {
85
+ // Only run on client side
85
86
  const [isMounted, setIsMounted] = React.useState(false);
86
- const { user, isLoading, isAuthenticated, loadCurrentProfile } = useAuth();
87
- // console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading, 'isAuthenticated:', isAuthenticated);
88
87
 
89
88
  // Track mount state to prevent hydration mismatch
90
89
  React.useEffect(() => {
91
90
  setIsMounted(true);
92
91
  }, []);
93
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>
103
+ );
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
+ }
112
+
113
+ function AdminLayoutClient({
114
+ children,
115
+ config,
116
+ enableParentSync = true
117
+ }: AdminLayoutProps) {
118
+ const { user, isLoading, isAuthenticated, loadCurrentProfile } = useAuth();
119
+ // console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading, 'isAuthenticated:', isAuthenticated);
120
+
94
121
  // useCfgApp hook is called here to initialize iframe communication
95
122
  // Automatically sets tokens in API client when received from parent
96
123
  const { isEmbedded } = useCfgApp({
@@ -129,11 +156,6 @@ export function AdminLayout({
129
156
  </div>
130
157
  );
131
158
 
132
- // During SSR and initial render, show loading to prevent hydration mismatch
133
- if (!isMounted) {
134
- return <LoadingState />;
135
- }
136
-
137
159
  // Show loading while auth is initializing (waiting for tokens from parent or profile loading)
138
160
  // OR if user object is not loaded yet (null/undefined)
139
161
  // OR if authentication status is not yet determined
@@ -9,7 +9,7 @@ import {
9
9
  CardHeader,
10
10
  CardTitle,
11
11
  } from '@djangocfg/ui/components';
12
- import { AccountsProvider } from '@djangocfg/api/cfg/contexts';
12
+ import { AccountsProvider } from '@djangocfg/layouts/auth/context';
13
13
  import { useAuth } from '../../auth';
14
14
 
15
15
  import { AvatarSection, ProfileForm } from './components';
@@ -5,7 +5,7 @@ import React, { useState } from 'react';
5
5
  import { toast } from 'sonner';
6
6
 
7
7
  import { Avatar, AvatarFallback, Button } from '@djangocfg/ui/components';
8
- import { useAccountsContext } from '@djangocfg/api/cfg/contexts';
8
+ import { useAccountsContext } from '@djangocfg/layouts/auth/context';
9
9
  import { useAuth } from '../../../auth';
10
10
 
11
11
  export const AvatarSection = () => {
@@ -22,7 +22,7 @@ import {
22
22
  useAccountsContext,
23
23
  PatchedUserProfileUpdateRequestSchema,
24
24
  type PatchedUserProfileUpdateRequest
25
- } from '@djangocfg/api/cfg/contexts';
25
+ } from '@djangocfg/layouts/auth/context';
26
26
  import { useAuth } from '../../../auth';
27
27
 
28
28
  export const ProfileForm = () => {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * UI Guide App
3
+ *
4
+ * Complete UI Guide application with UILayout
5
+ * Uses config-driven approach with context for navigation
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React from 'react';
11
+ import UIGuideView from './UIGuideView';
12
+
13
+ export function UIGuideApp() {
14
+ // UIGuideView now includes UIGuideLanding as 'overview' category
15
+ // and uses ShowcaseProvider context for navigation
16
+ // All component data comes from centralized config
17
+ return <UIGuideView />;
18
+ }
@@ -1,18 +1,33 @@
1
1
  /**
2
- * UI Guide App
2
+ * UI Guide App - Dynamic Import Wrapper
3
3
  *
4
- * Complete UI Guide application with UILayout
5
- * Uses config-driven approach with context for navigation
4
+ * Lazy loads the heavy UILayout library (~1.5MB with Mermaid, PrettyCode, etc.)
5
+ * Only loads when UI documentation page is accessed
6
6
  */
7
7
 
8
8
  'use client';
9
9
 
10
+ import dynamic from 'next/dynamic';
10
11
  import React from 'react';
11
- import UIGuideView from './UIGuideView';
12
+
13
+ // Dynamic import with loading state
14
+ const UIGuideAppClient = dynamic(() => import('./UIGuideApp.client').then(mod => ({ default: mod.UIGuideApp })), {
15
+ ssr: false,
16
+ loading: () => (
17
+ <div className="min-h-screen bg-background flex items-center justify-center">
18
+ <div className="text-center space-y-4">
19
+ <div className="flex justify-center">
20
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
21
+ </div>
22
+ <div className="space-y-2">
23
+ <h2 className="text-xl font-semibold text-foreground">Loading UI Guide</h2>
24
+ <p className="text-sm text-muted-foreground">Preparing component showcase...</p>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ ),
29
+ });
12
30
 
13
31
  export function UIGuideApp() {
14
- // UIGuideView now includes UIGuideLanding as 'overview' category
15
- // and uses ShowcaseProvider context for navigation
16
- // All component data comes from centralized config
17
- return <UIGuideView />;
32
+ return <UIGuideAppClient />;
18
33
  }