@djangocfg/layouts 2.1.429 → 2.1.431
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
CHANGED
|
@@ -162,6 +162,12 @@ export default function Layout({ children }) {
|
|
|
162
162
|
|
|
163
163
|
Wraps `Boundary` from `@djangocfg/ui-core` — same `variant`/`fallback`/`resetKeys` API, plus auto `FrontendMonitor.capture(...)`.
|
|
164
164
|
|
|
165
|
+
> **One top-level boundary.** `BaseApp` already mounts the app-wide crash
|
|
166
|
+
> boundary (UiProviders' built-in one, fed an i18n fallback) — don't wrap a
|
|
167
|
+
> second one around the whole app. Use `MonitorBoundary` for **per-section**
|
|
168
|
+
> isolation (so one panel's crash doesn't take the page), not as a duplicate
|
|
169
|
+
> root boundary. All boundaries share the single ui-core `Boundary` catch.
|
|
170
|
+
|
|
165
171
|
For local UI widgets that should not report to backend, use `Boundary` from `@djangocfg/ui-core` directly.
|
|
166
172
|
|
|
167
173
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.431",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
"check": "tsc --noEmit"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
|
-
"@djangocfg/api": "^2.1.
|
|
93
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
94
|
-
"@djangocfg/debuger": "^2.1.
|
|
95
|
-
"@djangocfg/i18n": "^2.1.
|
|
96
|
-
"@djangocfg/monitor": "^2.1.
|
|
97
|
-
"@djangocfg/ui-core": "^2.1.
|
|
92
|
+
"@djangocfg/api": "^2.1.431",
|
|
93
|
+
"@djangocfg/centrifugo": "^2.1.431",
|
|
94
|
+
"@djangocfg/debuger": "^2.1.431",
|
|
95
|
+
"@djangocfg/i18n": "^2.1.431",
|
|
96
|
+
"@djangocfg/monitor": "^2.1.431",
|
|
97
|
+
"@djangocfg/ui-core": "^2.1.431",
|
|
98
98
|
"@hookform/resolvers": "^5.2.2",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -125,14 +125,14 @@
|
|
|
125
125
|
"uuid": "^11.1.0"
|
|
126
126
|
},
|
|
127
127
|
"devDependencies": {
|
|
128
|
-
"@djangocfg/api": "^2.1.
|
|
129
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
130
|
-
"@djangocfg/debuger": "^2.1.
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
132
|
-
"@djangocfg/monitor": "^2.1.
|
|
133
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
134
|
-
"@djangocfg/ui-core": "^2.1.
|
|
135
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
128
|
+
"@djangocfg/api": "^2.1.431",
|
|
129
|
+
"@djangocfg/centrifugo": "^2.1.431",
|
|
130
|
+
"@djangocfg/debuger": "^2.1.431",
|
|
131
|
+
"@djangocfg/i18n": "^2.1.431",
|
|
132
|
+
"@djangocfg/monitor": "^2.1.431",
|
|
133
|
+
"@djangocfg/typescript-config": "^2.1.431",
|
|
134
|
+
"@djangocfg/ui-core": "^2.1.431",
|
|
135
|
+
"@djangocfg/ui-tools": "^2.1.431",
|
|
136
136
|
"@types/node": "^25.2.3",
|
|
137
137
|
"@types/react": "^19.2.15",
|
|
138
138
|
"@types/react-dom": "^19.2.3",
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* ErrorBoundary — the i18n'd, support-email fallback for Next app layouts.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Thin wrapper over the ui-core `Boundary` primitive: ui-core owns the catch
|
|
5
|
+
* machinery (getDerivedStateFromError / componentDidCatch / reset), this layer
|
|
6
|
+
* only supplies a localized fullscreen fallback + an optional support email.
|
|
7
|
+
* One catch implementation across the stack — UiProviders' built-in boundary,
|
|
8
|
+
* MonitorBoundary, and this all build on the same ui-core Boundary.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
'use client';
|
|
8
12
|
|
|
9
|
-
import {
|
|
10
|
-
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
13
|
+
import { useMemo, type ComponentType, type ErrorInfo, type ReactNode } from 'react';
|
|
11
14
|
|
|
15
|
+
import { Boundary, type BoundaryRenderProps } from '@djangocfg/ui-core/components';
|
|
12
16
|
import { getT } from '@djangocfg/i18n';
|
|
13
17
|
|
|
14
18
|
interface ErrorBoundaryProps {
|
|
@@ -17,90 +21,60 @@ interface ErrorBoundaryProps {
|
|
|
17
21
|
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (isDev) {
|
|
43
|
-
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
componentDidUpdate(_prevProps: ErrorBoundaryProps, prevState: ErrorBoundaryState) {
|
|
48
|
-
if (this.state.hasError && !prevState.hasError) {
|
|
49
|
-
this.handlePopState = () => {
|
|
50
|
-
window.location.reload();
|
|
51
|
-
};
|
|
52
|
-
window.addEventListener('popstate', this.handlePopState);
|
|
53
|
-
} else if (!this.state.hasError && prevState.hasError) {
|
|
54
|
-
if (this.handlePopState) {
|
|
55
|
-
window.removeEventListener('popstate', this.handlePopState);
|
|
56
|
-
this.handlePopState = null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
componentWillUnmount() {
|
|
62
|
-
if (this.handlePopState) {
|
|
63
|
-
window.removeEventListener('popstate', this.handlePopState);
|
|
64
|
-
this.handlePopState = null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
render() {
|
|
69
|
-
if (this.state.hasError) {
|
|
70
|
-
const title = getT('layouts.errors.somethingWentWrong');
|
|
71
|
-
const description = getT('layouts.errors.tryRefreshing');
|
|
72
|
-
const refreshButton = getT('layouts.errors.refreshPage');
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
76
|
-
<div className="max-w-md w-full space-y-4 text-center">
|
|
77
|
-
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
|
|
78
|
-
<p className="text-muted-foreground">
|
|
79
|
-
{description}
|
|
24
|
+
/** Localized fullscreen fallback. `reset()` re-mounts the tree (ui-core); we
|
|
25
|
+
* also offer a hard reload, matching the prior behavior. */
|
|
26
|
+
function makeFallback(supportEmail?: string) {
|
|
27
|
+
return function ErrorFallback({ reset }: BoundaryRenderProps) {
|
|
28
|
+
const title = getT('layouts.errors.somethingWentWrong');
|
|
29
|
+
const description = getT('layouts.errors.tryRefreshing');
|
|
30
|
+
const refreshButton = getT('layouts.errors.refreshPage');
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
33
|
+
<div className="max-w-md w-full space-y-4 text-center">
|
|
34
|
+
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
|
|
35
|
+
<p className="text-muted-foreground">{description}</p>
|
|
36
|
+
{supportEmail && (
|
|
37
|
+
<p className="text-sm text-muted-foreground">
|
|
38
|
+
{getT('layouts.errors.persistsContact', { email: '' })}{' '}
|
|
39
|
+
<a
|
|
40
|
+
href={`mailto:${supportEmail}`}
|
|
41
|
+
className="text-primary hover:underline"
|
|
42
|
+
>
|
|
43
|
+
{supportEmail}
|
|
44
|
+
</a>
|
|
80
45
|
</p>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<button
|
|
93
|
-
onClick={() => window.location.reload()}
|
|
94
|
-
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
|
95
|
-
>
|
|
96
|
-
{refreshButton}
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
46
|
+
)}
|
|
47
|
+
<button
|
|
48
|
+
onClick={() => {
|
|
49
|
+
reset();
|
|
50
|
+
// Hard reload as a fallback if a pure reset can't recover.
|
|
51
|
+
window.location.reload();
|
|
52
|
+
}}
|
|
53
|
+
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
|
54
|
+
>
|
|
55
|
+
{refreshButton}
|
|
56
|
+
</button>
|
|
99
57
|
</div>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return this.props.children;
|
|
104
|
-
}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
105
61
|
}
|
|
106
62
|
|
|
63
|
+
export function ErrorBoundary({ children, supportEmail, onError }: ErrorBoundaryProps) {
|
|
64
|
+
// Memoize so the boundary's FallbackComponent identity is stable across
|
|
65
|
+
// renders (no remount churn).
|
|
66
|
+
const Fallback = useMemo<ComponentType<BoundaryRenderProps>>(
|
|
67
|
+
() => makeFallback(supportEmail),
|
|
68
|
+
[supportEmail],
|
|
69
|
+
);
|
|
70
|
+
return (
|
|
71
|
+
<Boundary
|
|
72
|
+
variant="fullscreen"
|
|
73
|
+
name="layouts-error-boundary"
|
|
74
|
+
onError={onError}
|
|
75
|
+
FallbackComponent={Fallback}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</Boundary>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -5,10 +5,14 @@ runtime error tracker, and developer-facing toasts (with a Copy-as-cURL action).
|
|
|
5
5
|
|
|
6
6
|
## Pieces
|
|
7
7
|
|
|
8
|
+
All boundaries here build on the single `Boundary` primitive in
|
|
9
|
+
`@djangocfg/ui-core` — no duplicate catch logic. `BaseApp` mounts ONE boundary
|
|
10
|
+
(UiProviders' built-in, fed an i18n fallback), so apps don't wrap their own.
|
|
11
|
+
|
|
8
12
|
| File | What it does |
|
|
9
13
|
|------|--------------|
|
|
10
|
-
| `ErrorBoundary.tsx` |
|
|
11
|
-
| `MonitorBoundary.tsx` |
|
|
14
|
+
| `ErrorBoundary.tsx` | Thin wrapper over ui-core `Boundary` — i18n fallback + support email (for direct use; `BaseApp` no longer wraps it). |
|
|
15
|
+
| `MonitorBoundary.tsx` | ui-core `Boundary` that also reports to `@djangocfg/monitor`. |
|
|
12
16
|
| `ErrorLayout.tsx` | Full-page error layout (`getErrorContent` / `ERROR_CODES`). |
|
|
13
17
|
| `ErrorsTracker/` | Global runtime tracker — listens for error CustomEvents and shows toasts. |
|
|
14
18
|
|
|
@@ -57,7 +57,8 @@ import { NextRouterAdapter, NextLinkProvider } from '@djangocfg/ui-core/adapters
|
|
|
57
57
|
import { NextIntlLinkBridge } from './NextIntlLinkBridge';
|
|
58
58
|
import { ThemeProvider } from '@djangocfg/ui-core/theme';
|
|
59
59
|
import { ThemeStyleBridge } from '../../theme/ThemeStyleBridge';
|
|
60
|
-
import {
|
|
60
|
+
import type { BoundaryRenderProps } from '@djangocfg/ui-core/components';
|
|
61
|
+
import { getT } from '@djangocfg/i18n';
|
|
61
62
|
import { ErrorTrackingProvider } from '../../components/errors/ErrorsTracker';
|
|
62
63
|
import { AnalyticsProvider } from '../../snippets/Analytics';
|
|
63
64
|
import { AuthDialog } from '../../snippets/AuthDialog';
|
|
@@ -73,6 +74,51 @@ const DebugButton = React.lazy(() =>
|
|
|
73
74
|
// For backwards compatibility, re-export as BaseAppProps
|
|
74
75
|
export type BaseAppProps = BaseLayoutProps;
|
|
75
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Localized crash fallback fed into UiProviders' built-in Boundary. Renders the
|
|
79
|
+
* i18n'd "something went wrong" screen + optional support email. `reset()`
|
|
80
|
+
* re-mounts (soft recover); the Reload button hard-reloads when a soft reset
|
|
81
|
+
* can't recover (e.g. the crash is in a provider above the boundary's subtree).
|
|
82
|
+
*/
|
|
83
|
+
function AppErrorFallback({
|
|
84
|
+
reset,
|
|
85
|
+
supportEmail,
|
|
86
|
+
}: BoundaryRenderProps & { supportEmail?: string }) {
|
|
87
|
+
const title = getT('layouts.errors.somethingWentWrong');
|
|
88
|
+
const description = getT('layouts.errors.tryRefreshing');
|
|
89
|
+
const retry = getT('layouts.errors.refreshPage');
|
|
90
|
+
return (
|
|
91
|
+
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
|
92
|
+
<div className="max-w-md w-full space-y-4 text-center">
|
|
93
|
+
<h1 className="text-2xl font-bold text-foreground">{title}</h1>
|
|
94
|
+
<p className="text-muted-foreground">{description}</p>
|
|
95
|
+
{supportEmail && (
|
|
96
|
+
<p className="text-sm text-muted-foreground">
|
|
97
|
+
{getT('layouts.errors.persistsContact', { email: '' })}{' '}
|
|
98
|
+
<a href={`mailto:${supportEmail}`} className="text-primary hover:underline">
|
|
99
|
+
{supportEmail}
|
|
100
|
+
</a>
|
|
101
|
+
</p>
|
|
102
|
+
)}
|
|
103
|
+
<div className="flex items-center justify-center gap-2">
|
|
104
|
+
<button
|
|
105
|
+
onClick={reset}
|
|
106
|
+
className="rounded-md bg-primary px-4 py-2 text-primary-foreground hover:bg-primary/90"
|
|
107
|
+
>
|
|
108
|
+
{retry}
|
|
109
|
+
</button>
|
|
110
|
+
<button
|
|
111
|
+
onClick={() => window.location.reload()}
|
|
112
|
+
className="rounded-md border border-border px-4 py-2 text-foreground hover:bg-muted"
|
|
113
|
+
>
|
|
114
|
+
{getT('layouts.errors.refreshPage')}
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
76
122
|
/**
|
|
77
123
|
* BaseApp - Core providers wrapper for any React/Next.js app
|
|
78
124
|
*
|
|
@@ -131,7 +177,21 @@ export function BaseApp({
|
|
|
131
177
|
*/}
|
|
132
178
|
<NextRouterAdapter>
|
|
133
179
|
<NextLinkProvider>
|
|
134
|
-
|
|
180
|
+
{/* Single top-level boundary lives inside UiProviders. We feed it the
|
|
181
|
+
app's i18n fallback + onError instead of wrapping our own boundary on
|
|
182
|
+
top — one catch in the tree. errorBoundary={false} fully disables it
|
|
183
|
+
when the host opts out. */}
|
|
184
|
+
<UiProviders
|
|
185
|
+
errorBoundary={enableErrorBoundary ? undefined : false}
|
|
186
|
+
onError={errorBoundary?.onError}
|
|
187
|
+
errorFallback={
|
|
188
|
+
enableErrorBoundary
|
|
189
|
+
? (props) => (
|
|
190
|
+
<AppErrorFallback {...props} supportEmail={errorBoundary?.supportEmail} />
|
|
191
|
+
)
|
|
192
|
+
: undefined
|
|
193
|
+
}
|
|
194
|
+
>
|
|
135
195
|
<SWRConfig
|
|
136
196
|
value={{
|
|
137
197
|
revalidateOnFocus: swr?.revalidateOnFocus ?? true,
|
|
@@ -189,17 +249,7 @@ export function BaseApp({
|
|
|
189
249
|
</ThemeProvider>
|
|
190
250
|
);
|
|
191
251
|
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<ErrorBoundary
|
|
196
|
-
supportEmail={errorBoundary?.supportEmail}
|
|
197
|
-
onError={errorBoundary?.onError}
|
|
198
|
-
>
|
|
199
|
-
{content}
|
|
200
|
-
</ErrorBoundary>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
252
|
+
// The error boundary is now UiProviders' built-in one (fed our i18n fallback
|
|
253
|
+
// + onError above) — no outer wrapper needed.
|
|
204
254
|
return content;
|
|
205
255
|
}
|