@djangocfg/layouts 2.0.4 → 2.0.6

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.
Files changed (64) hide show
  1. package/README.md +138 -20
  2. package/package.json +20 -5
  3. package/src/components/RedirectPage/RedirectPage.tsx +70 -0
  4. package/src/components/RedirectPage/index.ts +7 -0
  5. package/src/components/core/index.ts +10 -0
  6. package/src/components/errors/ErrorLayout.tsx +228 -0
  7. package/src/components/errors/errorConfig.ts +118 -0
  8. package/src/components/errors/index.ts +10 -0
  9. package/src/components/index.ts +13 -5
  10. package/src/contexts/LeadsContext.tsx +156 -0
  11. package/src/contexts/NewsletterContext.tsx +263 -0
  12. package/src/contexts/SupportContext.tsx +256 -0
  13. package/src/contexts/index.ts +59 -0
  14. package/src/contexts/knowbase/ChatContext.tsx +174 -0
  15. package/src/contexts/knowbase/DocumentsContext.tsx +304 -0
  16. package/src/contexts/knowbase/SessionsContext.tsx +174 -0
  17. package/src/contexts/knowbase/index.ts +61 -0
  18. package/src/contexts/payments/BalancesContext.tsx +65 -0
  19. package/src/contexts/payments/CurrenciesContext.tsx +66 -0
  20. package/src/contexts/payments/OverviewContext.tsx +174 -0
  21. package/src/contexts/payments/PaymentsContext.tsx +132 -0
  22. package/src/contexts/payments/README.md +201 -0
  23. package/src/contexts/payments/RootPaymentsContext.tsx +68 -0
  24. package/src/contexts/payments/index.ts +50 -0
  25. package/src/index.ts +8 -0
  26. package/src/layouts/AppLayout/AppLayout.tsx +24 -14
  27. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +1 -1
  28. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +1 -1
  29. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +1 -1
  30. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +1 -1
  31. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +1 -1
  32. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +1 -1
  33. package/src/layouts/SupportLayout/SupportLayout.tsx +1 -1
  34. package/src/layouts/SupportLayout/components/TicketCard.tsx +1 -1
  35. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +1 -1
  36. package/src/layouts/SupportLayout/index.ts +2 -0
  37. package/src/layouts/SupportLayout/types.ts +2 -4
  38. package/src/pages/index.ts +6 -0
  39. package/src/pages/legal/LegalPage.tsx +85 -0
  40. package/src/pages/legal/configs.ts +131 -0
  41. package/src/pages/legal/index.ts +24 -0
  42. package/src/pages/legal/pages.tsx +58 -0
  43. package/src/pages/legal/types.ts +15 -0
  44. package/src/snippets/Chat/ChatWidget.tsx +1 -1
  45. package/src/snippets/Chat/components/SessionList.tsx +1 -1
  46. package/src/snippets/Chat/index.tsx +1 -1
  47. package/src/snippets/Chat/types.ts +7 -5
  48. package/src/snippets/ContactForm/ContactForm.tsx +20 -8
  49. package/src/utils/index.ts +1 -0
  50. package/src/utils/og-image.ts +169 -0
  51. /package/src/components/{JsonLd.tsx → core/JsonLd.tsx} +0 -0
  52. /package/src/components/{LucideIcon.tsx → core/LucideIcon.tsx} +0 -0
  53. /package/src/components/{PageProgress.tsx → core/PageProgress.tsx} +0 -0
  54. /package/src/components/{Suspense.tsx → core/Suspense.tsx} +0 -0
  55. /package/src/components/{ErrorBoundary.tsx → errors/ErrorBoundary.tsx} +0 -0
  56. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/README.md +0 -0
  57. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/components/ErrorButtons.tsx +0 -0
  58. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/components/ErrorToast.tsx +0 -0
  59. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/hooks.ts +0 -0
  60. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/index.ts +0 -0
  61. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/providers/ErrorTrackingProvider.tsx +0 -0
  62. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/types.ts +0 -0
  63. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/utils/curl-generator.ts +0 -0
  64. /package/src/components/{ErrorsTracker → errors/ErrorsTracker}/utils/formatters.ts +0 -0
