@umituz/web-design-system 1.0.4 → 1.3.1
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/README.md +181 -0
- package/package.json +18 -5
- package/src/global.d.ts +20 -0
- package/src/index.ts +3 -0
- package/src/infrastructure/error/ErrorBoundary.tsx +258 -0
- package/src/infrastructure/error/ErrorDisplay.tsx +346 -0
- package/src/infrastructure/error/SuspenseWrapper.tsx +134 -0
- package/src/infrastructure/error/index.ts +5 -0
- package/src/infrastructure/performance/index.ts +6 -0
- package/src/infrastructure/performance/useLazyLoading.ts +342 -0
- package/src/infrastructure/performance/useMemoryOptimization.ts +293 -0
- package/src/infrastructure/performance/usePerformanceMonitor.ts +158 -0
- package/src/infrastructure/security/index.ts +10 -0
- package/src/infrastructure/security/security-config.ts +171 -0
- package/src/infrastructure/security/useFormValidation.ts +216 -0
- package/src/infrastructure/security/validation.ts +242 -0
- package/src/infrastructure/utils/cn.util.ts +7 -7
- package/src/infrastructure/utils/index.ts +1 -1
- package/src/presentation/atoms/Input.tsx +1 -1
- package/src/presentation/atoms/Slider.tsx +1 -1
- package/src/presentation/atoms/Text.tsx +1 -1
- package/src/presentation/atoms/Tooltip.tsx +2 -2
- package/src/presentation/hooks/index.ts +2 -1
- package/src/presentation/hooks/useClickOutside.ts +1 -1
- package/src/presentation/molecules/CheckboxGroup.tsx +1 -1
- package/src/presentation/molecules/FormField.tsx +2 -2
- package/src/presentation/molecules/InputGroup.tsx +1 -1
- package/src/presentation/molecules/RadioGroup.tsx +1 -1
- package/src/presentation/organisms/Alert.tsx +1 -1
- package/src/presentation/organisms/Card.tsx +1 -1
- package/src/presentation/organisms/Footer.tsx +99 -0
- package/src/presentation/organisms/Navbar.tsx +1 -1
- package/src/presentation/organisms/Table.tsx +1 -1
- package/src/presentation/organisms/index.ts +3 -0
- package/src/presentation/templates/Form.tsx +2 -2
- package/src/presentation/templates/List.tsx +1 -1
- package/src/presentation/templates/PageHeader.tsx +78 -0
- package/src/presentation/templates/PageLayout.tsx +38 -0
- package/src/presentation/templates/ProjectSkeleton.tsx +35 -0
- package/src/presentation/templates/Section.tsx +1 -1
- package/src/presentation/templates/index.ts +9 -0
package/README.md
CHANGED
|
@@ -103,6 +103,46 @@ import type {
|
|
|
103
103
|
} from '@umituz/web-design-system/types';
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
### Security (Security & Validation)
|
|
107
|
+
```tsx
|
|
108
|
+
import {
|
|
109
|
+
validateInput,
|
|
110
|
+
validateEmail,
|
|
111
|
+
validateUrl,
|
|
112
|
+
sanitizeInput,
|
|
113
|
+
validateFileName,
|
|
114
|
+
CSP_CONFIG,
|
|
115
|
+
SECURITY_HEADERS,
|
|
116
|
+
VALIDATION_CONFIGS
|
|
117
|
+
} from '@umituz/web-design-system/security';
|
|
118
|
+
|
|
119
|
+
import { useFormValidation, COMMON_RULES } from '@umituz/web-design-system/security';
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Performance (Performance Optimization)
|
|
123
|
+
```tsx
|
|
124
|
+
import {
|
|
125
|
+
usePerformanceMonitor,
|
|
126
|
+
useLazyLoading,
|
|
127
|
+
useMemoryOptimization,
|
|
128
|
+
useLazyImage,
|
|
129
|
+
useLazyComponent,
|
|
130
|
+
useVirtualList
|
|
131
|
+
} from '@umituz/web-design-system/performance';
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Error (Error Handling & Boundaries)
|
|
135
|
+
```tsx
|
|
136
|
+
import {
|
|
137
|
+
ErrorBoundary,
|
|
138
|
+
ErrorDisplay,
|
|
139
|
+
SuspenseWrapper,
|
|
140
|
+
NetworkError,
|
|
141
|
+
NotFoundError,
|
|
142
|
+
ApiError
|
|
143
|
+
} from '@umituz/web-design-system/error';
|
|
144
|
+
```
|
|
145
|
+
|
|
106
146
|
## 🎨 Component Examples
|
|
107
147
|
|
|
108
148
|
### Button
|
|
@@ -192,6 +232,147 @@ const fontSize = fontSizes['lg']; // '1.125rem'
|
|
|
192
232
|
const fontWeight = fontWeights['semibold']; // '600'
|
|
193
233
|
```
|
|
194
234
|
|
|
235
|
+
## 🔒 Security Features
|
|
236
|
+
|
|
237
|
+
### Input Validation
|
|
238
|
+
```tsx
|
|
239
|
+
import { validateEmail, sanitizeInput, validateFileName } from '@umituz/web-design-system/security';
|
|
240
|
+
|
|
241
|
+
// Validate email
|
|
242
|
+
const result = validateEmail('user@example.com');
|
|
243
|
+
if (result.isValid) {
|
|
244
|
+
// Email is valid
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Sanitize user input
|
|
248
|
+
const clean = sanitizeInput(userInput);
|
|
249
|
+
|
|
250
|
+
// Validate file names
|
|
251
|
+
const fileResult = validateFileName(fileName);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Form Validation Hook
|
|
255
|
+
```tsx
|
|
256
|
+
import { useFormValidation, COMMON_RULES } from '@umituz/web-design-system/security';
|
|
257
|
+
|
|
258
|
+
const MyForm = () => {
|
|
259
|
+
const { formData, errors, updateField, validateAllFields, isFormValid } = useFormValidation(
|
|
260
|
+
{ name: '', email: '' },
|
|
261
|
+
{
|
|
262
|
+
name: COMMON_RULES.name,
|
|
263
|
+
email: COMMON_RULES.email
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<form onSubmit={handleSubmit}>
|
|
269
|
+
<input
|
|
270
|
+
value={formData.name}
|
|
271
|
+
onChange={(e) => updateField('name', e.target.value)}
|
|
272
|
+
/>
|
|
273
|
+
{errors.name && <span className="error">{errors.name}</span>}
|
|
274
|
+
</form>
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## ⚡ Performance Features
|
|
280
|
+
|
|
281
|
+
### Performance Monitoring
|
|
282
|
+
```tsx
|
|
283
|
+
import { usePerformanceMonitor } from '@umituz/web-design-system/performance';
|
|
284
|
+
|
|
285
|
+
const MyComponent = () => {
|
|
286
|
+
const { metrics, getPerformanceReport } = usePerformanceMonitor({
|
|
287
|
+
componentName: 'MyComponent',
|
|
288
|
+
trackRenders: true,
|
|
289
|
+
trackMemory: true
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return <div>Render time: {metrics.renderTime}ms</div>;
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Lazy Loading
|
|
297
|
+
```tsx
|
|
298
|
+
import { useLazyLoading, useLazyImage } from '@umituz/web-design-system/performance';
|
|
299
|
+
|
|
300
|
+
const LazyImage = () => {
|
|
301
|
+
const { targetRef, imageSrc, isLoaded } = useLazyImage('/large-image.jpg');
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<div ref={targetRef}>
|
|
305
|
+
{isLoaded ? <img src={imageSrc} alt="Lazy loaded" /> : <div>Loading...</div>}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Memory Optimization
|
|
312
|
+
```tsx
|
|
313
|
+
import { useMemoryOptimization } from '@umituz/web-design-system/performance';
|
|
314
|
+
|
|
315
|
+
const MyComponent = () => {
|
|
316
|
+
const { addEventListener, setTimeout, addCleanup, getMemoryStats } = useMemoryOptimization();
|
|
317
|
+
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
const cleanup = addEventListener(window, 'resize', handleResize);
|
|
320
|
+
const timerId = setTimeout(() => {}, 1000);
|
|
321
|
+
|
|
322
|
+
return () => {
|
|
323
|
+
cleanup();
|
|
324
|
+
// Automatic cleanup on unmount
|
|
325
|
+
};
|
|
326
|
+
}, []);
|
|
327
|
+
|
|
328
|
+
return <div>Memory stats: {getMemoryStats().totalTrackedItems} items</div>;
|
|
329
|
+
};
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## 🚨 Error Handling
|
|
333
|
+
|
|
334
|
+
### Error Boundary
|
|
335
|
+
```tsx
|
|
336
|
+
import { ErrorBoundary } from '@umituz/web-design-system/error';
|
|
337
|
+
|
|
338
|
+
<ErrorBoundary
|
|
339
|
+
level="page"
|
|
340
|
+
showDetails={true}
|
|
341
|
+
onError={(error, errorInfo) => console.error(error)}
|
|
342
|
+
>
|
|
343
|
+
<MyComponent />
|
|
344
|
+
</ErrorBoundary>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Error Display
|
|
348
|
+
```tsx
|
|
349
|
+
import { ErrorDisplay, NetworkError, ApiError } from '@umituz/web-design-system/error';
|
|
350
|
+
|
|
351
|
+
<ErrorDisplay
|
|
352
|
+
error={error}
|
|
353
|
+
title="Something went wrong"
|
|
354
|
+
severity="error"
|
|
355
|
+
showRetry={true}
|
|
356
|
+
onRetry={retryFunction}
|
|
357
|
+
/>
|
|
358
|
+
|
|
359
|
+
<NetworkError onRetry={retry} />
|
|
360
|
+
<ApiError error={apiError} onRetry={retry} />
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Suspense Wrapper
|
|
364
|
+
```tsx
|
|
365
|
+
import { SuspenseWrapper, PageSuspense } from '@umituz/web-design-system/error';
|
|
366
|
+
|
|
367
|
+
<PageSuspense loadingText="Loading page...">
|
|
368
|
+
<MyLazyComponent />
|
|
369
|
+
</PageSuspense>
|
|
370
|
+
|
|
371
|
+
<SuspenseWrapper variant="card" errorBoundaryLevel="feature">
|
|
372
|
+
<AsyncComponent />
|
|
373
|
+
</SuspenseWrapper>
|
|
374
|
+
```
|
|
375
|
+
|
|
195
376
|
## 📐 Atomic Design Principles
|
|
196
377
|
|
|
197
378
|
This package follows Atomic Design methodology:
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-design-system",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications",
|
|
5
6
|
"main": "./src/index.ts",
|
|
6
7
|
"types": "./src/index.ts",
|
|
7
8
|
"sideEffects": false,
|
|
@@ -16,6 +17,9 @@
|
|
|
16
17
|
"./constants": "./src/infrastructure/constants/index.ts",
|
|
17
18
|
"./tokens": "./src/domain/tokens/index.ts",
|
|
18
19
|
"./types": "./src/domain/types/index.ts",
|
|
20
|
+
"./security": "./src/infrastructure/security/index.ts",
|
|
21
|
+
"./performance": "./src/infrastructure/performance/index.ts",
|
|
22
|
+
"./error": "./src/infrastructure/error/index.ts",
|
|
19
23
|
"./package.json": "./package.json"
|
|
20
24
|
},
|
|
21
25
|
"scripts": {
|
|
@@ -34,23 +38,32 @@
|
|
|
34
38
|
"atoms",
|
|
35
39
|
"molecules",
|
|
36
40
|
"organisms",
|
|
37
|
-
"tailwind"
|
|
41
|
+
"tailwind",
|
|
42
|
+
"security",
|
|
43
|
+
"performance",
|
|
44
|
+
"error-handling",
|
|
45
|
+
"validation",
|
|
46
|
+
"optimization"
|
|
38
47
|
],
|
|
39
48
|
"author": "umituz",
|
|
40
49
|
"license": "MIT",
|
|
41
50
|
"repository": {
|
|
42
51
|
"type": "git",
|
|
43
|
-
"url": "https://github.com/umituz/web-design-system"
|
|
52
|
+
"url": "git+https://github.com/umituz/web-design-system.git"
|
|
44
53
|
},
|
|
45
54
|
"peerDependencies": {
|
|
46
55
|
"react": ">=18.0.0",
|
|
47
|
-
"react-dom": ">=18.0.0"
|
|
56
|
+
"react-dom": ">=18.0.0",
|
|
57
|
+
"clsx": ">=2.0.0",
|
|
58
|
+
"tailwind-merge": ">=2.0.0"
|
|
48
59
|
},
|
|
49
60
|
"devDependencies": {
|
|
50
61
|
"@types/react": "^18.0.0",
|
|
51
62
|
"@types/react-dom": "^18.0.0",
|
|
63
|
+
"clsx": "^2.1.1",
|
|
52
64
|
"react": "^18.0.0",
|
|
53
65
|
"react-dom": "^18.0.0",
|
|
66
|
+
"tailwind-merge": "^3.5.0",
|
|
54
67
|
"typescript": "~5.9.2"
|
|
55
68
|
},
|
|
56
69
|
"publishConfig": {
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Type Declarations
|
|
3
|
+
* @description Global type definitions for the design system
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface ImportMetaEnv {
|
|
8
|
+
readonly DEV: boolean;
|
|
9
|
+
readonly MODE: string;
|
|
10
|
+
readonly BASE_URL: string;
|
|
11
|
+
readonly PROD: boolean;
|
|
12
|
+
readonly SSR: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ImportMeta {
|
|
16
|
+
readonly env: ImportMetaEnv;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export {};
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,9 @@ export * from './domain/tokens';
|
|
|
21
21
|
export * from './domain/types';
|
|
22
22
|
export * from './infrastructure/utils';
|
|
23
23
|
export * from './infrastructure/constants';
|
|
24
|
+
export * from './infrastructure/security';
|
|
25
|
+
export * from './infrastructure/performance';
|
|
26
|
+
export * from './infrastructure/error';
|
|
24
27
|
export * from './presentation/atoms';
|
|
25
28
|
export * from './presentation/molecules';
|
|
26
29
|
export * from './presentation/organisms';
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import React, { Component, ErrorInfo, ReactNode, memo } from "react";
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
fallback?: ReactNode;
|
|
6
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
7
|
+
showDetails?: boolean;
|
|
8
|
+
level?: 'page' | 'component' | 'feature';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface State {
|
|
12
|
+
hasError: boolean;
|
|
13
|
+
error?: Error;
|
|
14
|
+
errorInfo?: ErrorInfo;
|
|
15
|
+
errorId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ErrorDetails = memo(({ error, errorInfo, showDetails }: {
|
|
19
|
+
error: Error;
|
|
20
|
+
errorInfo?: ErrorInfo;
|
|
21
|
+
showDetails: boolean;
|
|
22
|
+
}) => {
|
|
23
|
+
if (!showDetails || !import.meta.env.DEV) return null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div style={{ padding: '16px', background: '#f5f5f5', borderRadius: '8px', marginTop: '16px' }}>
|
|
27
|
+
<h4 style={{ fontWeight: 600, fontSize: '14px', marginBottom: '8px' }}>
|
|
28
|
+
🐛 Error Details (Development)
|
|
29
|
+
</h4>
|
|
30
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
31
|
+
<div>
|
|
32
|
+
<p style={{ fontSize: '12px', fontWeight: 500, color: '#666' }}>Message:</p>
|
|
33
|
+
<pre style={{ fontSize: '12px', color: '#dc2626', overflow: 'auto' }}>{error.message}</pre>
|
|
34
|
+
</div>
|
|
35
|
+
{error.stack && (
|
|
36
|
+
<div>
|
|
37
|
+
<p style={{ fontSize: '12px', fontWeight: 500, color: '#666' }}>Stack:</p>
|
|
38
|
+
<pre style={{ fontSize: '12px', color: '#666', overflow: 'auto', maxHeight: '128px' }}>
|
|
39
|
+
{error.stack}
|
|
40
|
+
</pre>
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
{errorInfo?.componentStack && (
|
|
44
|
+
<div>
|
|
45
|
+
<p style={{ fontSize: '12px', fontWeight: 500, color: '#666' }}>Component Stack:</p>
|
|
46
|
+
<pre style={{ fontSize: '12px', color: '#666', overflow: 'auto', maxHeight: '128px' }}>
|
|
47
|
+
{errorInfo.componentStack}
|
|
48
|
+
</pre>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
ErrorDetails.displayName = 'ErrorDetails';
|
|
57
|
+
|
|
58
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
59
|
+
constructor(props: Props) {
|
|
60
|
+
super(props);
|
|
61
|
+
this.state = { hasError: false };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static getDerivedStateFromError(error: Error): Partial<State> {
|
|
65
|
+
return {
|
|
66
|
+
hasError: true,
|
|
67
|
+
error,
|
|
68
|
+
errorId: `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
73
|
+
this.setState({ error, errorInfo });
|
|
74
|
+
|
|
75
|
+
this.props.onError?.(error, errorInfo);
|
|
76
|
+
|
|
77
|
+
if (import.meta.env.DEV) {
|
|
78
|
+
console.group('🚨 ErrorBoundary caught an error');
|
|
79
|
+
console.error('Error:', error);
|
|
80
|
+
console.error('Error Info:', errorInfo);
|
|
81
|
+
console.groupEnd();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private handleReset = () => {
|
|
86
|
+
this.setState({ hasError: false, error: undefined, errorInfo: undefined, errorId: undefined });
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
private handleGoHome = () => {
|
|
90
|
+
window.location.href = '/';
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
private getLevelConfig = () => {
|
|
94
|
+
const { level = 'component' } = this.props;
|
|
95
|
+
|
|
96
|
+
switch (level) {
|
|
97
|
+
case 'page':
|
|
98
|
+
return {
|
|
99
|
+
title: 'Page Error',
|
|
100
|
+
description: 'This page encountered an unexpected error.',
|
|
101
|
+
showHomeButton: true,
|
|
102
|
+
};
|
|
103
|
+
case 'feature':
|
|
104
|
+
return {
|
|
105
|
+
title: 'Feature Error',
|
|
106
|
+
description: 'This feature is temporarily unavailable due to an error.',
|
|
107
|
+
showHomeButton: false,
|
|
108
|
+
};
|
|
109
|
+
default:
|
|
110
|
+
return {
|
|
111
|
+
title: 'Component Error',
|
|
112
|
+
description: 'A component on this page encountered an error.',
|
|
113
|
+
showHomeButton: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
render() {
|
|
119
|
+
if (this.state.hasError) {
|
|
120
|
+
if (this.props.fallback) {
|
|
121
|
+
return this.props.fallback;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const config = this.getLevelConfig();
|
|
125
|
+
const { showDetails = false } = this.props;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div style={{
|
|
129
|
+
minHeight: '100vh',
|
|
130
|
+
display: 'flex',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
justifyContent: 'center',
|
|
133
|
+
padding: '16px',
|
|
134
|
+
background: 'linear-gradient(to bottom right, #fef2f2, #fff7ed)'
|
|
135
|
+
}}>
|
|
136
|
+
<div style={{
|
|
137
|
+
width: '100%',
|
|
138
|
+
maxWidth: '672px',
|
|
139
|
+
background: 'white',
|
|
140
|
+
borderRadius: '8px',
|
|
141
|
+
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)',
|
|
142
|
+
overflow: 'hidden'
|
|
143
|
+
}}>
|
|
144
|
+
<div style={{ padding: '24px' }}>
|
|
145
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
146
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
147
|
+
<div style={{
|
|
148
|
+
width: '48px',
|
|
149
|
+
height: '48px',
|
|
150
|
+
background: '#fecaca',
|
|
151
|
+
borderRadius: '50%',
|
|
152
|
+
display: 'flex',
|
|
153
|
+
alignItems: 'center',
|
|
154
|
+
justifyContent: 'center'
|
|
155
|
+
}}>
|
|
156
|
+
<span style={{ fontSize: '24px' }}>⚠️</span>
|
|
157
|
+
</div>
|
|
158
|
+
<div>
|
|
159
|
+
<h2 style={{ color: '#dc2626', margin: 0, fontSize: '20px', fontWeight: 600 }}>
|
|
160
|
+
{config.title}
|
|
161
|
+
</h2>
|
|
162
|
+
<p style={{ color: '#666', margin: 0, fontSize: '14px' }}>
|
|
163
|
+
{config.description}
|
|
164
|
+
</p>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
{this.state.errorId && (
|
|
168
|
+
<span style={{
|
|
169
|
+
background: '#dc2626',
|
|
170
|
+
color: 'white',
|
|
171
|
+
padding: '4px 8px',
|
|
172
|
+
borderRadius: '4px',
|
|
173
|
+
fontSize: '12px',
|
|
174
|
+
fontWeight: 500
|
|
175
|
+
}}>
|
|
176
|
+
ID: {this.state.errorId.slice(-8)}
|
|
177
|
+
</span>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<div style={{ padding: '0 24px 24px', display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
|
183
|
+
{this.state.error && (
|
|
184
|
+
<ErrorDetails
|
|
185
|
+
error={this.state.error}
|
|
186
|
+
errorInfo={this.state.errorInfo}
|
|
187
|
+
showDetails={showDetails}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
|
192
|
+
<button
|
|
193
|
+
onClick={this.handleReset}
|
|
194
|
+
style={{
|
|
195
|
+
padding: '8px 16px',
|
|
196
|
+
background: 'white',
|
|
197
|
+
border: '1px solid #d1d5db',
|
|
198
|
+
borderRadius: '6px',
|
|
199
|
+
fontSize: '14px',
|
|
200
|
+
fontWeight: 500,
|
|
201
|
+
cursor: 'pointer',
|
|
202
|
+
display: 'flex',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
gap: '8px'
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
🔄 Try Again
|
|
208
|
+
</button>
|
|
209
|
+
<button
|
|
210
|
+
onClick={() => window.location.reload()}
|
|
211
|
+
style={{
|
|
212
|
+
padding: '8px 16px',
|
|
213
|
+
background: '#3b82f6',
|
|
214
|
+
color: 'white',
|
|
215
|
+
border: 'none',
|
|
216
|
+
borderRadius: '6px',
|
|
217
|
+
fontSize: '14px',
|
|
218
|
+
fontWeight: 500,
|
|
219
|
+
cursor: 'pointer',
|
|
220
|
+
display: 'flex',
|
|
221
|
+
alignItems: 'center',
|
|
222
|
+
gap: '8px'
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
🔄 Refresh Page
|
|
226
|
+
</button>
|
|
227
|
+
{config.showHomeButton && (
|
|
228
|
+
<button
|
|
229
|
+
onClick={this.handleGoHome}
|
|
230
|
+
style={{
|
|
231
|
+
padding: '8px 16px',
|
|
232
|
+
background: '#6b7280',
|
|
233
|
+
color: 'white',
|
|
234
|
+
border: 'none',
|
|
235
|
+
borderRadius: '6px',
|
|
236
|
+
fontSize: '14px',
|
|
237
|
+
fontWeight: 500,
|
|
238
|
+
cursor: 'pointer',
|
|
239
|
+
display: 'flex',
|
|
240
|
+
alignItems: 'center',
|
|
241
|
+
gap: '8px'
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
🏠 Go Home
|
|
245
|
+
</button>
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return this.props.children;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export default ErrorBoundary;
|