@djangocfg/layouts 1.2.33 → 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/layouts/AppLayout/components/PackageVersions/packageVersions.config.ts +8 -8
- package/src/validation/README.md +88 -2
- package/src/validation/REFACTORING.md +162 -0
- package/src/validation/ValidationErrorButtons.tsx +80 -0
- package/src/validation/ValidationErrorToast.tsx +7 -77
- package/src/validation/curl-generator.ts +118 -0
- package/src/validation/index.ts +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.34",
|
|
4
4
|
"description": "Layout system and components for Unrealon applications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "DjangoCFG",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"check": "tsc --noEmit"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@djangocfg/api": "^1.2.
|
|
67
|
-
"@djangocfg/og-image": "^1.2.
|
|
68
|
-
"@djangocfg/ui": "^1.2.
|
|
66
|
+
"@djangocfg/api": "^1.2.34",
|
|
67
|
+
"@djangocfg/og-image": "^1.2.34",
|
|
68
|
+
"@djangocfg/ui": "^1.2.34",
|
|
69
69
|
"@hookform/resolvers": "^5.2.0",
|
|
70
70
|
"consola": "^3.4.2",
|
|
71
71
|
"lucide-react": "^0.468.0",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"vidstack": "0.6.15"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@djangocfg/typescript-config": "^1.2.
|
|
89
|
+
"@djangocfg/typescript-config": "^1.2.34",
|
|
90
90
|
"@types/node": "^24.7.2",
|
|
91
91
|
"@types/react": "19.2.2",
|
|
92
92
|
"@types/react-dom": "19.2.1",
|
|
@@ -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-11T06:22:05.284Z
|
|
20
20
|
*/
|
|
21
21
|
const PACKAGE_VERSIONS: PackageInfo[] = [
|
|
22
22
|
{
|
|
23
23
|
"name": "@djangocfg/ui",
|
|
24
|
-
"version": "1.2.
|
|
24
|
+
"version": "1.2.34"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"name": "@djangocfg/api",
|
|
28
|
-
"version": "1.2.
|
|
28
|
+
"version": "1.2.34"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"name": "@djangocfg/layouts",
|
|
32
|
-
"version": "1.2.
|
|
32
|
+
"version": "1.2.34"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "@djangocfg/markdown",
|
|
36
|
-
"version": "1.2.
|
|
36
|
+
"version": "1.2.34"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"name": "@djangocfg/og-image",
|
|
40
|
-
"version": "1.2.
|
|
40
|
+
"version": "1.2.34"
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"name": "@djangocfg/eslint-config",
|
|
44
|
-
"version": "1.2.
|
|
44
|
+
"version": "1.2.34"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "@djangocfg/typescript-config",
|
|
48
|
-
"version": "1.2.
|
|
48
|
+
"version": "1.2.34"
|
|
49
49
|
}
|
|
50
50
|
];
|
|
51
51
|
|
package/src/validation/README.md
CHANGED
|
@@ -8,7 +8,8 @@ Automatic Zod validation error tracking with toast notifications and copy-to-cli
|
|
|
8
8
|
|
|
9
9
|
✅ **Auto-capture validation errors** from API client via CustomEvent
|
|
10
10
|
✅ **Toast notifications** with destructive styling
|
|
11
|
-
✅ **Copy button** - One-click copy full error details to clipboard
|
|
11
|
+
✅ **Copy Error button** - One-click copy full error details JSON to clipboard
|
|
12
|
+
✅ **Copy cURL button** - Generate and copy cURL command with auth token
|
|
12
13
|
✅ **Error history** - Store and manage validation errors
|
|
13
14
|
✅ **Configurable** - Customize display and behavior
|
|
14
15
|
✅ **Type-safe** - Full TypeScript support
|
|
@@ -69,7 +70,9 @@ window.dispatchEvent(new CustomEvent('zod-validation-error', {
|
|
|
69
70
|
You'll see a toast with:
|
|
70
71
|
- **Title**: "❌ Validation Error in operation_name"
|
|
71
72
|
- **Description**: Endpoint, error count, first 3 errors
|
|
72
|
-
- **
|
|
73
|
+
- **Two Buttons** (displayed at bottom):
|
|
74
|
+
- **📋 Copy Error** - Copies full error JSON to clipboard
|
|
75
|
+
- **🔄 Copy cURL** - Generates and copies cURL command with auth token
|
|
73
76
|
|
|
74
77
|
---
|
|
75
78
|
|
|
@@ -490,8 +493,91 @@ validation/
|
|
|
490
493
|
|
|
491
494
|
---
|
|
492
495
|
|
|
496
|
+
## Copy as cURL Feature
|
|
497
|
+
|
|
498
|
+
### What Gets Copied
|
|
499
|
+
|
|
500
|
+
When you click **🔄 Copy cURL**, a ready-to-use cURL command is generated:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
curl 'http://localhost:8000/api/proxies/proxies/?page=1&page_size=100' \
|
|
504
|
+
-H 'Accept: */*' \
|
|
505
|
+
-H 'Content-Type: application/json' \
|
|
506
|
+
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
The generator automatically:
|
|
510
|
+
- ✅ Adds your auth token from localStorage
|
|
511
|
+
- ✅ Formats headers properly
|
|
512
|
+
- ✅ Uses correct HTTP method
|
|
513
|
+
- ✅ Escapes special characters
|
|
514
|
+
|
|
515
|
+
### Token Auto-Detection
|
|
516
|
+
|
|
517
|
+
The cURL generator looks for your token in localStorage:
|
|
518
|
+
- `access_token` (default)
|
|
519
|
+
- `token`
|
|
520
|
+
- `auth_token`
|
|
521
|
+
|
|
522
|
+
### Manual cURL Generation
|
|
523
|
+
|
|
524
|
+
You can also generate cURL commands programmatically:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
import { generateCurl, copyCurlToClipboard } from '@djangocfg/layouts/validation';
|
|
528
|
+
|
|
529
|
+
// Generate cURL
|
|
530
|
+
const curl = generateCurl({
|
|
531
|
+
method: 'POST',
|
|
532
|
+
path: '/api/users/',
|
|
533
|
+
token: 'your-token',
|
|
534
|
+
body: { name: 'John' },
|
|
535
|
+
headers: { 'X-Custom': 'value' },
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Copy to clipboard
|
|
539
|
+
await copyCurlToClipboard(curl);
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### API Reference - cURL
|
|
543
|
+
|
|
544
|
+
#### `generateCurl(options: CurlOptions): string`
|
|
545
|
+
|
|
546
|
+
**Options:**
|
|
547
|
+
```typescript
|
|
548
|
+
interface CurlOptions {
|
|
549
|
+
method: string; // HTTP method
|
|
550
|
+
path: string; // API path
|
|
551
|
+
token?: string; // Auth token (auto-fetched if omitted)
|
|
552
|
+
body?: any; // Request body
|
|
553
|
+
headers?: Record<string, string>; // Custom headers
|
|
554
|
+
baseUrl?: string; // API base URL (from env by default)
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
#### `generateCurlFromError(detail): string`
|
|
559
|
+
|
|
560
|
+
Generate cURL from validation error details. Auto-fetches token from localStorage.
|
|
561
|
+
|
|
562
|
+
#### `getAuthToken(): string | null`
|
|
563
|
+
|
|
564
|
+
Get authentication token from localStorage.
|
|
565
|
+
|
|
566
|
+
#### `copyCurlToClipboard(curl: string): Promise<boolean>`
|
|
567
|
+
|
|
568
|
+
Copy cURL command to clipboard. Returns `true` on success.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
493
572
|
## Changelog
|
|
494
573
|
|
|
574
|
+
### 2025-11-11 - v2.1.0
|
|
575
|
+
- ✨ Added **Copy cURL button** with automatic token injection
|
|
576
|
+
- ✨ Added `curl-generator.ts` utilities
|
|
577
|
+
- ✨ Added two buttons layout at bottom of toast
|
|
578
|
+
- ✨ Auto-detect auth token from localStorage
|
|
579
|
+
- 📝 Updated documentation with cURL examples
|
|
580
|
+
|
|
495
581
|
### 2025-11-11 - v2.0.0
|
|
496
582
|
- ✨ Added copy-to-clipboard button in toast
|
|
497
583
|
- ✨ Added `ValidationErrorToast` utilities
|
|
@@ -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
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { ToastAction } from '@djangocfg/ui';
|
|
12
12
|
import type { ZodError } from 'zod';
|
|
13
|
+
import { ValidationErrorButtons } from './ValidationErrorButtons';
|
|
13
14
|
|
|
14
15
|
export interface ValidationErrorDetail {
|
|
15
16
|
operation: string;
|
|
@@ -89,34 +90,6 @@ export function formatErrorForClipboard(detail: ValidationErrorDetail): string {
|
|
|
89
90
|
return JSON.stringify(errorData, null, 2);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
/**
|
|
93
|
-
* Copy text to clipboard
|
|
94
|
-
*/
|
|
95
|
-
async function copyToClipboard(text: string): Promise<boolean> {
|
|
96
|
-
if (typeof window === 'undefined') return false;
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
100
|
-
await navigator.clipboard.writeText(text);
|
|
101
|
-
return true;
|
|
102
|
-
} else {
|
|
103
|
-
// Fallback for older browsers
|
|
104
|
-
const textarea = document.createElement('textarea');
|
|
105
|
-
textarea.value = text;
|
|
106
|
-
textarea.style.position = 'fixed';
|
|
107
|
-
textarea.style.opacity = '0';
|
|
108
|
-
document.body.appendChild(textarea);
|
|
109
|
-
textarea.select();
|
|
110
|
-
const success = document.execCommand('copy');
|
|
111
|
-
document.body.removeChild(textarea);
|
|
112
|
-
return success;
|
|
113
|
-
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error('Failed to copy to clipboard:', error);
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
93
|
/**
|
|
121
94
|
* Build toast title from validation error
|
|
122
95
|
*/
|
|
@@ -165,49 +138,6 @@ export function buildToastDescription(
|
|
|
165
138
|
return descriptionParts.join(' • ');
|
|
166
139
|
}
|
|
167
140
|
|
|
168
|
-
/**
|
|
169
|
-
* Create copy action button for toast
|
|
170
|
-
*
|
|
171
|
-
* @param detail - Validation error details
|
|
172
|
-
* @param onCopySuccess - Optional callback when copy succeeds
|
|
173
|
-
* @param onCopyError - Optional callback when copy fails
|
|
174
|
-
*/
|
|
175
|
-
export function createCopyAction(
|
|
176
|
-
detail: ValidationErrorDetail,
|
|
177
|
-
onCopySuccess?: () => void,
|
|
178
|
-
onCopyError?: (error: Error) => void
|
|
179
|
-
): React.ReactElement<typeof ToastAction> {
|
|
180
|
-
const handleCopy = async (e: React.MouseEvent) => {
|
|
181
|
-
e.preventDefault();
|
|
182
|
-
e.stopPropagation();
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const formattedError = formatErrorForClipboard(detail);
|
|
186
|
-
const success = await copyToClipboard(formattedError);
|
|
187
|
-
|
|
188
|
-
if (success) {
|
|
189
|
-
console.log('✅ Validation error copied to clipboard');
|
|
190
|
-
onCopySuccess?.();
|
|
191
|
-
} else {
|
|
192
|
-
throw new Error('Clipboard API failed');
|
|
193
|
-
}
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error('❌ Failed to copy validation error:', error);
|
|
196
|
-
onCopyError?.(error as Error);
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<ToastAction
|
|
202
|
-
altText="Copy error details"
|
|
203
|
-
onClick={handleCopy}
|
|
204
|
-
className="shrink-0"
|
|
205
|
-
>
|
|
206
|
-
📋 Copy
|
|
207
|
-
</ToastAction>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
141
|
/**
|
|
212
142
|
* Create complete toast options for validation error
|
|
213
143
|
*
|
|
@@ -239,13 +169,13 @@ export function createValidationErrorToast(
|
|
|
239
169
|
|
|
240
170
|
return {
|
|
241
171
|
title: buildToastTitle(detail, config),
|
|
242
|
-
description:
|
|
172
|
+
description: (
|
|
173
|
+
<div className="flex flex-col gap-2">
|
|
174
|
+
<div>{buildToastDescription(detail, config)}</div>
|
|
175
|
+
<ValidationErrorButtons detail={detail} />
|
|
176
|
+
</div>
|
|
177
|
+
),
|
|
243
178
|
variant: 'destructive' as const,
|
|
244
179
|
duration: options?.duration,
|
|
245
|
-
action: createCopyAction(
|
|
246
|
-
detail,
|
|
247
|
-
options?.onCopySuccess,
|
|
248
|
-
options?.onCopyError
|
|
249
|
-
),
|
|
250
180
|
};
|
|
251
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
|
+
}
|
package/src/validation/index.ts
CHANGED
|
@@ -16,10 +16,21 @@ export {
|
|
|
16
16
|
|
|
17
17
|
export {
|
|
18
18
|
createValidationErrorToast,
|
|
19
|
-
createCopyAction,
|
|
20
19
|
buildToastTitle,
|
|
21
20
|
buildToastDescription,
|
|
22
21
|
formatZodIssuesForToast,
|
|
23
22
|
formatErrorForClipboard,
|
|
24
23
|
type ValidationErrorToastConfig,
|
|
25
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';
|