package/README.md CHANGED
@@ -254,41 +254,89 @@ import { ChatWidget, ChatUIProvider, useChatUI } from '@djangocfg/layouts/snippe
254
254
 
255
255
  ## Components
256
256
 
257
- Utility components for common use cases.
257
+ Utility components organized by category.
258
+
259
+ ### Core Components
258
260
 
259
261
  ```tsx
260
262
  import {
261
- ErrorBoundary,
262
- PageProgress,
263
263
  JsonLd,
264
- LucideIcon
265
- } from '@djangocfg/layouts/components';
264
+ LucideIcon,
265
+ PageProgress,
266
+ Suspense
267
+ } from '@djangocfg/layouts/components/core';
266
268
  ```
267
269
 
268
270
  | Component | Description |
269
271
  |-----------|-------------|
270
- | `ErrorBoundary` | React error boundary component |
271
- | `PageProgress` | Page loading progress indicator |
272
272
  | `JsonLd` | JSON-LD structured data component |
273
273
  | `LucideIcon` | Lucide icon wrapper component |
274
+ | `PageProgress` | Page loading progress indicator |
275
+ | `Suspense` | Suspense wrapper component |
274
276
 
275
- ## Validation
276
-
277
- Error tracking and validation utilities.
277
+ ### Error Components
278
278
 
279
279
  ```tsx
280
280
  import {
281
- ErrorTrackingProvider,
282
- useErrors,
283
- ErrorButtons,
284
- ErrorToast
285
- } from '@djangocfg/layouts/validation';
281
+ ErrorBoundary,
282
+ ErrorLayout,
283
+ getErrorContent,
284
+ ERROR_CODES
285
+ } from '@djangocfg/layouts/components/errors';
286
+ ```
287
+
288
+ | Component | Description |
289
+ |-----------|-------------|
290
+ | `ErrorBoundary` | React error boundary component |
291
+ | `ErrorLayout` | Reusable error page layout (404, 500, etc.) |
292
+ | `getErrorContent` | Get error content by status code |
293
+ | `ERROR_CODES` | Common HTTP error code constants |
294
+
295
+ **ErrorLayout Usage:**
296
+
297
+ ```tsx
298
+ // app/not-found.tsx
299
+ import { ErrorLayout } from '@djangocfg/layouts/components/errors';
300
+
301
+ export default function NotFound() {
302
+ return <ErrorLayout code={404} supportEmail="support@example.com" />;
303
+ }
304
+
305
+ // app/error.tsx
306
+ 'use client';
307
+ import { ErrorLayout } from '@djangocfg/layouts/components/errors';
308
+
309
+ export default function Error({ error, reset }) {
310
+ return <ErrorLayout code={500} supportEmail="support@example.com" />;
311
+ }
312
+ ```
313
+
314
+ ### Redirect Component
315
+
316
+ ```tsx
317
+ import { RedirectPage } from '@djangocfg/layouts/components/RedirectPage';
318
+
319
+ // app/page.tsx
320
+ export default function Page() {
321
+ return (
322
+ <RedirectPage
323
+ authenticatedPath="/dashboard"
324
+ unauthenticatedPath="/auth"
325
+ loadingText="Loading..."
326
+ />
327
+ );
328
+ }
286
329
  ```
287
330
 
288
331
  ### Error Tracking
289
332
 
290
333
  ```tsx
291
- import { ErrorTrackingProvider, useErrors } from '@djangocfg/layouts/validation';
334
+ import {
335
+ ErrorTrackingProvider,
336
+ useErrors,
337
+ ErrorButtons,
338
+ ErrorToast
339
+ } from '@djangocfg/layouts/components/errors/ErrorsTracker';
292
340
 
293
341
  <ErrorTrackingProvider>
294
342
  <YourApp />
@@ -298,6 +346,76 @@ import { ErrorTrackingProvider, useErrors } from '@djangocfg/layouts/validation'
298
346
  const { addError, clearErrors, errors } = useErrors();
299
347
  ```
