@djangocfg/layouts 1.2.32 → 1.2.34
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/AuthContext.tsx +15 -1
- package/src/index.ts +4 -1
- package/src/layouts/AppLayout/AppLayout.tsx +9 -2
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/layouts/AdminLayout/AdminLayout.tsx +39 -13
- package/src/layouts/AppLayout/layouts/AdminLayout/hooks/useApp.ts +37 -8
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +12 -2
- 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/validation/README.md +593 -0
- package/src/validation/REFACTORING.md +162 -0
- package/src/validation/ValidationErrorButtons.tsx +80 -0
- package/src/validation/ValidationErrorContext.tsx +333 -0
- package/src/validation/ValidationErrorToast.tsx +181 -0
- package/src/validation/curl-generator.ts +118 -0
- package/src/validation/index.ts +36 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationErrorToast Component
|
|
3
|
+
*
|
|
4
|
+
* Custom toast for validation errors with copy button
|
|
5
|
+
* Formats Zod validation errors and provides one-click copy functionality
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { ToastAction } from '@djangocfg/ui';
|
|
12
|
+
import type { ZodError } from 'zod';
|
|
13
|
+
import { ValidationErrorButtons } from './ValidationErrorButtons';
|
|
14
|
+
|
|
15
|
+
export interface ValidationErrorDetail {
|
|
16
|
+
operation: string;
|
|
17
|
+
path: string;
|
|
18
|
+
method: string;
|
|
19
|
+
error: ZodError;
|
|
20
|
+
response: any;
|
|
21
|
+
timestamp: Date;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ValidationErrorToastConfig {
|
|
25
|
+
/** Show operation name in title */
|
|
26
|
+
showOperation?: boolean;
|
|
27
|
+
/** Show API path in description */
|
|
28
|
+
showPath?: boolean;
|
|
29
|
+
/** Show error count in description */
|
|
30
|
+
showErrorCount?: boolean;
|
|
31
|
+
/** Maximum number of issues to show in description */
|
|
32
|
+
maxIssuesInDescription?: number;
|
|
33
|
+
/** Custom title prefix */
|
|
34
|
+
titlePrefix?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const defaultConfig: ValidationErrorToastConfig = {
|
|
38
|
+
showOperation: true,
|
|
39
|
+
showPath: true,
|
|
40
|
+
showErrorCount: true,
|
|
41
|
+
maxIssuesInDescription: 3,
|
|
42
|
+
titlePrefix: '❌ Validation Error',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Format Zod error issues for display in toast description
|
|
47
|
+
*/
|
|
48
|
+
export function formatZodIssuesForToast(
|
|
49
|
+
error: ZodError,
|
|
50
|
+
maxIssues: number = 3
|
|
51
|
+
): string {
|
|
52
|
+
const issues = error.issues.slice(0, maxIssues);
|
|
53
|
+
const formatted = issues.map((issue) => {
|
|
54
|
+
const path = issue.path.join('.') || 'root';
|
|
55
|
+
return `${path}: ${issue.message}`;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (error.issues.length > maxIssues) {
|
|
59
|
+
formatted.push(`... and ${error.issues.length - maxIssues} more`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return formatted.join(', ');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format full error details for copying to clipboard
|
|
67
|
+
* Includes all error information in structured JSON format
|
|
68
|
+
*/
|
|
69
|
+
export function formatErrorForClipboard(detail: ValidationErrorDetail): string {
|
|
70
|
+
const errorData = {
|
|
71
|
+
timestamp: detail.timestamp.toISOString(),
|
|
72
|
+
operation: detail.operation,
|
|
73
|
+
endpoint: {
|
|
74
|
+
method: detail.method,
|
|
75
|
+
path: detail.path,
|
|
76
|
+
},
|
|
77
|
+
validation_errors: detail.error.issues.map((issue) => ({
|
|
78
|
+
path: issue.path.join('.') || 'root',
|
|
79
|
+
message: issue.message,
|
|
80
|
+
code: issue.code,
|
|
81
|
+
...(('expected' in issue) && { expected: issue.expected }),
|
|
82
|
+
...(('received' in issue) && { received: issue.received }),
|
|
83
|
+
...(('minimum' in issue) && { minimum: issue.minimum }),
|
|
84
|
+
...(('maximum' in issue) && { maximum: issue.maximum }),
|
|
85
|
+
})),
|
|
86
|
+
response: detail.response,
|
|
87
|
+
total_errors: detail.error.issues.length,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return JSON.stringify(errorData, null, 2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build toast title from validation error
|
|
95
|
+
*/
|
|
96
|
+
export function buildToastTitle(
|
|
97
|
+
detail: ValidationErrorDetail,
|
|
98
|
+
config: ValidationErrorToastConfig = defaultConfig
|
|
99
|
+
): string {
|
|
100
|
+
const titleParts: string[] = [config.titlePrefix || '❌ Validation Error'];
|
|
101
|
+
|
|
102
|
+
if (config.showOperation) {
|
|
103
|
+
titleParts.push(`in ${detail.operation}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return titleParts.join(' ');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Build toast description from validation error
|
|
111
|
+
*/
|
|
112
|
+
export function buildToastDescription(
|
|
113
|
+
detail: ValidationErrorDetail,
|
|
114
|
+
config: ValidationErrorToastConfig = defaultConfig
|
|
115
|
+
): string {
|
|
116
|
+
const descriptionParts: string[] = [];
|
|
117
|
+
|
|
118
|
+
// Add HTTP method and path
|
|
119
|
+
if (config.showPath) {
|
|
120
|
+
descriptionParts.push(`${detail.method} ${detail.path}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add error count
|
|
124
|
+
if (config.showErrorCount) {
|
|
125
|
+
const count = detail.error.issues.length;
|
|
126
|
+
const plural = count === 1 ? 'error' : 'errors';
|
|
127
|
+
descriptionParts.push(`${count} ${plural}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Add formatted error messages
|
|
131
|
+
descriptionParts.push(
|
|
132
|
+
formatZodIssuesForToast(
|
|
133
|
+
detail.error,
|
|
134
|
+
config.maxIssuesInDescription
|
|
135
|
+
)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return descriptionParts.join(' • ');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Create complete toast options for validation error
|
|
143
|
+
*
|
|
144
|
+
* Usage:
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const { toast } = useToast();
|
|
147
|
+
*
|
|
148
|
+
* const toastOptions = createValidationErrorToast(errorDetail, {
|
|
149
|
+
* onCopySuccess: () => toast({ title: '✅ Copied!' }),
|
|
150
|
+
* onCopyError: () => toast({ title: '❌ Copy failed', variant: 'destructive' }),
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* toast(toastOptions);
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function createValidationErrorToast(
|
|
157
|
+
detail: ValidationErrorDetail,
|
|
158
|
+
options?: {
|
|
159
|
+
config?: Partial<ValidationErrorToastConfig>;
|
|
160
|
+
duration?: number;
|
|
161
|
+
onCopySuccess?: () => void;
|
|
162
|
+
onCopyError?: (error: Error) => void;
|
|
163
|
+
}
|
|
164
|
+
) {
|
|
165
|
+
const config: ValidationErrorToastConfig = {
|
|
166
|
+
...defaultConfig,
|
|
167
|
+
...options?.config,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
title: buildToastTitle(detail, config),
|
|
172
|
+
description: (
|
|
173
|
+
<div className="flex flex-col gap-2">
|
|
174
|
+
<div>{buildToastDescription(detail, config)}</div>
|
|
175
|
+
<ValidationErrorButtons detail={detail} />
|
|
176
|
+
</div>
|
|
177
|
+
),
|
|
178
|
+
variant: 'destructive' as const,
|
|
179
|
+
duration: options?.duration,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cURL Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates cURL commands from API request details with authentication token
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface CurlOptions {
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
token?: string;
|
|
11
|
+
body?: any;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
queryParams?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get authentication token from localStorage
|
|
19
|
+
*/
|
|
20
|
+
export function getAuthToken(): string | null {
|
|
21
|
+
if (typeof window === 'undefined') return null;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Priority order: access_token > token > auth_token
|
|
25
|
+
const token = localStorage.getItem('access_token') ||
|
|
26
|
+
localStorage.getItem('token') ||
|
|
27
|
+
localStorage.getItem('auth_token');
|
|
28
|
+
|
|
29
|
+
return token;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Failed to get auth token:', error);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format headers for cURL command
|
|
38
|
+
*/
|
|
39
|
+
function formatHeaders(headers: Record<string, string>): string[] {
|
|
40
|
+
return Object.entries(headers).map(
|
|
41
|
+
([key, value]) => `-H '${key}: ${value}'`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Escape single quotes in string for shell
|
|
47
|
+
*/
|
|
48
|
+
function escapeShell(str: string): string {
|
|
49
|
+
return str.replace(/'/g, "'\\''");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate cURL command from request details
|
|
54
|
+
*/
|
|
55
|
+
export function generateCurl(options: CurlOptions): string {
|
|
56
|
+
const {
|
|
57
|
+
method,
|
|
58
|
+
path,
|
|
59
|
+
token = getAuthToken() || undefined, // Auto-fetch if not provided
|
|
60
|
+
body,
|
|
61
|
+
headers = {},
|
|
62
|
+
baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000',
|
|
63
|
+
} = options;
|
|
64
|
+
|
|
65
|
+
const curlParts: string[] = ['curl'];
|
|
66
|
+
|
|
67
|
+
// Build URL
|
|
68
|
+
const url = `${baseUrl}${path}`;
|
|
69
|
+
curlParts.push(`'${url}'`);
|
|
70
|
+
|
|
71
|
+
// Add method if not GET
|
|
72
|
+
if (method.toUpperCase() !== 'GET') {
|
|
73
|
+
curlParts.push(`-X ${method.toUpperCase()}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default headers
|
|
77
|
+
const allHeaders: Record<string, string> = {
|
|
78
|
+
'Accept': '*/*',
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
...headers,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Add Authorization header if token exists
|
|
84
|
+
if (token) {
|
|
85
|
+
allHeaders['Authorization'] = `Bearer ${token}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add all headers
|
|
89
|
+
const headerStrings = formatHeaders(allHeaders);
|
|
90
|
+
curlParts.push(...headerStrings);
|
|
91
|
+
|
|
92
|
+
// Add body for non-GET requests
|
|
93
|
+
if (body && method.toUpperCase() !== 'GET') {
|
|
94
|
+
const bodyJson = typeof body === 'string'
|
|
95
|
+
? body
|
|
96
|
+
: JSON.stringify(body, null, 2);
|
|
97
|
+
curlParts.push(`-d '${escapeShell(bodyJson)}'`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Join with line continuation
|
|
101
|
+
return curlParts.join(' \\\n ');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generate cURL from validation error details
|
|
106
|
+
* Auto-fetches token from localStorage
|
|
107
|
+
*/
|
|
108
|
+
export function generateCurlFromError(detail: {
|
|
109
|
+
method: string;
|
|
110
|
+
path: string;
|
|
111
|
+
response?: any;
|
|
112
|
+
}): string {
|
|
113
|
+
return generateCurl({
|
|
114
|
+
method: detail.method,
|
|
115
|
+
path: detail.path,
|
|
116
|
+
// token is auto-fetched in generateCurl
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Error Tracking
|
|
3
|
+
*
|
|
4
|
+
* Automatic capture and management of Zod validation errors
|
|
5
|
+
* from API client using browser CustomEvent API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
ValidationErrorProvider,
|
|
10
|
+
useValidationErrors,
|
|
11
|
+
type ValidationErrorDetail,
|
|
12
|
+
type StoredValidationError,
|
|
13
|
+
type ValidationErrorConfig,
|
|
14
|
+
type ValidationErrorContextValue,
|
|
15
|
+
} from './ValidationErrorContext';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
createValidationErrorToast,
|
|
19
|
+
buildToastTitle,
|
|
20
|
+
buildToastDescription,
|
|
21
|
+
formatZodIssuesForToast,
|
|
22
|
+
formatErrorForClipboard,
|
|
23
|
+
type ValidationErrorToastConfig,
|
|
24
|
+
} from './ValidationErrorToast';
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
ValidationErrorButtons,
|
|
28
|
+
type ValidationErrorButtonsProps,
|
|
29
|
+
} from './ValidationErrorButtons';
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
generateCurl,
|
|
33
|
+
generateCurlFromError,
|
|
34
|
+
getAuthToken,
|
|
35
|
+
type CurlOptions,
|
|
36
|
+
} from './curl-generator';
|