@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 +14 -12
- package/src/components/errors/ErrorBoundary.tsx +12 -6
- package/src/components/errors/ErrorLayout.tsx +19 -9
- package/src/components/errors/errorConfig.ts +28 -22
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +128 -56
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +12 -4
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +6 -2
- package/src/layouts/_components/UserMenu.tsx +14 -6
- package/src/snippets/AuthDialog/AuthDialog.tsx +15 -6
- package/src/snippets/Breadcrumbs.tsx +19 -8
- package/src/snippets/McpChat/components/ChatPanel.tsx +16 -6
- package/src/snippets/McpChat/components/ChatSidebar.tsx +20 -8
- package/src/snippets/PWAInstall/components/A2HSHint.tsx +23 -10
- package/src/snippets/PWAInstall/components/DesktopGuide.tsx +44 -32
- package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +34 -25
- package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +34 -25
- package/src/snippets/PushNotifications/components/PushPrompt.tsx +16 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
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.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/
|
|
80
|
-
"@djangocfg/ui-
|
|
81
|
-
"@djangocfg/ui-
|
|
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.
|
|
105
|
-
"@djangocfg/
|
|
106
|
-
"@djangocfg/
|
|
107
|
-
"@djangocfg/
|
|
108
|
-
"@djangocfg/ui-
|
|
109
|
-
"@djangocfg/ui-
|
|
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">
|
|
55
|
+
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
|
|
50
56
|
<p className="text-muted-foreground">
|
|
51
|
-
|
|
57
|
+
{description}
|
|
52
58
|
</p>
|
|
53
59
|
{this.props.supportEmail && (
|
|
54
60
|
<p className="text-sm text-muted-foreground">
|
|
55
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
208
|
+
{labels.needHelp}{' '}
|
|
199
209
|
<a
|
|
200
210
|
href={`mailto:${supportEmail}`}
|
|
201
211
|
className="text-primary hover:underline"
|
|
202
212
|
>
|
|
203
|
-
|
|
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
|
}
|