@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,162 @@
|
|
|
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)
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationErrorContext - Global Zod validation error tracking
|
|
3
|
+
*
|
|
4
|
+
* Automatically captures all Zod validation errors from API client
|
|
5
|
+
* using browser CustomEvent API and provides centralized state management.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Automatic error capture via window.addEventListener
|
|
9
|
+
* - Toast notifications for user feedback
|
|
10
|
+
* - Error history with timestamps
|
|
11
|
+
* - Configurable error limits
|
|
12
|
+
* - React Context API for component access
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { useValidationErrors } from '@djangocfg/layouts';
|
|
17
|
+
*
|
|
18
|
+
* function MyComponent() {
|
|
19
|
+
* const { errors, clearErrors, clearError } = useValidationErrors();
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* <h2>Recent Validation Errors ({errors.length})</h2>
|
|
24
|
+
* {errors.map((error) => (
|
|
25
|
+
* <div key={error.id}>
|
|
26
|
+
* <strong>{error.operation}</strong>
|
|
27
|
+
* <p>{error.timestamp.toLocaleString()}</p>
|
|
28
|
+
* <button onClick={() => clearError(error.id)}>Clear</button>
|
|
29
|
+
* </div>
|
|
30
|
+
* ))}
|
|
31
|
+
* </div>
|
|
32
|
+
* );
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
'use client';
|
|
38
|
+
|
|
39
|
+
import React, { createContext, useContext, useCallback, useEffect, useState, ReactNode } from 'react';
|
|
40
|
+
import type { ZodError } from 'zod';
|
|
41
|
+
import { toast } from '@djangocfg/ui';
|
|
42
|
+
import { createValidationErrorToast } from './ValidationErrorToast';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validation error detail from CustomEvent
|
|
46
|
+
*/
|
|
47
|
+
export interface ValidationErrorDetail {
|
|
48
|
+
/** Operation/function name that failed validation */
|
|
49
|
+
operation: string;
|
|
50
|
+
/** API endpoint path */
|
|
51
|
+
path: string;
|
|
52
|
+
/** HTTP method */
|
|
53
|
+
method: string;
|
|
54
|
+
/** Zod validation error */
|
|
55
|
+
error: ZodError;
|
|
56
|
+
/** Raw response data that failed validation */
|
|
57
|
+
response: any;
|
|
58
|
+
/** Timestamp of the error */
|
|
59
|
+
timestamp: Date;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Stored validation error with unique ID
|
|
64
|
+
*/
|
|
65
|
+
export interface StoredValidationError extends ValidationErrorDetail {
|
|
66
|
+
/** Unique identifier for this error instance */
|
|
67
|
+
id: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Configuration for ValidationErrorProvider
|
|
72
|
+
*/
|
|
73
|
+
export interface ValidationErrorConfig {
|
|
74
|
+
/**
|
|
75
|
+
* Maximum number of errors to keep in history
|
|
76
|
+
* @default 50
|
|
77
|
+
*/
|
|
78
|
+
maxErrors?: number;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Enable toast notifications on validation errors
|
|
82
|
+
* @default true
|
|
83
|
+
*/
|
|
84
|
+
enableToast?: boolean;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Toast notification settings
|
|
88
|
+
*/
|
|
89
|
+
toastSettings?: {
|
|
90
|
+
/** Show operation name in toast */
|
|
91
|
+
showOperation?: boolean;
|
|
92
|
+
/** Show endpoint path in toast */
|
|
93
|
+
showPath?: boolean;
|
|
94
|
+
/** Show error count in toast */
|
|
95
|
+
showErrorCount?: boolean;
|
|
96
|
+
/** Toast duration in milliseconds (0 = no auto-dismiss) */
|
|
97
|
+
duration?: number;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Custom error handler (called before toast)
|
|
102
|
+
* Return false to prevent default toast notification
|
|
103
|
+
*/
|
|
104
|
+
onError?: (error: ValidationErrorDetail) => boolean | void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Context value
|
|
109
|
+
*/
|
|
110
|
+
export interface ValidationErrorContextValue {
|
|
111
|
+
/** Array of validation errors */
|
|
112
|
+
errors: StoredValidationError[];
|
|
113
|
+
|
|
114
|
+
/** Clear all errors */
|
|
115
|
+
clearErrors: () => void;
|
|
116
|
+
|
|
117
|
+
/** Clear specific error by ID */
|
|
118
|
+
clearError: (id: string) => void;
|
|
119
|
+
|
|
120
|
+
/** Get error count */
|
|
121
|
+
errorCount: number;
|
|
122
|
+
|
|
123
|
+
/** Get latest error */
|
|
124
|
+
latestError: StoredValidationError | null;
|
|
125
|
+
|
|
126
|
+
/** Configuration */
|
|
127
|
+
config: Required<ValidationErrorConfig>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ValidationErrorContext = createContext<ValidationErrorContextValue | undefined>(undefined);
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Default configuration
|
|
134
|
+
*/
|
|
135
|
+
const defaultConfig: Required<ValidationErrorConfig> = {
|
|
136
|
+
maxErrors: 50,
|
|
137
|
+
enableToast: true,
|
|
138
|
+
toastSettings: {
|
|
139
|
+
showOperation: true,
|
|
140
|
+
showPath: true,
|
|
141
|
+
showErrorCount: true,
|
|
142
|
+
duration: 8000, // 8 seconds
|
|
143
|
+
},
|
|
144
|
+
onError: () => true,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Generate unique ID for error
|
|
149
|
+
*/
|
|
150
|
+
let errorIdCounter = 0;
|
|
151
|
+
function generateErrorId(): string {
|
|
152
|
+
return `validation-error-${Date.now()}-${++errorIdCounter}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format Zod error issues for display
|
|
157
|
+
*/
|
|
158
|
+
function formatZodIssues(error: ZodError): string {
|
|
159
|
+
const issues = error.issues.slice(0, 3); // Show max 3 issues
|
|
160
|
+
const formatted = issues.map((issue) => {
|
|
161
|
+
const path = issue.path.join('.') || 'root';
|
|
162
|
+
return `${path}: ${issue.message}`;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (error.issues.length > 3) {
|
|
166
|
+
formatted.push(`... and ${error.issues.length - 3} more`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return formatted.join(', ');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface ValidationErrorProviderProps {
|
|
173
|
+
children: ReactNode;
|
|
174
|
+
config?: Partial<ValidationErrorConfig>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* ValidationErrorProvider Component
|
|
179
|
+
*
|
|
180
|
+
* Wraps application and provides validation error tracking.
|
|
181
|
+
* Automatically listens to 'zod-validation-error' CustomEvents
|
|
182
|
+
* from the API client.
|
|
183
|
+
*/
|
|
184
|
+
export function ValidationErrorProvider({ children, config: userConfig }: ValidationErrorProviderProps) {
|
|
185
|
+
const [errors, setErrors] = useState<StoredValidationError[]>([]);
|
|
186
|
+
|
|
187
|
+
// Merge user config with defaults
|
|
188
|
+
const config: Required<ValidationErrorConfig> = {
|
|
189
|
+
...defaultConfig,
|
|
190
|
+
...userConfig,
|
|
191
|
+
toastSettings: {
|
|
192
|
+
...defaultConfig.toastSettings,
|
|
193
|
+
...userConfig?.toastSettings,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Clear all errors
|
|
199
|
+
*/
|
|
200
|
+
const clearErrors = useCallback(() => {
|
|
201
|
+
setErrors([]);
|
|
202
|
+
}, []);
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clear specific error
|
|
206
|
+
*/
|
|
207
|
+
const clearError = useCallback((id: string) => {
|
|
208
|
+
setErrors((prev) => prev.filter((error) => error.id !== id));
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Handle validation error event
|
|
213
|
+
*/
|
|
214
|
+
const handleValidationError = useCallback(
|
|
215
|
+
(event: Event) => {
|
|
216
|
+
if (!(event instanceof CustomEvent)) return;
|
|
217
|
+
|
|
218
|
+
const detail = event.detail as ValidationErrorDetail;
|
|
219
|
+
|
|
220
|
+
// Create stored error with ID
|
|
221
|
+
const storedError: StoredValidationError = {
|
|
222
|
+
...detail,
|
|
223
|
+
id: generateErrorId(),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Add to errors array (limited by maxErrors)
|
|
227
|
+
setErrors((prev) => {
|
|
228
|
+
const updated = [storedError, ...prev];
|
|
229
|
+
return updated.slice(0, config.maxErrors);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Call custom error handler
|
|
233
|
+
const shouldShowToast = config.onError?.(detail) !== false;
|
|
234
|
+
|
|
235
|
+
// Show toast notification with copy button
|
|
236
|
+
if (config.enableToast && shouldShowToast) {
|
|
237
|
+
const { showOperation, showPath, showErrorCount } = config.toastSettings;
|
|
238
|
+
|
|
239
|
+
// Create toast with custom copy action button
|
|
240
|
+
const toastOptions = createValidationErrorToast(detail, {
|
|
241
|
+
config: {
|
|
242
|
+
showOperation,
|
|
243
|
+
showPath,
|
|
244
|
+
showErrorCount,
|
|
245
|
+
maxIssuesInDescription: 3,
|
|
246
|
+
titlePrefix: '❌ Validation Error',
|
|
247
|
+
},
|
|
248
|
+
duration: config.toastSettings.duration,
|
|
249
|
+
onCopySuccess: () => {
|
|
250
|
+
// Show success feedback when error details are copied
|
|
251
|
+
toast({
|
|
252
|
+
title: '✅ Copied!',
|
|
253
|
+
description: 'Error details copied to clipboard',
|
|
254
|
+
duration: 2000,
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
onCopyError: (error) => {
|
|
258
|
+
// Show error feedback if copy fails
|
|
259
|
+
toast({
|
|
260
|
+
title: '❌ Copy failed',
|
|
261
|
+
description: 'Could not copy error details',
|
|
262
|
+
variant: 'destructive',
|
|
263
|
+
duration: 2000,
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
toast(toastOptions);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
[config]
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Setup event listener
|
|
276
|
+
*/
|
|
277
|
+
useEffect(() => {
|
|
278
|
+
// Only run in browser
|
|
279
|
+
if (typeof window === 'undefined') return;
|
|
280
|
+
|
|
281
|
+
window.addEventListener('zod-validation-error', handleValidationError);
|
|
282
|
+
|
|
283
|
+
return () => {
|
|
284
|
+
window.removeEventListener('zod-validation-error', handleValidationError);
|
|
285
|
+
};
|
|
286
|
+
}, [handleValidationError]);
|
|
287
|
+
|
|
288
|
+
const value: ValidationErrorContextValue = {
|
|
289
|
+
errors,
|
|
290
|
+
clearErrors,
|
|
291
|
+
clearError,
|
|
292
|
+
errorCount: errors.length,
|
|
293
|
+
latestError: errors[0] || null,
|
|
294
|
+
config,
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<ValidationErrorContext.Provider value={value}>
|
|
299
|
+
{children}
|
|
300
|
+
</ValidationErrorContext.Provider>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* useValidationErrors Hook
|
|
306
|
+
*
|
|
307
|
+
* Access validation errors from any component
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```tsx
|
|
311
|
+
* function ErrorPanel() {
|
|
312
|
+
* const { errors, clearErrors } = useValidationErrors();
|
|
313
|
+
*
|
|
314
|
+
* if (errors.length === 0) return null;
|
|
315
|
+
*
|
|
316
|
+
* return (
|
|
317
|
+
* <div>
|
|
318
|
+
* <h3>Validation Errors ({errors.length})</h3>
|
|
319
|
+
* <button onClick={clearErrors}>Clear All</button>
|
|
320
|
+
* </div>
|
|
321
|
+
* );
|
|
322
|
+
* }
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
export function useValidationErrors(): ValidationErrorContextValue {
|
|
326
|
+
const context = useContext(ValidationErrorContext);
|
|
327
|
+
|
|
328
|
+
if (context === undefined) {
|
|
329
|
+
throw new Error('useValidationErrors must be used within ValidationErrorProvider');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return context;
|
|
333
|
+
}
|