@djangocfg/layouts 1.2.31 → 1.2.33
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/auth/context/AccountsContext.tsx +40 -9
- package/src/auth/context/AuthContext.tsx +43 -13
- package/src/auth/context/types.ts +1 -1
- package/src/auth/hooks/useAuthForm.ts +2 -1
- package/src/auth/hooks/useAutoAuth.ts +2 -1
- package/src/auth/hooks/useLocalStorage.ts +19 -18
- package/src/auth/hooks/useProfileCache.ts +4 -1
- package/src/auth/hooks/useSessionStorage.ts +19 -18
- package/src/index.ts +4 -1
- package/src/layouts/AppLayout/AppLayout.tsx +9 -2
- package/src/layouts/AppLayout/components/ErrorBoundary.tsx +3 -2
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +56 -19
- package/src/layouts/AppLayout/layouts/AdminLayout/components/ParentSync.tsx +29 -18
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +65 -19
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +12 -2
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +2 -1
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -1
- package/src/layouts/UILayout/components/layout/Header/Header.tsx +11 -4
- package/src/layouts/UILayout/components/layout/Header/HeaderDesktop.tsx +14 -7
- package/src/layouts/UILayout/components/layout/Header/TestValidationButton.tsx +265 -0
- package/src/layouts/UILayout/components/layout/Header/index.ts +2 -0
- package/src/utils/logger.ts +3 -1
- package/src/validation/README.md +507 -0
- package/src/validation/ValidationErrorContext.tsx +333 -0
- package/src/validation/ValidationErrorToast.tsx +251 -0
- package/src/validation/index.ts +25 -0
|
@@ -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-10T16:46:04.571Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.33"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.33"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.33"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.33"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.33"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.33"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.33"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
|
@@ -118,28 +118,65 @@ function AdminLayoutClient({
|
|
|
118
118
|
const { isAdminUser, user, isLoading, isAuthenticated, loadCurrentProfile } = useAuth();
|
|
119
119
|
// console.log('[AdminLayout] Rendering with user:', user, 'isLoading:', isLoading, 'isAuthenticated:', isAuthenticated);
|
|
120
120
|
|
|
121
|
+
// Use refs to prevent re-renders and maintain state across renders
|
|
122
|
+
const profileLoadedRef = React.useRef(false);
|
|
123
|
+
const tokensReceivedRef = React.useRef(false);
|
|
124
|
+
const loadCurrentProfileRef = React.useRef(loadCurrentProfile);
|
|
125
|
+
|
|
126
|
+
// Update ref when loadCurrentProfile changes (but don't trigger re-render)
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
loadCurrentProfileRef.current = loadCurrentProfile;
|
|
129
|
+
}, [loadCurrentProfile]);
|
|
130
|
+
|
|
131
|
+
// Create a STABLE callback that NEVER changes (no dependencies)
|
|
132
|
+
// Use refs to access latest values without recreating the callback
|
|
133
|
+
const handleAuthToken = React.useCallback(async (authToken: string, refreshToken?: string) => {
|
|
134
|
+
console.log('[AdminLayout] handleAuthToken called, tokensReceived:', tokensReceivedRef.current, 'profileLoaded:', profileLoadedRef.current);
|
|
135
|
+
|
|
136
|
+
// Prevent duplicate token processing
|
|
137
|
+
if (tokensReceivedRef.current) {
|
|
138
|
+
console.log('[AdminLayout] Tokens already received and processed, ignoring duplicate');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Mark tokens as received IMMEDIATELY to prevent race conditions
|
|
143
|
+
tokensReceivedRef.current = true;
|
|
144
|
+
|
|
145
|
+
console.log('[AdminLayout] First time receiving tokens, processing...');
|
|
146
|
+
console.log('[AdminLayout] authToken:', authToken.substring(0, 20) + '...', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
|
|
147
|
+
|
|
148
|
+
// Always set tokens in API client
|
|
149
|
+
api.setToken(authToken, refreshToken);
|
|
150
|
+
console.log('[AdminLayout] Tokens set in API client');
|
|
151
|
+
|
|
152
|
+
// Load user profile after setting tokens - ONLY ONCE per session
|
|
153
|
+
if (!profileLoadedRef.current) {
|
|
154
|
+
console.log('[AdminLayout] Loading user profile (first time)...');
|
|
155
|
+
try {
|
|
156
|
+
await loadCurrentProfileRef.current('AdminLayout.onAuthTokenReceived');
|
|
157
|
+
profileLoadedRef.current = true;
|
|
158
|
+
console.log('[AdminLayout] User profile loaded successfully');
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error('[AdminLayout] Failed to load profile:', error);
|
|
161
|
+
// Reset flags on error so user can retry
|
|
162
|
+
tokensReceivedRef.current = false;
|
|
163
|
+
profileLoadedRef.current = false;
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
console.log('[AdminLayout] Profile already loaded, skipping duplicate call');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Call custom handler if provided
|
|
170
|
+
if (config?.onAuthTokenReceived) {
|
|
171
|
+
console.log('[AdminLayout] Calling custom onAuthTokenReceived handler');
|
|
172
|
+
config.onAuthTokenReceived(authToken, refreshToken);
|
|
173
|
+
}
|
|
174
|
+
}, []); // ← EMPTY DEPS - callback NEVER changes
|
|
175
|
+
|
|
121
176
|
// useCfgApp hook is called here to initialize iframe communication
|
|
122
177
|
// Automatically sets tokens in API client when received from parent
|
|
123
178
|
const { isEmbedded } = useCfgApp({
|
|
124
|
-
onAuthTokenReceived:
|
|
125
|
-
// console.log('[AdminLayout] onAuthTokenReceived called');
|
|
126
|
-
// console.log('[AdminLayout] authToken:', authToken.substring(0, 20) + '...', 'refreshToken:', refreshToken ? refreshToken.substring(0, 20) + '...' : 'null');
|
|
127
|
-
|
|
128
|
-
// Always set tokens in API client
|
|
129
|
-
api.setToken(authToken, refreshToken);
|
|
130
|
-
// console.log('[AdminLayout] Tokens set in API client');
|
|
131
|
-
|
|
132
|
-
// Load user profile after setting tokens
|
|
133
|
-
// console.log('[AdminLayout] Loading user profile...');
|
|
134
|
-
await loadCurrentProfile();
|
|
135
|
-
// console.log('[AdminLayout] User profile loaded');
|
|
136
|
-
|
|
137
|
-
// Call custom handler if provided
|
|
138
|
-
if (config?.onAuthTokenReceived) {
|
|
139
|
-
// console.log('[AdminLayout] Calling custom onAuthTokenReceived handler');
|
|
140
|
-
config.onAuthTokenReceived(authToken, refreshToken);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
179
|
+
onAuthTokenReceived: handleAuthToken // Now truly stable - never recreated
|
|
143
180
|
});
|
|
144
181
|
|
|
145
182
|
// console.log('[AdminLayout] isEmbedded:', isEmbedded);
|
|
@@ -13,6 +13,7 @@ import { useRouter } from 'next/router';
|
|
|
13
13
|
import { useAuth } from '../../../../../auth';
|
|
14
14
|
import { useThemeContext } from '@djangocfg/ui';
|
|
15
15
|
import { useCfgApp } from '../hooks/useApp';
|
|
16
|
+
import { authLogger } from '../../../../../utils/logger';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* ParentSync Component
|
|
@@ -65,7 +66,7 @@ function ParentSyncClient() {
|
|
|
65
66
|
}
|
|
66
67
|
}, [isEmbedded, parentTheme, setTheme]);
|
|
67
68
|
|
|
68
|
-
// 2. Send auth status from iframe → parent
|
|
69
|
+
// 2. Send auth status from iframe → parent (debounced to prevent spam)
|
|
69
70
|
useEffect(() => {
|
|
70
71
|
// Only send if embedded and mounted
|
|
71
72
|
if (!isEmbedded || !isMounted) {
|
|
@@ -76,24 +77,34 @@ function ParentSyncClient() {
|
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
80
|
+
// Use primitive values to avoid unnecessary re-renders from object reference changes
|
|
81
|
+
const isAuthenticated = auth.isAuthenticated;
|
|
82
|
+
const isLoading = auth.isLoading;
|
|
83
|
+
const hasUser = !!auth.user;
|
|
84
84
|
|
|
85
|
-
// console.log('[ParentSync] 📤 Sending auth status to parent:',
|
|
85
|
+
// console.log('[ParentSync] 📤 Sending auth status to parent:', { isAuthenticated, isLoading, hasUser });
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
// Debounce the postMessage to prevent excessive calls
|
|
88
|
+
const timeoutId = setTimeout(() => {
|
|
89
|
+
try {
|
|
90
|
+
window.parent.postMessage({
|
|
91
|
+
type: 'iframe-auth-status',
|
|
92
|
+
data: { isAuthenticated, isLoading, hasUser }
|
|
93
|
+
}, '*');
|
|
94
|
+
// authLogger.debug('Auth status sent successfully');
|
|
95
|
+
} catch (e) {
|
|
96
|
+
authLogger.error('Failed to send auth status:', e);
|
|
97
|
+
}
|
|
98
|
+
}, 100); // 100ms debounce
|
|
99
|
+
|
|
100
|
+
return () => clearTimeout(timeoutId);
|
|
101
|
+
}, [
|
|
102
|
+
auth.isAuthenticated,
|
|
103
|
+
auth.isLoading,
|
|
104
|
+
!!auth.user, // Convert to boolean to prevent object reference changes
|
|
105
|
+
isEmbedded,
|
|
106
|
+
isMounted
|
|
107
|
+
]);
|
|
97
108
|
|
|
98
109
|
// 3. iframe-resize removed - was causing log spam
|
|
99
110
|
|
|
@@ -111,7 +122,7 @@ function ParentSyncClient() {
|
|
|
111
122
|
data: { path: url }
|
|
112
123
|
}, '*');
|
|
113
124
|
} catch (e) {
|
|
114
|
-
|
|
125
|
+
authLogger.error('Failed to send navigation event:', e);
|
|
115
126
|
}
|
|
116
127
|
};
|
|
117
128
|
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
'use client';
|
|
6
6
|
|
|
7
|
+
import React from 'react';
|
|
7
8
|
import { useState, useEffect } from 'react';
|
|
8
9
|
import { useRouter } from 'next/router';
|
|
10
|
+
import { authLogger } from '../../../../../utils/logger';
|
|
9
11
|
|
|
10
12
|
export interface UseCfgAppReturn {
|
|
11
13
|
/**
|
|
@@ -93,6 +95,14 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
93
95
|
const [parentTheme, setParentTheme] = useState<'light' | 'dark' | null>(null);
|
|
94
96
|
const [parentThemeMode, setParentThemeMode] = useState<string | null>(null);
|
|
95
97
|
|
|
98
|
+
// Store callback in ref OUTSIDE useEffect so it's always available
|
|
99
|
+
const callbackRef = React.useRef(options?.onAuthTokenReceived);
|
|
100
|
+
|
|
101
|
+
// Update callback ref when options change (but don't re-register listener)
|
|
102
|
+
React.useEffect(() => {
|
|
103
|
+
callbackRef.current = options?.onAuthTokenReceived;
|
|
104
|
+
}, [options?.onAuthTokenReceived]);
|
|
105
|
+
|
|
96
106
|
useEffect(() => {
|
|
97
107
|
setIsMounted(true);
|
|
98
108
|
|
|
@@ -109,6 +119,11 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
109
119
|
setReferrer(document.referrer);
|
|
110
120
|
}
|
|
111
121
|
|
|
122
|
+
// Debounce timeout for parent-auth messages
|
|
123
|
+
let authTokenTimeout: NodeJS.Timeout | null = null;
|
|
124
|
+
// Track if we've already processed auth tokens
|
|
125
|
+
let authTokenProcessed = false;
|
|
126
|
+
|
|
112
127
|
// Listen for messages from parent window
|
|
113
128
|
const handleMessage = (event: MessageEvent) => {
|
|
114
129
|
// console.log('[useCfgApp] RAW message event:', {
|
|
@@ -123,20 +138,47 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
123
138
|
|
|
124
139
|
switch (type) {
|
|
125
140
|
case 'parent-auth':
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
} else {
|
|
138
|
-
console.warn('[useCfgApp] parent-auth message received but authToken or callback missing:', { hasToken: !!data?.authToken, hasCallback: !!options?.onAuthTokenReceived });
|
|
141
|
+
console.log('[useCfgApp] parent-auth message received, authTokenProcessed:', authTokenProcessed);
|
|
142
|
+
|
|
143
|
+
// Prevent processing if already handled
|
|
144
|
+
if (authTokenProcessed) {
|
|
145
|
+
console.log('[useCfgApp] Auth tokens already processed, ignoring duplicate message');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Cancel previous timeout to debounce rapid auth messages
|
|
150
|
+
if (authTokenTimeout) {
|
|
151
|
+
clearTimeout(authTokenTimeout);
|
|
139
152
|
}
|
|
153
|
+
|
|
154
|
+
// Debounce auth token processing to prevent rapid calls (300ms)
|
|
155
|
+
authTokenTimeout = setTimeout(() => {
|
|
156
|
+
// Double-check still not processed (race condition protection)
|
|
157
|
+
if (authTokenProcessed) {
|
|
158
|
+
console.log('[useCfgApp] Auth tokens already processed during debounce, skipping');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Receive authentication tokens from parent
|
|
163
|
+
if (data?.authToken && callbackRef.current) {
|
|
164
|
+
console.log('[useCfgApp] Auth tokens found, calling onAuthTokenReceived callback');
|
|
165
|
+
console.log('[useCfgApp] authToken:', data.authToken.substring(0, 20) + '...', 'refreshToken:', data.refreshToken ? data.refreshToken.substring(0, 20) + '...' : 'null');
|
|
166
|
+
|
|
167
|
+
// Mark as processed BEFORE calling callback
|
|
168
|
+
authTokenProcessed = true;
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
callbackRef.current(data.authToken, data.refreshToken);
|
|
172
|
+
console.log('[useCfgApp] onAuthTokenReceived callback completed successfully');
|
|
173
|
+
} catch (e) {
|
|
174
|
+
authLogger.error('Failed to process auth tokens:', e);
|
|
175
|
+
// Reset on error to allow retry
|
|
176
|
+
authTokenProcessed = false;
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
authLogger.warn('parent-auth message received but authToken or callback missing:', { hasToken: !!data?.authToken, hasCallback: !!callbackRef.current });
|
|
180
|
+
}
|
|
181
|
+
}, 300); // 300ms debounce
|
|
140
182
|
break;
|
|
141
183
|
|
|
142
184
|
case 'parent-theme':
|
|
@@ -148,9 +190,9 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
148
190
|
if (data.themeMode) {
|
|
149
191
|
setParentThemeMode(data.themeMode);
|
|
150
192
|
}
|
|
151
|
-
//
|
|
193
|
+
// authLogger.debug('Theme set successfully:', data.theme);
|
|
152
194
|
} catch (e) {
|
|
153
|
-
|
|
195
|
+
authLogger.error('Failed to process theme:', e);
|
|
154
196
|
}
|
|
155
197
|
}
|
|
156
198
|
break;
|
|
@@ -174,9 +216,9 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
174
216
|
referrer: document.referrer
|
|
175
217
|
}
|
|
176
218
|
}, '*');
|
|
177
|
-
//
|
|
219
|
+
// authLogger.debug('iframe-ready message sent');
|
|
178
220
|
} catch (e) {
|
|
179
|
-
|
|
221
|
+
authLogger.error('Failed to notify parent about ready state:', e);
|
|
180
222
|
}
|
|
181
223
|
} else {
|
|
182
224
|
// console.log('[useCfgApp] Not in iframe, skipping iframe-ready message');
|
|
@@ -184,8 +226,12 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
184
226
|
|
|
185
227
|
return () => {
|
|
186
228
|
window.removeEventListener('message', handleMessage);
|
|
229
|
+
// Clear timeout on cleanup
|
|
230
|
+
if (authTokenTimeout) {
|
|
231
|
+
clearTimeout(authTokenTimeout);
|
|
232
|
+
}
|
|
187
233
|
};
|
|
188
|
-
}, [
|
|
234
|
+
}, []); // ← EMPTY DEPS - register listener ONCE
|
|
189
235
|
|
|
190
236
|
// Notify parent about route changes
|
|
191
237
|
useEffect(() => {
|
|
@@ -200,7 +246,7 @@ export function useCfgApp(options?: UseCfgAppOptions): UseCfgAppReturn {
|
|
|
200
246
|
}
|
|
201
247
|
}, '*');
|
|
202
248
|
} catch (e) {
|
|
203
|
-
|
|
249
|
+
authLogger.error('Failed to notify parent about navigation:', e);
|
|
204
250
|
}
|
|
205
251
|
}, [router.asPath, router.pathname, isEmbedded, isMounted]);
|
|
206
252
|
|
|
@@ -9,11 +9,18 @@
|
|
|
9
9
|
import React, { ReactNode } from 'react';
|
|
10
10
|
import { ThemeProvider, Toaster } from '@djangocfg/ui';
|
|
11
11
|
import { AuthProvider } from '../../../auth';
|
|
12
|
+
import { ValidationErrorProvider } from '../../../validation';
|
|
12
13
|
import type { AppLayoutConfig } from '../types';
|
|
14
|
+
import type { ValidationErrorConfig } from '../../../validation';
|
|
13
15
|
|
|
14
16
|
export interface CoreProvidersProps {
|
|
15
17
|
children: ReactNode;
|
|
16
18
|
config: AppLayoutConfig;
|
|
19
|
+
/**
|
|
20
|
+
* Configuration for validation error tracking
|
|
21
|
+
* @default { enableToast: true, maxErrors: 50 }
|
|
22
|
+
*/
|
|
23
|
+
validationConfig?: Partial<ValidationErrorConfig>;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/**
|
|
@@ -22,9 +29,10 @@ export interface CoreProvidersProps {
|
|
|
22
29
|
* Provides:
|
|
23
30
|
* - ThemeProvider (dark/light mode)
|
|
24
31
|
* - AuthProvider (authentication)
|
|
32
|
+
* - ValidationErrorProvider (Zod validation error tracking)
|
|
25
33
|
* - Toaster (notifications)
|
|
26
34
|
*/
|
|
27
|
-
export function CoreProviders({ children, config }: CoreProvidersProps) {
|
|
35
|
+
export function CoreProviders({ children, config, validationConfig }: CoreProvidersProps) {
|
|
28
36
|
return (
|
|
29
37
|
<ThemeProvider>
|
|
30
38
|
<AuthProvider
|
|
@@ -37,7 +45,9 @@ export function CoreProviders({ children, config }: CoreProvidersProps) {
|
|
|
37
45
|
},
|
|
38
46
|
}}
|
|
39
47
|
>
|
|
40
|
-
{
|
|
48
|
+
<ValidationErrorProvider config={validationConfig}>
|
|
49
|
+
{children}
|
|
50
|
+
</ValidationErrorProvider>
|
|
41
51
|
</AuthProvider>
|
|
42
52
|
|
|
43
53
|
{/* Global toast notifications */}
|
|
@@ -36,6 +36,7 @@ import { z } from 'zod';
|
|
|
36
36
|
import { usePaymentsContext, useRootPaymentsContext } from '@djangocfg/api/cfg/contexts';
|
|
37
37
|
import { PAYMENT_EVENTS, closePaymentsDialog } from '../events';
|
|
38
38
|
import { openPaymentDetailsDialog } from '../events';
|
|
39
|
+
import { paymentsLogger } from '../../../utils/logger';
|
|
39
40
|
|
|
40
41
|
// Payment creation schema
|
|
41
42
|
const PaymentCreateSchema = z.object({
|
|
@@ -139,7 +140,7 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
139
140
|
openPaymentDetailsDialog(String(paymentId));
|
|
140
141
|
}
|
|
141
142
|
} catch (error) {
|
|
142
|
-
|
|
143
|
+
paymentsLogger.error('Failed to create payment:', error);
|
|
143
144
|
} finally {
|
|
144
145
|
setIsSubmitting(false);
|
|
145
146
|
}
|
|
@@ -7,6 +7,7 @@ import { toast } from 'sonner';
|
|
|
7
7
|
import { Avatar, AvatarFallback, Button } from '@djangocfg/ui/components';
|
|
8
8
|
import { useAccountsContext } from '@djangocfg/layouts/auth/context';
|
|
9
9
|
import { useAuth } from '../../../auth';
|
|
10
|
+
import { profileLogger } from '../../../utils/logger';
|
|
10
11
|
|
|
11
12
|
export const AvatarSection = () => {
|
|
12
13
|
const { user } = useAuth();
|
|
@@ -58,7 +59,7 @@ export const AvatarSection = () => {
|
|
|
58
59
|
setAvatarPreview(null);
|
|
59
60
|
} catch (error) {
|
|
60
61
|
toast.error('Failed to upload avatar');
|
|
61
|
-
|
|
62
|
+
profileLogger.error('Avatar upload error:', error);
|
|
62
63
|
} finally {
|
|
63
64
|
setIsUploading(false);
|
|
64
65
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { CopyAIButton } from './CopyAIButton';
|
|
10
|
+
import { TestValidationButton } from './TestValidationButton';
|
|
10
11
|
|
|
11
12
|
export interface HeaderProps {
|
|
12
13
|
/** Page title */
|
|
@@ -42,10 +43,16 @@ export function Header({
|
|
|
42
43
|
</div>
|
|
43
44
|
</div>
|
|
44
45
|
|
|
45
|
-
{/* Right:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
{/* Right: Developer Tools */}
|
|
47
|
+
<div className="flex items-center gap-2">
|
|
48
|
+
{/* Test Validation Error Button (dev only) */}
|
|
49
|
+
<TestValidationButton size="sm" />
|
|
50
|
+
|
|
51
|
+
{/* Copy for AI Button */}
|
|
52
|
+
{onCopyForAI && (
|
|
53
|
+
<CopyAIButton onCopyForAI={onCopyForAI} />
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
49
56
|
</div>
|
|
50
57
|
</div>
|
|
51
58
|
</header>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
import { CopyAIButton } from './CopyAIButton';
|
|
10
|
+
import { TestValidationButton } from './TestValidationButton';
|
|
10
11
|
|
|
11
12
|
interface HeaderDesktopProps {
|
|
12
13
|
/** Page title */
|
|
@@ -31,13 +32,19 @@ export function HeaderDesktop({
|
|
|
31
32
|
<h1 className="text-lg font-semibold">{title}</h1>
|
|
32
33
|
</div>
|
|
33
34
|
|
|
34
|
-
{/*
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
size="sm"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
{/* Developer Tools */}
|
|
36
|
+
<div className="flex items-center gap-2">
|
|
37
|
+
{/* Test Validation Error Button (dev only) */}
|
|
38
|
+
<TestValidationButton size="sm" />
|
|
39
|
+
|
|
40
|
+
{/* Copy for AI Button */}
|
|
41
|
+
<CopyAIButton
|
|
42
|
+
onCopyForAI={onCopyForAI}
|
|
43
|
+
size="sm"
|
|
44
|
+
showLabel={true}
|
|
45
|
+
className="gap-2"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
41
48
|
</div>
|
|
42
49
|
</div>
|
|
43
50
|
);
|