@djangocfg/layouts 1.2.39 → 1.2.41
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 +16 -7
- package/src/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/layouts/AppLayout/providers/CoreProviders.tsx +28 -9
- package/src/validation/README.md +145 -547
- package/src/validation/components/ErrorButtons.tsx +100 -0
- package/src/validation/components/ErrorToast.tsx +159 -0
- package/src/validation/hooks.ts +10 -0
- package/src/validation/index.ts +31 -23
- package/src/validation/providers/ErrorTrackingProvider.tsx +265 -0
- package/src/validation/types.ts +278 -0
- package/src/validation/utils/formatters.ts +114 -0
- package/src/validation/REFACTORING.md +0 -162
- package/src/validation/ValidationErrorButtons.tsx +0 -80
- package/src/validation/ValidationErrorContext.tsx +0 -333
- package/src/validation/ValidationErrorToast.tsx +0 -181
- /package/src/validation/{curl-generator.ts → utils/curl-generator.ts} +0 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Tracking Types
|
|
3
|
+
*
|
|
4
|
+
* Common types for all error tracking functionality
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ZodError } from 'zod';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base error detail from CustomEvent
|
|
11
|
+
*/
|
|
12
|
+
export interface BaseErrorDetail {
|
|
13
|
+
/** Timestamp of the error */
|
|
14
|
+
timestamp: Date;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validation error detail (from zod-validation-error event)
|
|
19
|
+
*/
|
|
20
|
+
export interface ValidationErrorDetail extends BaseErrorDetail {
|
|
21
|
+
type: 'validation';
|
|
22
|
+
/** Operation/function name that failed validation */
|
|
23
|
+
operation: string;
|
|
24
|
+
/** API endpoint path */
|
|
25
|
+
path: string;
|
|
26
|
+
/** HTTP method */
|
|
27
|
+
method: string;
|
|
28
|
+
/** Zod validation error */
|
|
29
|
+
error: ZodError;
|
|
30
|
+
/** Raw response data that failed validation */
|
|
31
|
+
response: any;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* CORS error detail (from cors-error event)
|
|
36
|
+
*/
|
|
37
|
+
export interface CORSErrorDetail extends BaseErrorDetail {
|
|
38
|
+
type: 'cors';
|
|
39
|
+
/** API endpoint that was blocked */
|
|
40
|
+
url: string;
|
|
41
|
+
/** HTTP method */
|
|
42
|
+
method: string;
|
|
43
|
+
/** Error message */
|
|
44
|
+
error: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Network error detail (from network-error event)
|
|
49
|
+
*/
|
|
50
|
+
export interface NetworkErrorDetail extends BaseErrorDetail {
|
|
51
|
+
type: 'network';
|
|
52
|
+
/** API endpoint that failed */
|
|
53
|
+
url: string;
|
|
54
|
+
/** HTTP method */
|
|
55
|
+
method: string;
|
|
56
|
+
/** Error message */
|
|
57
|
+
error: string;
|
|
58
|
+
/** Status code if available */
|
|
59
|
+
statusCode?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Union type of all error details
|
|
64
|
+
*/
|
|
65
|
+
export type ErrorDetail = ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Stored error with unique ID
|
|
69
|
+
*/
|
|
70
|
+
export type StoredError<T extends ErrorDetail = ErrorDetail> = T & {
|
|
71
|
+
/** Unique identifier for this error instance */
|
|
72
|
+
id: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Configuration for specific error type
|
|
77
|
+
*/
|
|
78
|
+
export interface ErrorTypeConfig {
|
|
79
|
+
/**
|
|
80
|
+
* Enable tracking for this error type
|
|
81
|
+
* @default true
|
|
82
|
+
*/
|
|
83
|
+
enabled?: boolean;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Show toast notifications
|
|
87
|
+
* @default true
|
|
88
|
+
*/
|
|
89
|
+
showToast?: boolean;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Maximum number of errors to keep in history
|
|
93
|
+
* @default 50
|
|
94
|
+
*/
|
|
95
|
+
maxErrors?: number;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Toast duration in milliseconds (0 = no auto-dismiss)
|
|
99
|
+
* @default 8000 for validation, 0 for cors/network
|
|
100
|
+
*/
|
|
101
|
+
duration?: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validation error specific config
|
|
106
|
+
*/
|
|
107
|
+
export interface ValidationErrorConfig extends ErrorTypeConfig {
|
|
108
|
+
/**
|
|
109
|
+
* Show operation name in toast
|
|
110
|
+
* @default true
|
|
111
|
+
*/
|
|
112
|
+
showOperation?: boolean;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Show endpoint path in toast
|
|
116
|
+
* @default true
|
|
117
|
+
*/
|
|
118
|
+
showPath?: boolean;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Show error count in toast
|
|
122
|
+
* @default true
|
|
123
|
+
*/
|
|
124
|
+
showErrorCount?: boolean;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Maximum number of issues to show in toast
|
|
128
|
+
* @default 3
|
|
129
|
+
*/
|
|
130
|
+
maxIssuesInToast?: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* CORS error specific config
|
|
135
|
+
*/
|
|
136
|
+
export interface CORSErrorConfig extends ErrorTypeConfig {
|
|
137
|
+
/**
|
|
138
|
+
* Show full URL in toast
|
|
139
|
+
* @default true
|
|
140
|
+
*/
|
|
141
|
+
showUrl?: boolean;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Show HTTP method in toast
|
|
145
|
+
* @default true
|
|
146
|
+
*/
|
|
147
|
+
showMethod?: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Network error specific config
|
|
152
|
+
*/
|
|
153
|
+
export interface NetworkErrorConfig extends ErrorTypeConfig {
|
|
154
|
+
/**
|
|
155
|
+
* Show full URL in toast
|
|
156
|
+
* @default true
|
|
157
|
+
*/
|
|
158
|
+
showUrl?: boolean;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Show HTTP method in toast
|
|
162
|
+
* @default true
|
|
163
|
+
*/
|
|
164
|
+
showMethod?: boolean;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Show status code in toast
|
|
168
|
+
* @default true
|
|
169
|
+
*/
|
|
170
|
+
showStatusCode?: boolean;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Complete error tracking configuration
|
|
175
|
+
*/
|
|
176
|
+
export interface ErrorTrackingConfig {
|
|
177
|
+
/**
|
|
178
|
+
* Validation error tracking configuration
|
|
179
|
+
*/
|
|
180
|
+
validation?: ValidationErrorConfig;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* CORS error tracking configuration
|
|
184
|
+
*/
|
|
185
|
+
cors?: CORSErrorConfig;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Network error tracking configuration
|
|
189
|
+
*/
|
|
190
|
+
network?: NetworkErrorConfig;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Custom error handler (called before toast for all errors)
|
|
194
|
+
* Return false to prevent default toast notification
|
|
195
|
+
*/
|
|
196
|
+
onError?: (error: ErrorDetail) => boolean | void;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Error tracking context value
|
|
201
|
+
*/
|
|
202
|
+
export interface ErrorTrackingContextValue {
|
|
203
|
+
/** All errors */
|
|
204
|
+
errors: StoredError[];
|
|
205
|
+
|
|
206
|
+
/** Validation errors only */
|
|
207
|
+
validationErrors: StoredError<ValidationErrorDetail>[];
|
|
208
|
+
|
|
209
|
+
/** CORS errors only */
|
|
210
|
+
corsErrors: StoredError<CORSErrorDetail>[];
|
|
211
|
+
|
|
212
|
+
/** Network errors only */
|
|
213
|
+
networkErrors: StoredError<NetworkErrorDetail>[];
|
|
214
|
+
|
|
215
|
+
/** Clear all errors */
|
|
216
|
+
clearErrors: () => void;
|
|
217
|
+
|
|
218
|
+
/** Clear errors by type */
|
|
219
|
+
clearErrorsByType: (type: 'validation' | 'cors' | 'network') => void;
|
|
220
|
+
|
|
221
|
+
/** Clear specific error by ID */
|
|
222
|
+
clearError: (id: string) => void;
|
|
223
|
+
|
|
224
|
+
/** Get error count */
|
|
225
|
+
errorCount: number;
|
|
226
|
+
|
|
227
|
+
/** Get latest error */
|
|
228
|
+
latestError: StoredError | null;
|
|
229
|
+
|
|
230
|
+
/** Configuration */
|
|
231
|
+
config: {
|
|
232
|
+
validation: Required<ValidationErrorConfig>;
|
|
233
|
+
cors: Required<CORSErrorConfig>;
|
|
234
|
+
network: Required<NetworkErrorConfig>;
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Event names for error tracking
|
|
240
|
+
*/
|
|
241
|
+
export const ERROR_EVENTS = {
|
|
242
|
+
VALIDATION: 'zod-validation-error',
|
|
243
|
+
CORS: 'cors-error',
|
|
244
|
+
NETWORK: 'network-error',
|
|
245
|
+
} as const;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Default configurations
|
|
249
|
+
*/
|
|
250
|
+
export const DEFAULT_ERROR_CONFIG: Required<ErrorTypeConfig> = {
|
|
251
|
+
enabled: true,
|
|
252
|
+
showToast: true,
|
|
253
|
+
maxErrors: 50,
|
|
254
|
+
duration: 8000,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export const DEFAULT_VALIDATION_CONFIG: Required<ValidationErrorConfig> = {
|
|
258
|
+
...DEFAULT_ERROR_CONFIG,
|
|
259
|
+
showOperation: true,
|
|
260
|
+
showPath: true,
|
|
261
|
+
showErrorCount: true,
|
|
262
|
+
maxIssuesInToast: 3,
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export const DEFAULT_CORS_CONFIG: Required<CORSErrorConfig> = {
|
|
266
|
+
...DEFAULT_ERROR_CONFIG,
|
|
267
|
+
duration: 0, // Don't auto-dismiss CORS errors
|
|
268
|
+
showUrl: true,
|
|
269
|
+
showMethod: true,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const DEFAULT_NETWORK_CONFIG: Required<NetworkErrorConfig> = {
|
|
273
|
+
...DEFAULT_ERROR_CONFIG,
|
|
274
|
+
duration: 0, // Don't auto-dismiss network errors
|
|
275
|
+
showUrl: true,
|
|
276
|
+
showMethod: true,
|
|
277
|
+
showStatusCode: true,
|
|
278
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Formatters
|
|
3
|
+
*
|
|
4
|
+
* Format different error types for display and copying
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ZodError } from 'zod';
|
|
8
|
+
import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail } from '../types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format Zod error issues for display
|
|
12
|
+
*/
|
|
13
|
+
export function formatZodIssues(error: ZodError, maxIssues: number = 3): string {
|
|
14
|
+
const issues = error.issues.slice(0, maxIssues);
|
|
15
|
+
const formatted = issues.map((issue) => {
|
|
16
|
+
const path = issue.path.join('.') || 'root';
|
|
17
|
+
return `${path}: ${issue.message}`;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (error.issues.length > maxIssues) {
|
|
21
|
+
formatted.push(`... and ${error.issues.length - maxIssues} more`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return formatted.join(', ');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format validation error for clipboard
|
|
29
|
+
*/
|
|
30
|
+
export function formatValidationErrorForClipboard(detail: ValidationErrorDetail): string {
|
|
31
|
+
const errorData = {
|
|
32
|
+
type: 'validation',
|
|
33
|
+
timestamp: detail.timestamp.toISOString(),
|
|
34
|
+
operation: detail.operation,
|
|
35
|
+
endpoint: {
|
|
36
|
+
method: detail.method,
|
|
37
|
+
path: detail.path,
|
|
38
|
+
},
|
|
39
|
+
validation_errors: detail.error.issues.map((issue) => ({
|
|
40
|
+
path: issue.path.join('.') || 'root',
|
|
41
|
+
message: issue.message,
|
|
42
|
+
code: issue.code,
|
|
43
|
+
...(('expected' in issue) && { expected: issue.expected }),
|
|
44
|
+
...(('received' in issue) && { received: issue.received }),
|
|
45
|
+
...(('minimum' in issue) && { minimum: issue.minimum }),
|
|
46
|
+
...(('maximum' in issue) && { maximum: issue.maximum }),
|
|
47
|
+
})),
|
|
48
|
+
response: detail.response,
|
|
49
|
+
total_errors: detail.error.issues.length,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return JSON.stringify(errorData, null, 2);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Format CORS error for clipboard
|
|
57
|
+
*/
|
|
58
|
+
export function formatCORSErrorForClipboard(detail: CORSErrorDetail): string {
|
|
59
|
+
const errorData = {
|
|
60
|
+
type: 'cors',
|
|
61
|
+
timestamp: detail.timestamp.toISOString(),
|
|
62
|
+
url: detail.url,
|
|
63
|
+
method: detail.method,
|
|
64
|
+
error: detail.error,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return JSON.stringify(errorData, null, 2);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format network error for clipboard
|
|
72
|
+
*/
|
|
73
|
+
export function formatNetworkErrorForClipboard(detail: NetworkErrorDetail): string {
|
|
74
|
+
const errorData = {
|
|
75
|
+
type: 'network',
|
|
76
|
+
timestamp: detail.timestamp.toISOString(),
|
|
77
|
+
url: detail.url,
|
|
78
|
+
method: detail.method,
|
|
79
|
+
error: detail.error,
|
|
80
|
+
...(detail.statusCode && { statusCode: detail.statusCode }),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return JSON.stringify(errorData, null, 2);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Extract domain from URL
|
|
88
|
+
*/
|
|
89
|
+
export function extractDomain(url: string): string {
|
|
90
|
+
try {
|
|
91
|
+
const urlObj = new URL(url);
|
|
92
|
+
return urlObj.origin;
|
|
93
|
+
} catch {
|
|
94
|
+
return url;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Format error title based on type
|
|
100
|
+
*/
|
|
101
|
+
export function formatErrorTitle(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail): string {
|
|
102
|
+
switch (detail.type) {
|
|
103
|
+
case 'validation':
|
|
104
|
+
return `❌ Validation Error in ${detail.operation}`;
|
|
105
|
+
case 'cors':
|
|
106
|
+
return '🚫 CORS Error';
|
|
107
|
+
case 'network':
|
|
108
|
+
return detail.statusCode
|
|
109
|
+
? `⚠️ Network Error (${detail.statusCode})`
|
|
110
|
+
: '⚠️ Network Error';
|
|
111
|
+
default:
|
|
112
|
+
return '❌ Error';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
# Validation Error System - Refactoring Summary
|
|
2
|
-
|
|
3
|
-
## Changes Made
|
|
4
|
-
|
|
5
|
-
### ✅ Added: Copy as cURL Feature
|
|
6
|
-
|
|
7
|
-
**New Files:**
|
|
8
|
-
- `curl-generator.ts` - cURL command generation
|
|
9
|
-
- `ValidationErrorButtons.tsx` - Button component using `useCopy` hook
|
|
10
|
-
|
|
11
|
-
**Key Improvements:**
|
|
12
|
-
1. **Auto token injection** - Token automatically fetched from localStorage
|
|
13
|
-
2. **useCopy integration** - Uses existing `useCopy` hook instead of custom clipboard logic
|
|
14
|
-
3. **Two buttons layout** - Both buttons at bottom of toast (not on the side)
|
|
15
|
-
4. **Clean separation** - Buttons in separate component, generator in separate file
|
|
16
|
-
|
|
17
|
-
### 🗑️ Removed: Legacy Code
|
|
18
|
-
|
|
19
|
-
**Removed Functions:**
|
|
20
|
-
- `copyToClipboard()` from ValidationErrorToast.tsx (replaced by `useCopy`)
|
|
21
|
-
- `copyCurlToClipboard()` from curl-generator.ts (replaced by `useCopy`)
|
|
22
|
-
- `createCopyAction()` (replaced by ValidationErrorButtons)
|
|
23
|
-
- `createCopyErrorAction()` (replaced by ValidationErrorButtons)
|
|
24
|
-
- `createCopyCurlAction()` (replaced by ValidationErrorButtons)
|
|
25
|
-
|
|
26
|
-
### ♻️ Refactored: Cleaner API
|
|
27
|
-
|
|
28
|
-
**Before:**
|
|
29
|
-
```typescript
|
|
30
|
-
// Multiple separate functions
|
|
31
|
-
createCopyAction(detail, onSuccess, onError);
|
|
32
|
-
createCopyErrorAction(detail, onSuccess, onError);
|
|
33
|
-
createCopyCurlAction(detail, onSuccess, onError);
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
**After:**
|
|
37
|
-
```typescript
|
|
38
|
-
// Single component with useCopy
|
|
39
|
-
<ValidationErrorButtons detail={detail} />
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### 📦 File Structure
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
validation/
|
|
46
|
-
├── curl-generator.ts # cURL generation logic only
|
|
47
|
-
├── ValidationErrorButtons.tsx # Button component (uses useCopy)
|
|
48
|
-
├── ValidationErrorToast.tsx # Toast utilities (no copy logic)
|
|
49
|
-
├── ValidationErrorContext.tsx # Provider & hook
|
|
50
|
-
├── index.ts # Public exports
|
|
51
|
-
└── README.md # Documentation
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Benefits
|
|
55
|
-
|
|
56
|
-
1. **No code duplication** - Uses existing `useCopy` instead of custom clipboard code
|
|
57
|
-
2. **Consistent UX** - All copy operations show the same toast feedback
|
|
58
|
-
3. **Cleaner separation** - Each file has single responsibility
|
|
59
|
-
4. **Easier to maintain** - Less code, clearer structure
|
|
60
|
-
5. **Better DX** - Simple `<ValidationErrorButtons />` instead of 3 separate functions
|
|
61
|
-
|
|
62
|
-
## API Changes
|
|
63
|
-
|
|
64
|
-
### Removed Exports
|
|
65
|
-
```typescript
|
|
66
|
-
// ❌ No longer exported
|
|
67
|
-
createCopyAction
|
|
68
|
-
createCopyErrorAction
|
|
69
|
-
createCopyCurlAction
|
|
70
|
-
copyCurlToClipboard
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### New Exports
|
|
74
|
-
```typescript
|
|
75
|
-
// ✅ New exports
|
|
76
|
-
ValidationErrorButtons // Component
|
|
77
|
-
generateCurl // Generate cURL string
|
|
78
|
-
generateCurlFromError // Generate from error detail
|
|
79
|
-
getAuthToken // Get token from localStorage
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Unchanged Exports
|
|
83
|
-
```typescript
|
|
84
|
-
// ✅ Still available
|
|
85
|
-
ValidationErrorProvider
|
|
86
|
-
useValidationErrors
|
|
87
|
-
createValidationErrorToast
|
|
88
|
-
formatErrorForClipboard
|
|
89
|
-
buildToastTitle
|
|
90
|
-
buildToastDescription
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Migration Guide
|
|
94
|
-
|
|
95
|
-
If you were using the old `createCopyAction`:
|
|
96
|
-
|
|
97
|
-
**Before:**
|
|
98
|
-
```typescript
|
|
99
|
-
const toastOptions = createValidationErrorToast(errorDetail, {
|
|
100
|
-
onCopySuccess: () => console.log('Copied'),
|
|
101
|
-
onCopyError: (e) => console.error(e),
|
|
102
|
-
});
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
**After:**
|
|
106
|
-
```typescript
|
|
107
|
-
// Just use it - ValidationErrorButtons handles everything
|
|
108
|
-
const toastOptions = createValidationErrorToast(errorDetail);
|
|
109
|
-
// That's it! Toast feedback is automatic via useCopy
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Implementation Details
|
|
113
|
-
|
|
114
|
-
### ValidationErrorButtons Component
|
|
115
|
-
|
|
116
|
-
Uses `useCopy` hook from `@djangocfg/ui`:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
const { copyToClipboard } = useCopy();
|
|
120
|
-
|
|
121
|
-
const handleCopyError = async (e) => {
|
|
122
|
-
const json = JSON.stringify(errorData, null, 2);
|
|
123
|
-
await copyToClipboard(json, '✅ Error details copied');
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const handleCopyCurl = async (e) => {
|
|
127
|
-
const curl = generateCurlFromError(detail);
|
|
128
|
-
await copyToClipboard(curl, '✅ cURL command copied');
|
|
129
|
-
};
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Auto Token Injection
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
export function generateCurl(options: CurlOptions): string {
|
|
136
|
-
const { token = getAuthToken() || undefined } = options;
|
|
137
|
-
// ...
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Token is auto-fetched if not provided.
|
|
142
|
-
|
|
143
|
-
## Testing
|
|
144
|
-
|
|
145
|
-
1. Trigger validation error (e.g., visit `/api/proxies/proxies/`)
|
|
146
|
-
2. Toast appears with two buttons at bottom
|
|
147
|
-
3. Click **📋 Copy Error** → Success toast appears → JSON in clipboard
|
|
148
|
-
4. Click **🔄 Copy cURL** → Success toast appears → cURL in clipboard
|
|
149
|
-
5. Paste cURL in terminal → Works with your auth token!
|
|
150
|
-
|
|
151
|
-
## What's Next
|
|
152
|
-
|
|
153
|
-
Possible future enhancements:
|
|
154
|
-
- [ ] Support POST/PUT body in cURL
|
|
155
|
-
- [ ] Option to copy without token
|
|
156
|
-
- [ ] Different formats (fetch, axios)
|
|
157
|
-
- [ ] Copy request+response together
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
**Date:** 2025-11-11
|
|
162
|
-
**Impact:** Breaking changes for anyone using internal copy functions (unlikely)
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ValidationErrorButtons Component
|
|
3
|
-
*
|
|
4
|
-
* Copy buttons for validation errors using useCopy hook
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use client';
|
|
8
|
-
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { Button, useCopy } from '@djangocfg/ui';
|
|
11
|
-
import { generateCurlFromError } from './curl-generator';
|
|
12
|
-
import type { ValidationErrorDetail } from './ValidationErrorToast';
|
|
13
|
-
|
|
14
|
-
export interface ValidationErrorButtonsProps {
|
|
15
|
-
detail: ValidationErrorDetail;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function ValidationErrorButtons({ detail }: ValidationErrorButtonsProps) {
|
|
19
|
-
const { copyToClipboard } = useCopy();
|
|
20
|
-
|
|
21
|
-
const handleCopyError = async (e: React.MouseEvent) => {
|
|
22
|
-
e.preventDefault();
|
|
23
|
-
e.stopPropagation();
|
|
24
|
-
|
|
25
|
-
const errorData = {
|
|
26
|
-
timestamp: detail.timestamp.toISOString(),
|
|
27
|
-
operation: detail.operation,
|
|
28
|
-
endpoint: {
|
|
29
|
-
method: detail.method,
|
|
30
|
-
path: detail.path,
|
|
31
|
-
},
|
|
32
|
-
validation_errors: detail.error.issues.map((issue) => ({
|
|
33
|
-
path: issue.path.join('.') || 'root',
|
|
34
|
-
message: issue.message,
|
|
35
|
-
code: issue.code,
|
|
36
|
-
...(('expected' in issue) && { expected: issue.expected }),
|
|
37
|
-
...(('received' in issue) && { received: issue.received }),
|
|
38
|
-
})),
|
|
39
|
-
response: detail.response,
|
|
40
|
-
total_errors: detail.error.issues.length,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const formattedError = JSON.stringify(errorData, null, 2);
|
|
44
|
-
await copyToClipboard(formattedError, '✅ Error details copied');
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const handleCopyCurl = async (e: React.MouseEvent) => {
|
|
48
|
-
e.preventDefault();
|
|
49
|
-
e.stopPropagation();
|
|
50
|
-
|
|
51
|
-
const curl = generateCurlFromError({
|
|
52
|
-
method: detail.method,
|
|
53
|
-
path: detail.path,
|
|
54
|
-
response: detail.response,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
await copyToClipboard(curl, '✅ cURL command copied');
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div className="flex gap-2 mt-2">
|
|
62
|
-
<Button
|
|
63
|
-
size="sm"
|
|
64
|
-
variant="outline"
|
|
65
|
-
onClick={handleCopyError}
|
|
66
|
-
className="h-8 text-xs"
|
|
67
|
-
>
|
|
68
|
-
📋 Copy Error
|
|
69
|
-
</Button>
|
|
70
|
-
<Button
|
|
71
|
-
size="sm"
|
|
72
|
-
variant="outline"
|
|
73
|
-
onClick={handleCopyCurl}
|
|
74
|
-
className="h-8 text-xs"
|
|
75
|
-
>
|
|
76
|
-
🔄 Copy cURL
|
|
77
|
-
</Button>
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
}
|