@djangocfg/layouts 1.2.33 → 1.2.35

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.
@@ -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: buildToastDescription(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
+ ),
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
+ }
@@ -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';