300
348
 
349
+ ### Update Notifier
350
+
351
+ ```tsx
352
+ import { UpdateNotifier } from '@djangocfg/layouts/components/UpdateNotifier';
353
+
354
+ <UpdateNotifier />
355
+ ```
356
+
357
+ ## Pages
358
+
359
+ Ready-to-use page components.
360
+
361
+ ### Legal Pages
362
+
363
+ Pre-built legal page components with default configurations.
364
+
365
+ ```tsx
366
+ import {
367
+ PrivacyPage,
368
+ TermsPage,
369
+ CookiesPage,
370
+ SecurityPage
371
+ } from '@djangocfg/layouts/pages/legal';
372
+
373
+ // app/legal/privacy/page.tsx
374
+ export default PrivacyPage;
375
+
376
+ // Or customize
377
+ import { PrivacyPage, privacyConfig } from '@djangocfg/layouts/pages/legal';
378
+
379
+ export default function CustomPrivacy() {
380
+ return <PrivacyPage config={{
381
+ ...privacyConfig,
382
+ lastUpdated: '2024-01-01',
383
+ }} />;
384
+ }
385
+ ```
386
+
387
+ | Page | Description |
388
+ |------|-------------|
389
+ | `PrivacyPage` | Privacy policy page |
390
+ | `TermsPage` | Terms of service page |
391
+ | `CookiesPage` | Cookie policy page |
392
+ | `SecurityPage` | Security policy page |
393
+
394
+ ## Utils
395
+
396
+ Utility functions and helpers.
397
+
398
+ ```tsx
399
+ import {
400
+ generateOgImageUrl,
401
+ getAbsoluteOgImageUrl,
402
+ createOgImageUrlBuilder
403
+ } from '@djangocfg/layouts/utils/og-image';
404
+
405
+ // Generate OG image URL
406
+ const ogUrl = generateOgImageUrl('/api/og', {
407
+ title: 'My Page',
408
+ description: 'Page description',
409
+ siteName: 'My Site',
410
+ });
411
+ ```
412
+
413
+ | Utility | Description |
414
+ |---------|-------------|
415
+ | `generateOgImageUrl` | Generate OG image URL with base64 encoding |
416
+ | `getAbsoluteOgImageUrl` | Get absolute OG image URL |
417
+ | `createOgImageUrlBuilder` | Create OG image URL builder with defaults |
418
+
301
419
  ## Exports
302
420
 
303
421
  | Path | Content |
@@ -308,10 +426,10 @@ const { addError, clearErrors, errors } = useErrors();
308
426
  | `@djangocfg/layouts/auth/context` | Auth context only |
309
427
  | `@djangocfg/layouts/auth/hooks` | Auth hooks only |
310
428
  | `@djangocfg/layouts/snippets` | Reusable components + Analytics |
311
- | `@djangocfg/layouts/components` | Utility components |
312
- | `@djangocfg/layouts/validation` | Validation & error tracking |
313
- | `@djangocfg/layouts/utils` | Utilities |
314
- | `@djangocfg/layouts/types` | TypeScript types |
429
+ | `@djangocfg/layouts/components` | All utility components |
430
+ | `@djangocfg/layouts/pages` | Page components (legal pages) |
431
+ | `@djangocfg/layouts/pages/legal` | Legal page components |
432
+ | `@djangocfg/layouts/utils` | Utilities (og-image, logger) |
315
433
  | `@djangocfg/layouts/styles` | CSS |
316
434
  | `@djangocfg/layouts/styles/dashboard` | Dashboard-specific CSS |
317
435
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -64,6 +64,21 @@
64
64
  "import": "./src/utils/index.ts",
65
65
  "default": "./src/utils/index.ts"
66
66
  },
67
+ "./components": {
68
+ "types": "./src/components/index.ts",
69
+ "import": "./src/components/index.ts",
70
+ "default": "./src/components/index.ts"
71
+ },
72
+ "./pages": {
73
+ "types": "./src/pages/index.ts",
74
+ "import": "./src/pages/index.ts",
75
+ "default": "./src/pages/index.ts"
76
+ },
77
+ "./contexts": {
78
+ "types": "./src/contexts/index.ts",
79
+ "import": "./src/contexts/index.ts",
80
+ "default": "./src/contexts/index.ts"
81
+ },
67
82
  "./styles": "./src/styles/index.css",
