@djangocfg/layouts 2.1.110 → 2.1.111

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.110",
3
+ "version": "2.1.111",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -74,11 +74,12 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.110",
78
- "@djangocfg/centrifugo": "^2.1.110",
79
- "@djangocfg/ui-core": "^2.1.110",
80
- "@djangocfg/ui-nextjs": "^2.1.110",
81
- "@djangocfg/ui-tools": "^2.1.110",
77
+ "@djangocfg/api": "^2.1.111",
78
+ "@djangocfg/centrifugo": "^2.1.111",
79
+ "@djangocfg/i18n": "^2.1.111",
80
+ "@djangocfg/ui-core": "^2.1.111",
81
+ "@djangocfg/ui-nextjs": "^2.1.111",
82
+ "@djangocfg/ui-tools": "^2.1.111",
82
83
  "@hookform/resolvers": "^5.2.2",
83
84
  "consola": "^3.4.2",
84
85
  "lucide-react": "^0.545.0",
@@ -101,12 +102,13 @@
101
102
  "uuid": "^11.1.0"
102
103
  },
103
104
  "devDependencies": {
104
- "@djangocfg/api": "^2.1.110",
105
- "@djangocfg/centrifugo": "^2.1.110",
106
- "@djangocfg/typescript-config": "^2.1.110",
107
- "@djangocfg/ui-core": "^2.1.110",
108
- "@djangocfg/ui-nextjs": "^2.1.110",
109
- "@djangocfg/ui-tools": "^2.1.110",
105
+ "@djangocfg/api": "^2.1.111",
106
+ "@djangocfg/i18n": "^2.1.111",
107
+ "@djangocfg/centrifugo": "^2.1.111",
108
+ "@djangocfg/typescript-config": "^2.1.111",
109
+ "@djangocfg/ui-core": "^2.1.111",
110
+ "@djangocfg/ui-nextjs": "^2.1.111",
111
+ "@djangocfg/ui-tools": "^2.1.111",
110
112
  "@types/node": "^24.7.2",
111
113
  "@types/react": "^19.1.0",
112
114
  "@types/react-dom": "^19.1.0",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Simple ErrorBoundary Component
3
- *
3
+ *
4
4
  * Catches React errors and displays a fallback UI
5
5
  */
6
6
 
@@ -8,6 +8,8 @@
8
8
 
9
9
  import React, { Component, ErrorInfo, ReactNode } from 'react';
10
10
 
11
+ import { getT } from '@djangocfg/i18n';
12
+
11
13
  interface ErrorBoundaryProps {
12
14
  children: ReactNode;
13
15
  supportEmail?: string;
@@ -34,7 +36,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
34
36
  if (this.props.onError) {
35
37
  this.props.onError(error, errorInfo);
36
38
  }
37
-
39
+
38
40
  // Log to console in development
39
41
  if (process.env.NODE_ENV === 'development') {
40
42
  console.error('ErrorBoundary caught an error:', error, errorInfo);
@@ -43,16 +45,20 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
43
45
 
44
46
  render() {
45
47
  if (this.state.hasError) {
48
+ const title = getT('layouts.errors.somethingWentWrong');
49
+ const description = getT('layouts.errors.tryRefreshing');
50
+ const refreshButton = getT('layouts.errors.refreshPage');
51
+
46
52
  return (
47
53
  <div className="flex min-h-screen items-center justify-center bg-background p-4">
48
54
  <div className="max-w-md w-full space-y-4 text-center">
49
- <h1 className="text-2xl font-bold text-foreground">Something went wrong</h1>
55
+ <h1 className="text-2xl font-bold text-foreground">{title}</h1>
50
56
  <p className="text-muted-foreground">
51
- We're sorry, but something unexpected happened. Please try refreshing the page.
57
+ {description}
52
58
  </p>
53
59
  {this.props.supportEmail && (
54
60
  <p className="text-sm text-muted-foreground">
55
- If the problem persists, please contact{' '}
61
+ {getT('layouts.errors.persistsContact', { email: '' })}{' '}
56
62
  <a
57
63
  href={`mailto:${this.props.supportEmail}`}
58
64
  className="text-primary hover:underline"
@@ -65,7 +71,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
65
71
  onClick={() => window.location.reload()}
66
72
  className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
67
73
  >
68
- Refresh Page
74
+ {refreshButton}
69
75
  </button>
70
76
  </div>
71
77
  </div>
@@ -17,9 +17,10 @@
17
17
 
18
18
  'use client';
19
19
 
20
- import React from 'react';
20
+ import React, { useMemo } from 'react';
21
21
 
22
22
  import { Button } from '@djangocfg/ui-core/components';
23
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
23
24
 
24
25
  import { getErrorContent } from './errorConfig';
25
26
 
@@ -112,17 +113,26 @@ export function ErrorLayout({
112
113
  illustration,
113
114
  supportEmail = 'support@example.com',
114
115
  }: ErrorLayoutProps) {
115
-
116
+ const t = useTypedT<I18nTranslations>();
117
+
118
+ const labels = useMemo(() => ({
119
+ goBack: t('layouts.errors.goBack'),
120
+ goHome: t('layouts.errors.goHome'),
121
+ needHelp: t('layouts.errors.needHelp'),
122
+ contactSupport: t('layouts.errors.contactSupport'),
123
+ error: t('layouts.errors.error'),
124
+ }), [t]);
125
+
116
126
  // Get content (Title/Description) from config. Note: Illustration check removed.
117
127
  // The function getErrorContent MUST NOT return React components/functions.
118
128
  const autoContent = code && (!title || !description)
119
- ? getErrorContent(code)
129
+ ? getErrorContent(code, t)
120
130
  : null;
121
131
 
122
132
  // Fallback to auto-generated values
123
- const finalTitle = title || autoContent?.title || 'Error';
133
+ const finalTitle = title || autoContent?.title || labels.error;
124
134
  const finalDescription = description || autoContent?.description;
125
-
135
+
126
136
  // ILLUSTRATION FIX: Use passed prop OR compute the icon locally using getErrorIcon.
127
137
  const finalIllustration = illustration ?? getErrorIcon(code);
128
138
 
@@ -178,7 +188,7 @@ export function ErrorLayout({
178
188
  onClick={handleGoBack}
179
189
  style={{ minWidth: '140px', padding: '12px 32px' }}
180
190
  >
181
- Go Back
191
+ {labels.goBack}
182
192
  </Button>
183
193
  <Button
184
194
  variant="default"
@@ -186,7 +196,7 @@ export function ErrorLayout({
186
196
  onClick={handleGoHome}
187
197
  style={{ minWidth: '140px', padding: '12px 32px' }}
188
198
  >
189
- Go Home
199
+ {labels.goHome}
190
200
  </Button>
191
201
  </>
192
202
  )}
@@ -195,12 +205,12 @@ export function ErrorLayout({
195
205
  {/* Additional Info */}
196
206
  <div className="pt-8 text-sm text-muted-foreground">
197
207
  <p>
198
- Need help? Contact{' '}
208
+ {labels.needHelp}{' '}
199
209
  <a
200
210
  href={`mailto:${supportEmail}`}
201
211
  className="text-primary hover:underline"
202
212
  >
203
- support
213
+ {labels.contactSupport}
204
214
  </a>
205
215
  </p>
206
216
  </div>
@@ -13,90 +13,96 @@ export interface ErrorContent {
13
13
  description: string;
14
14
  }
15
15
 
16
+ type TranslationFn = (key: string, params?: Record<string, string | number>) => string;
17
+
16
18
  /**
17
19
  * Get standardized error content based on status code
18
20
  *
19
21
  * @param statusCode - HTTP status code or custom error type
22
+ * @param t - Translation function from useT()
20
23
  * @returns Error content configuration (title and description only)
21
24
  *
22
25
  * @example
23
26
  * ```tsx
24
- * const { title, description } = getErrorContent(404);
27
+ * const { title, description } = getErrorContent(404, t);
25
28
  * <ErrorLayout title={title} description={description} code={404} />
26
29
  * ```
27
30
  */
28
- export function getErrorContent(statusCode?: number | string): ErrorContent {
31
+ export function getErrorContent(statusCode?: number | string, t?: TranslationFn): ErrorContent {
29
32
  const code = typeof statusCode === 'string' ? parseInt(statusCode, 10) : statusCode;
30
33
 
34
+ // Helper to get translation or fallback
35
+ const tr = (key: string, fallback: string) => t ? t(key) : fallback;
36
+
31
37
  switch (code) {
32
38
  // 400 Bad Request
33
39
  case 400:
34
40
  return {
35
- title: 'Bad Request',
36
- description: 'The request could not be understood. Please check your input and try again.',
41
+ title: tr('layouts.errors.badRequest', 'Bad Request'),
42
+ description: tr('layouts.errors.badRequestDesc', 'The request could not be understood. Please check your input and try again.'),
37
43
  };
38
44
 
39
45
  // 401 Unauthorized
40
46
  case 401:
41
47
  return {
42
- title: 'Authentication Required',
43
- description: 'You need to sign in to access this page.',
48
+ title: tr('layouts.errors.authRequired', 'Authentication Required'),
49
+ description: tr('layouts.errors.authRequiredDesc', 'You need to sign in to access this page.'),
44
50
  };
45
51
 
46
52
  // 403 Forbidden
47
53
  case 403:
48
54
  return {
49
- title: 'Access Denied',
50
- description: "You don't have permission to access this resource.",
55
+ title: tr('layouts.errors.accessDenied', 'Access Denied'),
56
+ description: tr('layouts.errors.accessDeniedDesc', "You don't have permission to access this resource."),
51
57
  };
52
58
 
53
59
  // 404 Not Found
54
60
  case 404:
55
61
  return {
56
- title: 'Page Not Found',
57
- description: "The page you're looking for doesn't exist or has been moved.",
62
+ title: tr('layouts.errors.pageNotFound', 'Page Not Found'),
63
+ description: tr('layouts.errors.pageNotFoundDesc', "The page you're looking for doesn't exist or has been moved."),
58
64
  };
59
65
 
60
66
  // 408 Request Timeout
61
67
  case 408:
62
68
  return {
63
- title: 'Request Timeout',
64
- description: 'The request took too long to process. Please try again.',
69
+ title: tr('layouts.errors.requestTimeout', 'Request Timeout'),
70
+ description: tr('layouts.errors.requestTimeoutDesc', 'The request took too long to process. Please try again.'),
65
71
  };
66
72
 
67
73
  // 500 Internal Server Error
68
74
  case 500:
69
75
  return {
70
- title: 'Server Error',
71
- description: "Something went wrong on our end. We're working to fix it.",
76
+ title: tr('layouts.errors.serverError', 'Server Error'),
77
+ description: tr('layouts.errors.serverErrorDesc', "Something went wrong on our end. We're working to fix it."),
72
78
  };
73
79
 
74
80
  // 502 Bad Gateway
75
81
  case 502:
76
82
  return {
77
- title: 'Bad Gateway',
78
- description: 'The server received an invalid response. Please try again later.',
83
+ title: tr('layouts.errors.badGateway', 'Bad Gateway'),
84
+ description: tr('layouts.errors.badGatewayDesc', 'The server received an invalid response. Please try again later.'),
79
85
  };
80
86
 
81
87
  // 503 Service Unavailable
82
88
  case 503:
83
89
  return {
84
- title: 'Service Unavailable',
85
- description: 'The service is temporarily unavailable. Please try again later.',
90
+ title: tr('layouts.errors.serviceUnavailable', 'Service Unavailable'),
91
+ description: tr('layouts.errors.serviceUnavailableDesc', 'The service is temporarily unavailable. Please try again later.'),
86
92
  };
87
93
 
88
94
  // 504 Gateway Timeout
89
95
  case 504:
90
96
  return {
91
- title: 'Gateway Timeout',
92
- description: 'The server took too long to respond. Please try again.',
97
+ title: tr('layouts.errors.gatewayTimeout', 'Gateway Timeout'),
98
+ description: tr('layouts.errors.gatewayTimeoutDesc', 'The server took too long to respond. Please try again.'),
93
99
  };
94
100
 
95
101
  // Default / Unknown Error
96
102
  default:
97
103
  return {
98
- title: 'Something Went Wrong',
99
- description: 'An unexpected error occurred. Please try again or contact support.',
104
+ title: tr('layouts.errors.somethingWentWrong', 'Something Went Wrong'),
105
+ description: tr('layouts.errors.unknownErrorDesc', 'An unexpected error occurred. Please try again or contact support.'),
100
106
  };
101
107
  }
102
108
  }