@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.
@@ -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
- }