68
83
  "./styles/dashboard": "./src/styles/dashboard.css"
69
84
  },
@@ -77,9 +92,9 @@
77
92
  "check": "tsc --noEmit"
78
93
  },
79
94
  "peerDependencies": {
80
- "@djangocfg/api": "^1.4.34",
81
- "@djangocfg/centrifugo": "^1.4.34",
82
- "@djangocfg/ui": "^1.4.34",
95
+ "@djangocfg/api": "^1.4.36",
96
+ "@djangocfg/centrifugo": "^1.4.36",
97
+ "@djangocfg/ui": "^1.4.36",
83
98
  "@hookform/resolvers": "^5.2.0",
84
99
  "consola": "^3.4.2",
85
100
  "lucide-react": "^0.468.0",
@@ -98,7 +113,7 @@
98
113
  "react-ga4": "^2.1.0"
99
114
  },
100
115
  "devDependencies": {
101
- "@djangocfg/typescript-config": "^1.4.34",
116
+ "@djangocfg/typescript-config": "^1.4.36",
102
117
  "@types/node": "^24.7.2",
103
118
  "@types/react": "19.2.2",
104
119
  "@types/react-dom": "19.2.1",
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+ import { useAuth } from '../../auth';
5
+ import { useRouter } from '@djangocfg/ui/hooks';
6
+ import { Preloader } from '@djangocfg/ui/components';
7
+
8
+ export interface RedirectPageProps {
9
+ /**
10
+ * Path to redirect to when user is authenticated
11
+ * @default '/private'
12
+ */
13
+ authenticatedPath?: string;
14
+ /**
15
+ * Path to redirect to when user is not authenticated
16
+ * @default '/auth'
17
+ */
18
+ unauthenticatedPath?: string;
19
+ /**
20
+ * Custom loading text
21
+ * @default 'Loading...'
22
+ */
23
+ loadingText?: string;
24
+ }
25
+
26
+ /**
27
+ * RedirectPage - Root page component that handles authentication redirect
28
+ *
29
+ * Redirects authenticated users to authenticatedPath, otherwise to unauthenticatedPath
30
+ *
31
+ * Usage:
32
+ * ```tsx
33
+ * // app/page.tsx
34
+ * import { RedirectPage } from '@djangocfg/layouts/components';
35
+ *
36
+ * export default function Page() {
37
+ * return (
38
+ * <RedirectPage
39
+ * authenticatedPath="/private"
40
+ * unauthenticatedPath="/auth"
41
+ * />
42
+ * );
43
+ * }
44
+ * ```
45
+ */
46
+ export function RedirectPage({
47
+ authenticatedPath = '/private',
48
+ unauthenticatedPath = '/auth',
49
+ loadingText = 'Loading...',
50
+ }: RedirectPageProps) {
51
+ const { isAuthenticated } = useAuth();
52
+ const router = useRouter();
53
+
54
+ useEffect(() => {
55
+ if (!isAuthenticated) {
56
+ router.push(unauthenticatedPath);
57
+ } else {
58
+ router.push(authenticatedPath);
59
+ }
60
+ }, [isAuthenticated, router, authenticatedPath, unauthenticatedPath]);
61
+
62
+ return (
63
+ <Preloader
64
+ variant="fullscreen"
65
+ text={loadingText}
66
+ size="lg"
67
+ />
68
+ );
69
+ }
70
+
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Redirect component exports
3
+ */
4
+
5
+ export { RedirectPage } from './RedirectPage';
6
+ export type { RedirectPageProps } from './RedirectPage';
7
+
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Core components exports
3
+ */
4
+
5
+ export { JsonLd } from './JsonLd';
6
+ export { LucideIcon } from './LucideIcon';
7
+ export type { LucideIconProps } from './LucideIcon';
8
+ export { PageProgress } from './PageProgress';
9
+ export { Suspense } from './Suspense';
10
+
@@ -0,0 +1,228 @@
1
+ /**
2
+ * ErrorLayout - Universal Error Display
3
+ *
4
+ * Minimalist error page with customizable content
5
+ * Works with Next.js error pages (404.tsx, 500.tsx, error.tsx)
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * // app/not-found.tsx
10
+ * import { ErrorLayout } from '@djangocfg/layouts/components';
11
+ *
12
+ * export default function NotFound() {
13
+ * return <ErrorLayout code={404} supportEmail={settings.contact.email} />;
14
+ * }
15
+ * ```
16
+ */
17
+
18
+ 'use client';
19
+
20
+ import React from 'react';
21
+ import { Button } from '@djangocfg/ui/components';
22
+ import { getErrorContent } from './errorConfig';
23
+
24
+ export interface ErrorLayoutProps {
25
+ /** Error code (e.g., "404", "500", "403") - if provided, auto-configures title/description/icon */
26
+ code?: string | number;
27
+ /** Error title (auto-generated from code if not provided) */
28
+ title?: string;
29
+ /** Error description (auto-generated from code if not provided) */
30
+ description?: string;
31
+ /** Custom action buttons */
32
+ actions?: React.ReactNode;
33
+ /** Show default actions (back, home) */
34
+ showDefaultActions?: boolean;
35
+ /** Custom illustration/icon (auto-generated from code if not provided) */
36
+ illustration?: React.ReactNode;
37
+ /** Support email for contact link */
38
+ supportEmail?: string;
39
+ }
40
+
41
+ // Local function to select the icon based on the code.
42
+ // This is safe as it's defined and used inside a Client Component.
43
+ function getErrorIcon(code?: string | number): React.ReactNode {
44
+ const c = code ? String(code) : '';
45
+
46
+ // NOTE: You can replace these SVG paths with imported Lucid Icons
47
+ // (e.g., <AlertTriangle />) if you prefer.
48
+ switch (c) {
49
+ case '404':
50
+ return (
51
+ <svg
52
+ className="w-24 h-24 mx-auto text-muted-foreground/50"
53
+ fill="none"
54
+ stroke="currentColor"
55
+ viewBox="0 0 24 24"
56
+ aria-hidden="true"
57
+ >
58
+ {/* Missing Page Icon */}
59
+ <path
60
+ strokeLinecap="round"
61
+ strokeLinejoin="round"
62
+ strokeWidth={1.5}
63
+ d="M9.343 3.07a7.227 7.227 0 0111.558 0c.806.515 1.393 1.39 1.393 2.37v6.636c0 .98-.587 1.855-1.393 2.37a7.227 7.227 0 01-11.558 0c-.806-.515-1.393-1.39-1.393-2.37V5.44c0-.98.587-1.855 1.393-2.37zM12 13a1 1 0 100-2 1 1 0 000 2z"
64
+ />
65
+ </svg>
66
+ );
67
+ case '500':
68
+ return (
69
+ <svg
70
+ className="w-24 h-24 mx-auto text-muted-foreground/50"
71
+ fill="none"
72
+ stroke="currentColor"
73
+ viewBox="0 0 24 24"
74
+ aria-hidden="true"
75
+ >
76
+ {/* Server Error Icon */}
77
+ <path
78
+ strokeLinecap="round"
79
+ strokeLinejoin="round"
80
+ strokeWidth={1.5}
81
+ d="M18.364 18.364A9 9 0 005.636 5.636m12.728 0l-6.849 6.849m0 0l-6.849-6.849m6.849 6.849V21m0 0h7.5M12 21v-7.5M7.5 21H3m7.5 0h7.5M3 18V9a4.5 4.5 0 014.5-4.5h9A4.5 4.5 0 0121 9v9a4.5 4.5 0 01-4.5 4.5h-9A4.5 4.5 0 013 18z"
82
+ />
83
+ </svg>
84
+ );
85
+ case '403':
86
+ return (
87
+ <svg
88
+ className="w-24 h-24 mx-auto text-muted-foreground/50"
89
+ fill="none"
90
+ stroke="currentColor"
91
+ viewBox="0 0 24 24"
92
+ aria-hidden="true"
93
+ >
94
+ {/* Forbidden Icon */}
95
+ <path
96
+ strokeLinecap="round"
97
+ strokeLinejoin="round"
98
+ strokeWidth={1.5}
99
+ d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
100
+ />
101
+ </svg>
102
+ );
103
+ default:
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * ErrorLayout Component
110
+ */
111
+ export function ErrorLayout({
112
+ code,
113
+ title,
114
+ description,
115
+ actions,
116
+ showDefaultActions = true,
117
+ illustration,
118
+ supportEmail = 'support@example.com',
119
+ }: ErrorLayoutProps) {
120
+
121
+ // Get content (Title/Description) from config. Note: Illustration check removed.
122
+ // The function getErrorContent MUST NOT return React components/functions.
123
+ const autoContent = code && (!title || !description)
124
+ ? getErrorContent(code)
125
+ : null;
126
+
127
+ // Fallback to auto-generated values
128
+ const finalTitle = title || autoContent?.title || 'Error';
129
+ const finalDescription = description || autoContent?.description;
130
+
131
+ // ILLUSTRATION FIX: Use passed prop OR compute the icon locally using getErrorIcon.
132
+ const finalIllustration = illustration ?? getErrorIcon(code);
133
+
134
+
135
+ const handleGoBack = () => {
136
+ if (document.referrer && document.referrer !== window.location.href) {
137
+ window.location.href = document.referrer;
138
+ } else if (window.history.length > 1) {
139
+ window.history.back();
140
+ } else {
141
+ window.location.href = '/';
142
+ }
143
+ };
144
+
145
+ const handleGoHome = () => {
146
+ window.location.href = '/';
147
+ };
148
+
149
+ return (
150
+ <div className="min-h-screen flex items-center justify-center px-4 bg-background">
151
+ <div className="max-w-2xl w-full text-center space-y-8">
152
+ {/* Error Code */}
153
+ {code && (
154
+ <div className="relative">
155
+ <h1
156
+ className="text-[12rem] font-bold leading-none text-muted/20 select-none"
157
+ aria-hidden="true"
158
+ >
159
+ {code}
160
+ </h1>
161
+ </div>
162
+ )}
163
+
164
+ {/* Illustration */}
165
+ {finalIllustration && (
166
+ <div className="flex justify-center py-8">
167
+ {finalIllustration}
168
+ </div>
169
+ )}
170
+
171
+ {/* Error Content */}
172
+ <div className="space-y-4">
173
+ <h2 className="text-4xl font-bold text-foreground">
174
+ {finalTitle}
175
+ </h2>
176
+
177
+ {finalDescription && (
178
+ <p className="text-lg text-muted-foreground max-w-md mx-auto">
179
+ {finalDescription}
180
+ </p>
181
+ )}
182
+ </div>
183
+
184
+ {/* Actions */}
185
+ <div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
186
+ {/* Custom actions */}
187
+ {actions}
188
+
189
+ {/* Default actions */}
190
+ {showDefaultActions && !actions && (
191
+ <>
192
+ <Button
193
+ variant="outline"
194
+ size="lg"
195
+ onClick={handleGoBack}
196
+ style={{ minWidth: '140px', padding: '12px 32px' }}
197
+ >
198
+ Go Back
199
+ </Button>
200
+ <Button
201
+ variant="default"
202
+ size="lg"
203
+ onClick={handleGoHome}
204
+ style={{ minWidth: '140px', padding: '12px 32px' }}
205
+ >
206
+ Go Home
207
+ </Button>
208
+ </>
209
+ )}
210
+ </div>
211
+
212
+ {/* Additional Info */}
213
+ <div className="pt-8 text-sm text-muted-foreground">
214
+ <p>
215
+ Need help? Contact{' '}
216
+ <a
217
+ href={`mailto:${supportEmail}`}
218
+ className="text-primary hover:underline"
219
+ >
220
+ support
221
+ </a>
222
+ </p>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ );
227
+ }
228
+
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Universal Error Configuration
3
+ *
4
+ * Provides standard error content for common HTTP status codes
5
+ * Use this to maintain consistency across error pages
6
+ *
7
+ * NOTE: Only returns primitives (title, description) - NO React components
8
+ * This ensures safe serialization during prerendering
9
+ */
10
+
11
+ export interface ErrorContent {
12
+ title: string;
13
+ description: string;
14
+ }
15
+
16
+ /**
17
+ * Get standardized error content based on status code
18
+ *
19
+ * @param statusCode - HTTP status code or custom error type
20
+ * @returns Error content configuration (title and description only)
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * const { title, description } = getErrorContent(404);
25
+ * <ErrorLayout title={title} description={description} code={404} />
26
+ * ```
27
+ */
28
+ export function getErrorContent(statusCode?: number | string): ErrorContent {
29
+ const code = typeof statusCode === 'string' ? parseInt(statusCode, 10) : statusCode;
30
+
31
+ switch (code) {
32
+ // 400 Bad Request
33
+ case 400:
34
+ return {
35
+ title: 'Bad Request',
36
+ description: 'The request could not be understood. Please check your input and try again.',
37
+ };
38
+
39
+ // 401 Unauthorized
40
+ case 401:
41
+ return {
42
+ title: 'Authentication Required',
43
+ description: 'You need to sign in to access this page.',
44
+ };
45
+
46
+ // 403 Forbidden
47
+ case 403:
48
+ return {
49
+ title: 'Access Denied',
50
+ description: "You don't have permission to access this resource.",
51
+ };
52
+
53
+ // 404 Not Found
54
+ case 404:
55
+ return {
56
+ title: 'Page Not Found',
57
+ description: "The page you're looking for doesn't exist or has been moved.",
58
+ };
59
+
60
+ // 408 Request Timeout
61
+ case 408:
62
+ return {
63
+ title: 'Request Timeout',
64
+ description: 'The request took too long to process. Please try again.',
65
+ };
66
+
67
+ // 500 Internal Server Error
68
+ case 500:
69
+ return {
70
+ title: 'Server Error',
71
+ description: "Something went wrong on our end. We're working to fix it.",
72
+ };
73
+
74
+ // 502 Bad Gateway
75
+ case 502:
76
+ return {
77
+ title: 'Bad Gateway',
78
+ description: 'The server received an invalid response. Please try again later.',
79
+ };
80
+
81
+ // 503 Service Unavailable
82
+ case 503:
83
+ return {
84
+ title: 'Service Unavailable',
85
+ description: 'The service is temporarily unavailable. Please try again later.',
86
+ };
87
+
88
+ // 504 Gateway Timeout
89
+ case 504:
90
+ return {
91
+ title: 'Gateway Timeout',
92
+ description: 'The server took too long to respond. Please try again.',
93
+ };
94
+
95
+ // Default / Unknown Error
96
+ default:
97
+ return {
98
+ title: 'Something Went Wrong',
99
+ description: 'An unexpected error occurred. Please try again or contact support.',
100
+ };
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Common error codes as constants
106
+ */
107
+ export const ERROR_CODES = {
108
+ BAD_REQUEST: 400,
109
+ UNAUTHORIZED: 401,
110
+ FORBIDDEN: 403,
111
+ NOT_FOUND: 404,
112
+ TIMEOUT: 408,
113
+ SERVER_ERROR: 500,
114
+ BAD_GATEWAY: 502,
115
+ SERVICE_UNAVAILABLE: 503,
116
+ GATEWAY_TIMEOUT: 504,
117
+ } as const;
118
+
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Error components exports
3
+ */
4
+
5
+ export { ErrorBoundary } from './ErrorBoundary';
6
+ export { ErrorLayout } from './ErrorLayout';
7
+ export type { ErrorLayoutProps } from './ErrorLayout';
8
+ export { getErrorContent, ERROR_CODES } from './errorConfig';
9
+ export type { ErrorContent } from './errorConfig';
10
+