@djangocfg/layouts 2.1.541 → 2.1.543
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 +1 -1
- package/package.json +17 -17
- package/src/components/core/JsonLd.tsx +1 -1
- package/src/components/errors/ErrorsTracker/components/ErrorButtons.tsx +4 -7
- package/src/components/errors/ErrorsTracker/components/ErrorToast.tsx +3 -41
- package/src/components/errors/ErrorsTracker/index.ts +1 -4
- package/src/components/errors/ErrorsTracker/providers/ErrorTrackingProvider.tsx +40 -59
- package/src/components/errors/ErrorsTracker/types.ts +2 -53
- package/src/components/errors/ErrorsTracker/utils/curl-generator.ts +2 -2
- package/src/components/errors/ErrorsTracker/utils/formatters.ts +2 -29
- package/src/components/errors/README.md +1 -1
- package/src/configurator/auth/schema.ts +1 -1
- package/src/configurator/index.ts +1 -1
- package/src/configurator/private/schema.ts +1 -1
- package/src/layouts/AppLayout/BaseApp.tsx +25 -43
- package/src/layouts/AppLayout/README.md +1 -2
- package/src/layouts/AuthLayout/AuthLayout.tsx +6 -6
- package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +2 -1
- package/src/layouts/AuthLayout/context.tsx +0 -1
- package/src/layouts/AuthLayout/types.ts +6 -3
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +2 -7
- package/src/layouts/PrivateLayout/components/PrivateSidebarAccount.tsx +1 -1
- package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +3 -8
- package/src/layouts/PrivateLayout/components/SidebarFeatured.tsx +10 -6
- package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +7 -7
- package/src/layouts/PublicLayout/components/UserMenu.tsx +0 -1
- package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +1 -1
- package/src/layouts/PublicLayout/hooks/useResponsiveOverflow.ts +13 -10
- package/src/layouts/PublicLayout/primitives/NavActions.tsx +7 -9
- package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +1 -22
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +5 -1
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +7 -25
- package/src/layouts/types/index.ts +0 -1
- package/src/layouts/types/layout.types.ts +1 -4
- package/src/layouts/types/providers.types.ts +1 -14
- package/src/snippets/Breadcrumbs.tsx +3 -3
- package/src/testing/MockAuthFormProvider.tsx +0 -1
- package/src/utils/navItemsShallowEqual.ts +34 -0
- package/src/utils/shallowEqualBy.ts +29 -0
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ send analytics directly from a layout.
|
|
|
193
193
|
| `@djangocfg/layouts/layouts` | Layout components. |
|
|
194
194
|
| `@djangocfg/layouts/components` | Misc + `ErrorLayout`, `RedirectPage`. |
|
|
195
195
|
| `@djangocfg/layouts/pages/legal` | Legal page templates. |
|
|
196
|
-
| `@djangocfg/layouts/configurator` | JSON Schemas for `<JsonSchemaForm>`-driven configurator UIs (PrivateLayout for now). Pair with `@djangocfg/
|
|
196
|
+
| `@djangocfg/layouts/configurator` | JSON Schemas for `<JsonSchemaForm>`-driven configurator UIs (PrivateLayout for now). Pair with `@djangocfg/widget-forms`. |
|
|
197
197
|
| `@djangocfg/layouts/styles` | Base CSS. |
|
|
198
198
|
|
|
199
199
|
Extensions: `@djangocfg/ext-newsletter`, `ext-knowbase`, `ext-leads`, `ext-payments`, `ext-support`, …
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.543",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"files": [
|
|
82
82
|
"dist",
|
|
83
83
|
"src",
|
|
84
|
+
"!src/**/*.stories.tsx",
|
|
84
85
|
"README.md",
|
|
85
86
|
"LICENSE"
|
|
86
87
|
],
|
|
@@ -89,12 +90,11 @@
|
|
|
89
90
|
"check": "tsc --noEmit"
|
|
90
91
|
},
|
|
91
92
|
"peerDependencies": {
|
|
92
|
-
"@djangocfg/analytics": "^2.1.
|
|
93
|
-
"@djangocfg/api": "^2.1.
|
|
94
|
-
"@djangocfg/
|
|
95
|
-
"@djangocfg/
|
|
96
|
-
"@djangocfg/
|
|
97
|
-
"@djangocfg/ui-core": "^2.1.541",
|
|
93
|
+
"@djangocfg/analytics": "^2.1.543",
|
|
94
|
+
"@djangocfg/api": "^2.1.543",
|
|
95
|
+
"@djangocfg/devtools": "^2.1.543",
|
|
96
|
+
"@djangocfg/i18n": "^2.1.543",
|
|
97
|
+
"@djangocfg/ui-core": "^2.1.543",
|
|
98
98
|
"@hookform/resolvers": "^5.2.2",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -102,8 +102,8 @@
|
|
|
102
102
|
"next": "^16.2.2",
|
|
103
103
|
"next-intl": "^4.9.1",
|
|
104
104
|
"p-retry": "^7.0.0",
|
|
105
|
-
"react": "^19.
|
|
106
|
-
"react-dom": "^19.
|
|
105
|
+
"react": "^19.0.0",
|
|
106
|
+
"react-dom": "^19.0.0",
|
|
107
107
|
"react-hook-form": "^7.69.0",
|
|
108
108
|
"sonner": "2.0.7",
|
|
109
109
|
"swr": "^2.3.7",
|
|
@@ -124,19 +124,19 @@
|
|
|
124
124
|
"uuid": "^11.1.1"
|
|
125
125
|
},
|
|
126
126
|
"devDependencies": {
|
|
127
|
-
"@djangocfg/analytics": "^2.1.
|
|
128
|
-
"@djangocfg/api": "^2.1.
|
|
129
|
-
"@djangocfg/
|
|
130
|
-
"@djangocfg/
|
|
131
|
-
"@djangocfg/
|
|
132
|
-
"@djangocfg/
|
|
133
|
-
"@
|
|
134
|
-
"@djangocfg/ui-tools": "^2.1.541",
|
|
127
|
+
"@djangocfg/analytics": "^2.1.543",
|
|
128
|
+
"@djangocfg/api": "^2.1.543",
|
|
129
|
+
"@djangocfg/devtools": "^2.1.543",
|
|
130
|
+
"@djangocfg/i18n": "^2.1.543",
|
|
131
|
+
"@djangocfg/typescript-config": "^2.1.543",
|
|
132
|
+
"@djangocfg/ui-core": "^2.1.543",
|
|
133
|
+
"@storybook/react-vite": "^10.5.0",
|
|
135
134
|
"@types/node": "^25.9.5",
|
|
136
135
|
"@types/react": "19.2.15",
|
|
137
136
|
"@types/react-dom": "19.2.3",
|
|
138
137
|
"eslint": "^9.39.5",
|
|
139
138
|
"next-intl": "^4.13.2",
|
|
139
|
+
"storybook": "^10.5.0",
|
|
140
140
|
"typescript": "^5.9.3",
|
|
141
141
|
"zustand": "^5.0.14"
|
|
142
142
|
},
|
|
@@ -14,21 +14,20 @@ import { useCopy } from '@djangocfg/ui-core/hooks';
|
|
|
14
14
|
|
|
15
15
|
import { generateCurlFromError } from '../utils/curl-generator';
|
|
16
16
|
import {
|
|
17
|
-
formatCentrifugoErrorForClipboard,
|
|
18
17
|
formatCORSErrorForClipboard,
|
|
19
18
|
formatNetworkErrorForClipboard,
|
|
20
19
|
formatValidationErrorForClipboard
|
|
21
20
|
} from '../utils/formatters';
|
|
22
21
|
|
|
23
|
-
import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail
|
|
22
|
+
import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail } from '../types';
|
|
24
23
|
export interface ErrorButtonsProps {
|
|
25
|
-
detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail
|
|
24
|
+
detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
/**
|
|
29
28
|
* Format error for clipboard based on type
|
|
30
29
|
*/
|
|
31
|
-
function formatErrorForClipboard(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail
|
|
30
|
+
function formatErrorForClipboard(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail): string {
|
|
32
31
|
switch (detail.type) {
|
|
33
32
|
case 'validation':
|
|
34
33
|
return formatValidationErrorForClipboard(detail);
|
|
@@ -36,8 +35,6 @@ function formatErrorForClipboard(detail: ValidationErrorDetail | CORSErrorDetail
|
|
|
36
35
|
return formatCORSErrorForClipboard(detail);
|
|
37
36
|
case 'network':
|
|
38
37
|
return formatNetworkErrorForClipboard(detail);
|
|
39
|
-
case 'centrifugo':
|
|
40
|
-
return formatCentrifugoErrorForClipboard(detail);
|
|
41
38
|
default:
|
|
42
39
|
return JSON.stringify(detail, null, 2);
|
|
43
40
|
}
|
|
@@ -46,7 +43,7 @@ function formatErrorForClipboard(detail: ValidationErrorDetail | CORSErrorDetail
|
|
|
46
43
|
/**
|
|
47
44
|
* Check if error supports cURL generation
|
|
48
45
|
*/
|
|
49
|
-
function supportsCurl(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail
|
|
46
|
+
function supportsCurl(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail): boolean {
|
|
50
47
|
return detail.type === 'validation';
|
|
51
48
|
}
|
|
52
49
|
|
|
@@ -17,12 +17,10 @@ import type {
|
|
|
17
17
|
ValidationErrorDetail,
|
|
18
18
|
CORSErrorDetail,
|
|
19
19
|
NetworkErrorDetail,
|
|
20
|
-
CentrifugoErrorDetail,
|
|
21
20
|
RuntimeErrorDetail,
|
|
22
21
|
ValidationErrorConfig,
|
|
23
22
|
CORSErrorConfig,
|
|
24
23
|
NetworkErrorConfig,
|
|
25
|
-
CentrifugoErrorConfig,
|
|
26
24
|
RuntimeErrorConfig,
|
|
27
25
|
} from '../types';
|
|
28
26
|
/**
|
|
@@ -130,7 +128,7 @@ function buildNetworkDescription(
|
|
|
130
128
|
<div className="opacity-90">{detail.error}</div>
|
|
131
129
|
|
|
132
130
|
{dpopHint && (
|
|
133
|
-
<div className="rounded-md bg-
|
|
131
|
+
<div className="rounded-md bg-warning/10 px-2 py-1 text-xs text-warning-foreground">
|
|
134
132
|
{dpopHint}
|
|
135
133
|
</div>
|
|
136
134
|
)}
|
|
@@ -140,40 +138,6 @@ function buildNetworkDescription(
|
|
|
140
138
|
);
|
|
141
139
|
}
|
|
142
140
|
|
|
143
|
-
/**
|
|
144
|
-
* Build centrifugo error description
|
|
145
|
-
*/
|
|
146
|
-
function buildCentrifugoDescription(
|
|
147
|
-
detail: CentrifugoErrorDetail,
|
|
148
|
-
config: Required<CentrifugoErrorConfig>
|
|
149
|
-
): React.ReactNode {
|
|
150
|
-
const parts: string[] = [];
|
|
151
|
-
|
|
152
|
-
// Add method info
|
|
153
|
-
if (config.showMethod) {
|
|
154
|
-
parts.push(`RPC: ${detail.method}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Add error code
|
|
158
|
-
if (config.showCode && detail.code !== undefined) {
|
|
159
|
-
parts.push(`Code: ${detail.code}`);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<div className="flex flex-col gap-2 text-sm">
|
|
164
|
-
{parts.length > 0 && (
|
|
165
|
-
<div className="font-mono text-xs opacity-90">
|
|
166
|
-
{parts.join(' • ')}
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
169
|
-
|
|
170
|
-
<div className="opacity-90">{detail.error}</div>
|
|
171
|
-
|
|
172
|
-
<ErrorButtons detail={detail} />
|
|
173
|
-
</div>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
141
|
/**
|
|
178
142
|
* Build runtime error description
|
|
179
143
|
*/
|
|
@@ -198,8 +162,8 @@ function buildRuntimeDescription(
|
|
|
198
162
|
* Create toast options for any error type
|
|
199
163
|
*/
|
|
200
164
|
export function createErrorToast(
|
|
201
|
-
detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail |
|
|
202
|
-
config: Required<ValidationErrorConfig | CORSErrorConfig | NetworkErrorConfig |
|
|
165
|
+
detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail | RuntimeErrorDetail,
|
|
166
|
+
config: Required<ValidationErrorConfig | CORSErrorConfig | NetworkErrorConfig | RuntimeErrorConfig>
|
|
203
167
|
) {
|
|
204
168
|
let description: React.ReactNode;
|
|
205
169
|
|
|
@@ -208,8 +172,6 @@ export function createErrorToast(
|
|
|
208
172
|
description = buildValidationDescription(detail, config as Required<ValidationErrorConfig>);
|
|
209
173
|
} else if (detail.type === 'cors') {
|
|
210
174
|
description = buildCORSDescription(detail, config as Required<CORSErrorConfig>);
|
|
211
|
-
} else if (detail.type === 'centrifugo') {
|
|
212
|
-
description = buildCentrifugoDescription(detail, config as Required<CentrifugoErrorConfig>);
|
|
213
175
|
} else if (detail.type === 'runtime') {
|
|
214
176
|
description = buildRuntimeDescription(detail, config as Required<RuntimeErrorConfig>);
|
|
215
177
|
} else {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error Tracking - Unified error tracking for all error types
|
|
3
3
|
*
|
|
4
|
-
* Single provider and hook for validation, CORS, network,
|
|
4
|
+
* Single provider and hook for validation, CORS, network, and runtime errors
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// Main provider and hook
|
|
@@ -21,14 +21,12 @@ export type {
|
|
|
21
21
|
ValidationErrorDetail,
|
|
22
22
|
CORSErrorDetail,
|
|
23
23
|
NetworkErrorDetail,
|
|
24
|
-
CentrifugoErrorDetail,
|
|
25
24
|
RuntimeErrorDetail,
|
|
26
25
|
StoredError,
|
|
27
26
|
ErrorTrackingConfig,
|
|
28
27
|
ValidationErrorConfig,
|
|
29
28
|
CORSErrorConfig,
|
|
30
29
|
NetworkErrorConfig,
|
|
31
|
-
CentrifugoErrorConfig,
|
|
32
30
|
RuntimeErrorConfig,
|
|
33
31
|
ErrorTrackingContextValue,
|
|
34
32
|
TrackedErrorType,
|
|
@@ -47,7 +45,6 @@ export {
|
|
|
47
45
|
formatValidationErrorForClipboard,
|
|
48
46
|
formatCORSErrorForClipboard,
|
|
49
47
|
formatNetworkErrorForClipboard,
|
|
50
|
-
formatCentrifugoErrorForClipboard,
|
|
51
48
|
formatRuntimeErrorForClipboard,
|
|
52
49
|
formatErrorTitle,
|
|
53
50
|
extractDomain,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
'use client';
|
|
28
28
|
|
|
29
29
|
import React, {
|
|
30
|
-
createContext, ReactNode, useCallback, useContext, useEffect, useState
|
|
30
|
+
createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState
|
|
31
31
|
} from 'react';
|
|
32
32
|
|
|
33
33
|
import { toast } from '@djangocfg/ui-core/hooks';
|
|
@@ -36,7 +36,6 @@ import { useDebugMode, isProduction } from '@djangocfg/devtools';
|
|
|
36
36
|
import { createErrorToast } from '../components/ErrorToast';
|
|
37
37
|
import { generateShortErrorId, getProductionToast } from '../utils/formatters';
|
|
38
38
|
import {
|
|
39
|
-
DEFAULT_CENTRIFUGO_CONFIG,
|
|
40
39
|
DEFAULT_CORS_CONFIG,
|
|
41
40
|
DEFAULT_NETWORK_CONFIG,
|
|
42
41
|
DEFAULT_RUNTIME_CONFIG,
|
|
@@ -50,12 +49,10 @@ import type {
|
|
|
50
49
|
ValidationErrorConfig,
|
|
51
50
|
CORSErrorConfig,
|
|
52
51
|
NetworkErrorConfig,
|
|
53
|
-
CentrifugoErrorConfig,
|
|
54
52
|
RuntimeErrorConfig,
|
|
55
53
|
ValidationErrorDetail,
|
|
56
54
|
CORSErrorDetail,
|
|
57
55
|
NetworkErrorDetail,
|
|
58
|
-
CentrifugoErrorDetail,
|
|
59
56
|
RuntimeErrorDetail,
|
|
60
57
|
ErrorTrackingContextValue,
|
|
61
58
|
TrackedErrorType,
|
|
@@ -82,8 +79,6 @@ function getErrorDedupeKey(detail: ErrorDetail): string {
|
|
|
82
79
|
return `cors:${detail.url}`;
|
|
83
80
|
case 'network':
|
|
84
81
|
return `network:${detail.url}:${detail.statusCode || 'unknown'}`;
|
|
85
|
-
case 'centrifugo':
|
|
86
|
-
return `centrifugo:${detail.method}:${detail.code || 'unknown'}`;
|
|
87
82
|
case 'runtime':
|
|
88
83
|
return `runtime:${detail.source}:${detail.message.slice(0, 50)}`;
|
|
89
84
|
default:
|
|
@@ -169,7 +164,6 @@ export interface ErrorTrackingProviderProps {
|
|
|
169
164
|
validation?: Partial<ValidationErrorConfig>;
|
|
170
165
|
cors?: Partial<CORSErrorConfig>;
|
|
171
166
|
network?: Partial<NetworkErrorConfig>;
|
|
172
|
-
centrifugo?: Partial<CentrifugoErrorConfig>;
|
|
173
167
|
runtime?: Partial<RuntimeErrorConfig>;
|
|
174
168
|
onError?: (error: ErrorDetail) => boolean | void;
|
|
175
169
|
/** Called with the raw ErrorDetail for every tracked error. Wire to devtools.capture() (via errorDetailToDevtoolsEvent) to forward errors to the backend. */
|
|
@@ -186,7 +180,6 @@ export function ErrorTrackingProvider({
|
|
|
186
180
|
validation: userValidationConfig,
|
|
187
181
|
cors: userCorsConfig,
|
|
188
182
|
network: userNetworkConfig,
|
|
189
|
-
centrifugo: userCentrifugoConfig,
|
|
190
183
|
runtime: userRuntimeConfig,
|
|
191
184
|
onError,
|
|
192
185
|
onMonitorCapture,
|
|
@@ -194,31 +187,29 @@ export function ErrorTrackingProvider({
|
|
|
194
187
|
const [errors, setErrors] = useState<StoredError[]>([]);
|
|
195
188
|
const isDebugMode = useDebugMode();
|
|
196
189
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
...userCorsConfig,
|
|
206
|
-
};
|
|
190
|
+
// Merged configs must be referentially stable: they feed the listener effect
|
|
191
|
+
// below, and a fresh object each render would detach and re-attach every
|
|
192
|
+
// window listener on every render — including the re-render each tracked
|
|
193
|
+
// error causes, dropping any error dispatched inside that gap.
|
|
194
|
+
const validationConfig: Required<ValidationErrorConfig> = useMemo(
|
|
195
|
+
() => ({ ...DEFAULT_VALIDATION_CONFIG, ...userValidationConfig }),
|
|
196
|
+
[userValidationConfig]
|
|
197
|
+
);
|
|
207
198
|
|
|
208
|
-
const
|
|
209
|
-
...
|
|
210
|
-
|
|
211
|
-
|
|
199
|
+
const corsConfig: Required<CORSErrorConfig> = useMemo(
|
|
200
|
+
() => ({ ...DEFAULT_CORS_CONFIG, ...userCorsConfig }),
|
|
201
|
+
[userCorsConfig]
|
|
202
|
+
);
|
|
212
203
|
|
|
213
|
-
const
|
|
214
|
-
...
|
|
215
|
-
|
|
216
|
-
|
|
204
|
+
const networkConfig: Required<NetworkErrorConfig> = useMemo(
|
|
205
|
+
() => ({ ...DEFAULT_NETWORK_CONFIG, ...userNetworkConfig }),
|
|
206
|
+
[userNetworkConfig]
|
|
207
|
+
);
|
|
217
208
|
|
|
218
|
-
const runtimeConfig: Required<RuntimeErrorConfig> =
|
|
219
|
-
...DEFAULT_RUNTIME_CONFIG,
|
|
220
|
-
|
|
221
|
-
|
|
209
|
+
const runtimeConfig: Required<RuntimeErrorConfig> = useMemo(
|
|
210
|
+
() => ({ ...DEFAULT_RUNTIME_CONFIG, ...userRuntimeConfig }),
|
|
211
|
+
[userRuntimeConfig]
|
|
212
|
+
);
|
|
222
213
|
|
|
223
214
|
/**
|
|
224
215
|
* Clear all errors
|
|
@@ -245,7 +236,7 @@ export function ErrorTrackingProvider({
|
|
|
245
236
|
* Handle any error event
|
|
246
237
|
*/
|
|
247
238
|
const handleError = useCallback(
|
|
248
|
-
(detail: ErrorDetail, config: Required<ValidationErrorConfig | CORSErrorConfig | NetworkErrorConfig |
|
|
239
|
+
(detail: ErrorDetail, config: Required<ValidationErrorConfig | CORSErrorConfig | NetworkErrorConfig | RuntimeErrorConfig>) => {
|
|
249
240
|
// Check for duplicate errors (within deduplication window)
|
|
250
241
|
const isUnique = shouldShowError(detail);
|
|
251
242
|
|
|
@@ -287,9 +278,19 @@ export function ErrorTrackingProvider({
|
|
|
287
278
|
}
|
|
288
279
|
}
|
|
289
280
|
},
|
|
290
|
-
|
|
281
|
+
// `isProduction` is a module constant, not reactive state.
|
|
282
|
+
[onError, onMonitorCapture, isDebugMode]
|
|
291
283
|
);
|
|
292
284
|
|
|
285
|
+
// The listener effect must not re-run when the handler changes identity: hosts
|
|
286
|
+
// pass `onError`/`onMonitorCapture` as inline arrows, so `handleError` is new
|
|
287
|
+
// on every render. Listeners read the current handler through this ref and
|
|
288
|
+
// stay attached for the provider's whole lifetime.
|
|
289
|
+
const handleErrorRef = useRef(handleError);
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
handleErrorRef.current = handleError;
|
|
292
|
+
}, [handleError]);
|
|
293
|
+
|
|
293
294
|
/**
|
|
294
295
|
* Setup event listeners
|
|
295
296
|
*/
|
|
@@ -318,7 +319,7 @@ export function ErrorTrackingProvider({
|
|
|
318
319
|
error: error as ValidationErrorDetail['error'],
|
|
319
320
|
response: raw.response ?? raw.data,
|
|
320
321
|
};
|
|
321
|
-
|
|
322
|
+
handleErrorRef.current(detail, validationConfig);
|
|
322
323
|
};
|
|
323
324
|
window.addEventListener(ERROR_EVENTS.VALIDATION, handler);
|
|
324
325
|
handlers.push({ event: ERROR_EVENTS.VALIDATION, handler });
|
|
@@ -332,32 +333,12 @@ export function ErrorTrackingProvider({
|
|
|
332
333
|
...event.detail,
|
|
333
334
|
type: 'network' as const,
|
|
334
335
|
};
|
|
335
|
-
|
|
336
|
+
handleErrorRef.current(detail, networkConfig);
|
|
336
337
|
};
|
|
337
338
|
window.addEventListener(ERROR_EVENTS.NETWORK, handler);
|
|
338
339
|
handlers.push({ event: ERROR_EVENTS.NETWORK, handler });
|
|
339
340
|
}
|
|
340
341
|
|
|
341
|
-
// Centrifugo errors (unified event with type discriminator)
|
|
342
|
-
if (centrifugoConfig.enabled) {
|
|
343
|
-
const handler = (event: Event) => {
|
|
344
|
-
if (!(event instanceof CustomEvent)) return;
|
|
345
|
-
// Filter: only handle 'error' type from unified centrifugo event
|
|
346
|
-
if (event.detail?.type !== 'error') return;
|
|
347
|
-
const detail: CentrifugoErrorDetail = {
|
|
348
|
-
type: 'centrifugo' as const,
|
|
349
|
-
method: event.detail.data?.method || 'unknown',
|
|
350
|
-
error: event.detail.data?.error || 'Unknown error',
|
|
351
|
-
code: event.detail.data?.code,
|
|
352
|
-
data: event.detail.data?.data,
|
|
353
|
-
timestamp: event.detail.timestamp || new Date(),
|
|
354
|
-
};
|
|
355
|
-
handleError(detail, centrifugoConfig);
|
|
356
|
-
};
|
|
357
|
-
window.addEventListener(ERROR_EVENTS.CENTRIFUGO, handler);
|
|
358
|
-
handlers.push({ event: ERROR_EVENTS.CENTRIFUGO, handler });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
342
|
// Runtime errors (generic JS errors from anywhere in the app)
|
|
362
343
|
if (runtimeConfig.enabled) {
|
|
363
344
|
const handler = (event: Event) => {
|
|
@@ -370,7 +351,7 @@ export function ErrorTrackingProvider({
|
|
|
370
351
|
context: event.detail.context,
|
|
371
352
|
timestamp: event.detail.timestamp || new Date(),
|
|
372
353
|
};
|
|
373
|
-
|
|
354
|
+
handleErrorRef.current(detail, runtimeConfig);
|
|
374
355
|
};
|
|
375
356
|
window.addEventListener(ERROR_EVENTS.RUNTIME, handler);
|
|
376
357
|
handlers.push({ event: ERROR_EVENTS.RUNTIME, handler });
|
|
@@ -382,13 +363,15 @@ export function ErrorTrackingProvider({
|
|
|
382
363
|
window.removeEventListener(event, handler);
|
|
383
364
|
});
|
|
384
365
|
};
|
|
385
|
-
|
|
366
|
+
// No `cors` listener: nothing dispatches `cors-error`. A blocked request
|
|
367
|
+
// surfaces as a network error carrying `possibly_cors`, so `corsConfig`
|
|
368
|
+
// configures presentation only and is not a dependency here.
|
|
369
|
+
}, [validationConfig, networkConfig, runtimeConfig]);
|
|
386
370
|
|
|
387
371
|
// Filter errors by type
|
|
388
372
|
const validationErrors = errors.filter((e) => e.type === 'validation') as StoredError<ValidationErrorDetail>[];
|
|
389
373
|
const corsErrors = errors.filter((e) => e.type === 'cors') as StoredError<CORSErrorDetail>[];
|
|
390
374
|
const networkErrors = errors.filter((e) => e.type === 'network') as StoredError<NetworkErrorDetail>[];
|
|
391
|
-
const centrifugoErrors = errors.filter((e) => e.type === 'centrifugo') as StoredError<CentrifugoErrorDetail>[];
|
|
392
375
|
const runtimeErrors = errors.filter((e) => e.type === 'runtime') as StoredError<RuntimeErrorDetail>[];
|
|
393
376
|
|
|
394
377
|
const value: ErrorTrackingContextValue = {
|
|
@@ -396,7 +379,6 @@ export function ErrorTrackingProvider({
|
|
|
396
379
|
validationErrors,
|
|
397
380
|
corsErrors,
|
|
398
381
|
networkErrors,
|
|
399
|
-
centrifugoErrors,
|
|
400
382
|
runtimeErrors,
|
|
401
383
|
clearErrors,
|
|
402
384
|
clearErrorsByType,
|
|
@@ -407,7 +389,6 @@ export function ErrorTrackingProvider({
|
|
|
407
389
|
validation: validationConfig,
|
|
408
390
|
cors: corsConfig,
|
|
409
391
|
network: networkConfig,
|
|
410
|
-
centrifugo: centrifugoConfig,
|
|
411
392
|
runtime: runtimeConfig,
|
|
412
393
|
},
|
|
413
394
|
};
|
|
@@ -28,7 +28,7 @@ export interface ValidationErrorDetail extends BaseErrorDetail {
|
|
|
28
28
|
/** Zod validation error */
|
|
29
29
|
error: ZodError;
|
|
30
30
|
/** Raw response data that failed validation */
|
|
31
|
-
response:
|
|
31
|
+
response: unknown;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -65,21 +65,6 @@ export interface NetworkErrorDetail extends BaseErrorDetail {
|
|
|
65
65
|
possibly_cors?: boolean;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
/**
|
|
69
|
-
* Centrifugo error detail (from centrifugo-error event)
|
|
70
|
-
*/
|
|
71
|
-
export interface CentrifugoErrorDetail extends BaseErrorDetail {
|
|
72
|
-
type: 'centrifugo';
|
|
73
|
-
/** RPC method that failed */
|
|
74
|
-
method: string;
|
|
75
|
-
/** Error message */
|
|
76
|
-
error: string;
|
|
77
|
-
/** Error code from Centrifugo */
|
|
78
|
-
code?: number;
|
|
79
|
-
/** Additional data sent with the request */
|
|
80
|
-
data?: any;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
68
|
/**
|
|
84
69
|
* Every error category the tracker stores.
|
|
85
70
|
*
|
|
@@ -94,7 +79,6 @@ export type TrackedErrorType =
|
|
|
94
79
|
| 'validation'
|
|
95
80
|
| 'cors'
|
|
96
81
|
| 'network'
|
|
97
|
-
| 'centrifugo'
|
|
98
82
|
| 'runtime';
|
|
99
83
|
|
|
100
84
|
/**
|
|
@@ -116,7 +100,7 @@ export interface RuntimeErrorDetail extends BaseErrorDetail {
|
|
|
116
100
|
/**
|
|
117
101
|
* Union type of all error details
|
|
118
102
|
*/
|
|
119
|
-
export type ErrorDetail = ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail |
|
|
103
|
+
export type ErrorDetail = ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail | RuntimeErrorDetail;
|
|
120
104
|
|
|
121
105
|
/**
|
|
122
106
|
* Stored error with unique ID
|
|
@@ -224,23 +208,6 @@ export interface NetworkErrorConfig extends ErrorTypeConfig {
|
|
|
224
208
|
showStatusCode?: boolean;
|
|
225
209
|
}
|
|
226
210
|
|
|
227
|
-
/**
|
|
228
|
-
* Centrifugo error specific config
|
|
229
|
-
*/
|
|
230
|
-
export interface CentrifugoErrorConfig extends ErrorTypeConfig {
|
|
231
|
-
/**
|
|
232
|
-
* Show RPC method in toast
|
|
233
|
-
* @default true
|
|
234
|
-
*/
|
|
235
|
-
showMethod?: boolean;
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Show error code in toast
|
|
239
|
-
* @default true
|
|
240
|
-
*/
|
|
241
|
-
showCode?: boolean;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
211
|
/**
|
|
245
212
|
* Runtime error specific config
|
|
246
213
|
*/
|
|
@@ -271,11 +238,6 @@ export interface ErrorTrackingConfig {
|
|
|
271
238
|
*/
|
|
272
239
|
network?: NetworkErrorConfig;
|
|
273
240
|
|
|
274
|
-
/**
|
|
275
|
-
* Centrifugo error tracking configuration
|
|
276
|
-
*/
|
|
277
|
-
centrifugo?: CentrifugoErrorConfig;
|
|
278
|
-
|
|
279
241
|
/**
|
|
280
242
|
* Runtime error tracking configuration
|
|
281
243
|
*/
|
|
@@ -304,9 +266,6 @@ export interface ErrorTrackingContextValue {
|
|
|
304
266
|
/** Network errors only */
|
|
305
267
|
networkErrors: StoredError<NetworkErrorDetail>[];
|
|
306
268
|
|
|
307
|
-
/** Centrifugo errors only */
|
|
308
|
-
centrifugoErrors: StoredError<CentrifugoErrorDetail>[];
|
|
309
|
-
|
|
310
269
|
/** Runtime errors only */
|
|
311
270
|
runtimeErrors: StoredError<RuntimeErrorDetail>[];
|
|
312
271
|
|
|
@@ -330,7 +289,6 @@ export interface ErrorTrackingContextValue {
|
|
|
330
289
|
validation: Required<ValidationErrorConfig>;
|
|
331
290
|
cors: Required<CORSErrorConfig>;
|
|
332
291
|
network: Required<NetworkErrorConfig>;
|
|
333
|
-
centrifugo: Required<CentrifugoErrorConfig>;
|
|
334
292
|
runtime: Required<RuntimeErrorConfig>;
|
|
335
293
|
};
|
|
336
294
|
}
|
|
@@ -342,8 +300,6 @@ export const ERROR_EVENTS = {
|
|
|
342
300
|
VALIDATION: 'zod-validation-error',
|
|
343
301
|
CORS: 'cors-error',
|
|
344
302
|
NETWORK: 'network-error',
|
|
345
|
-
/** Unified Centrifugo event - filter by detail.type === 'error' */
|
|
346
|
-
CENTRIFUGO: 'centrifugo',
|
|
347
303
|
/** Runtime/generic errors (Tour, components, etc.) */
|
|
348
304
|
RUNTIME: 'runtime-error',
|
|
349
305
|
} as const;
|
|
@@ -381,13 +337,6 @@ export const DEFAULT_NETWORK_CONFIG: Required<NetworkErrorConfig> = {
|
|
|
381
337
|
showStatusCode: true,
|
|
382
338
|
};
|
|
383
339
|
|
|
384
|
-
export const DEFAULT_CENTRIFUGO_CONFIG: Required<CentrifugoErrorConfig> = {
|
|
385
|
-
...DEFAULT_ERROR_CONFIG,
|
|
386
|
-
duration: 0, // Don't auto-dismiss centrifugo errors
|
|
387
|
-
showMethod: true,
|
|
388
|
-
showCode: true,
|
|
389
|
-
};
|
|
390
|
-
|
|
391
340
|
export const DEFAULT_RUNTIME_CONFIG: Required<RuntimeErrorConfig> = {
|
|
392
341
|
...DEFAULT_ERROR_CONFIG,
|
|
393
342
|
duration: 8000,
|
|
@@ -13,7 +13,7 @@ export interface CurlOptions {
|
|
|
13
13
|
method: string;
|
|
14
14
|
path: string;
|
|
15
15
|
token?: string;
|
|
16
|
-
body?:
|
|
16
|
+
body?: unknown;
|
|
17
17
|
headers?: Record<string, string>;
|
|
18
18
|
baseUrl?: string;
|
|
19
19
|
queryParams?: Record<string, string>;
|
|
@@ -126,7 +126,7 @@ export function generateCurl(options: CurlOptions): string {
|
|
|
126
126
|
export function generateCurlFromError(detail: {
|
|
127
127
|
method: string;
|
|
128
128
|
path: string;
|
|
129
|
-
response?:
|
|
129
|
+
response?: unknown;
|
|
130
130
|
}): string {
|
|
131
131
|
return generateCurl({
|
|
132
132
|
method: detail.method,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ZodError } from 'zod';
|
|
8
|
-
import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail,
|
|
8
|
+
import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail, RuntimeErrorDetail, ErrorDetail } from '../types';
|
|
9
9
|
import type { DevtoolsEvent } from '@djangocfg/devtools';
|
|
10
10
|
import { EventType, EventLevel } from '@djangocfg/devtools';
|
|
11
11
|
|
|
@@ -99,22 +99,6 @@ export function formatNetworkErrorForClipboard(detail: NetworkErrorDetail): stri
|
|
|
99
99
|
return JSON.stringify(errorData, null, 2);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* Format centrifugo error for clipboard
|
|
104
|
-
*/
|
|
105
|
-
export function formatCentrifugoErrorForClipboard(detail: CentrifugoErrorDetail): string {
|
|
106
|
-
const errorData = {
|
|
107
|
-
type: 'centrifugo',
|
|
108
|
-
timestamp: detail.timestamp.toISOString(),
|
|
109
|
-
method: detail.method,
|
|
110
|
-
error: detail.error,
|
|
111
|
-
...(detail.code !== undefined && { code: detail.code }),
|
|
112
|
-
...(detail.data && { data: detail.data }),
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
return JSON.stringify(errorData, null, 2);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
102
|
/**
|
|
119
103
|
* Extract domain from URL
|
|
120
104
|
*/
|
|
@@ -160,8 +144,6 @@ export function errorDetailToDevtoolsEvent(detail: ErrorDetail): DevtoolsEvent {
|
|
|
160
144
|
return { event_type: EventType.NETWORK_ERROR, level: EventLevel.ERROR, message: `CORS: ${detail.method} ${detail.url}`, http_method: detail.method, http_url: detail.url, url }
|
|
161
145
|
case 'network':
|
|
162
146
|
return { event_type: EventType.NETWORK_ERROR, level: detail.statusCode && detail.statusCode >= 500 ? EventLevel.ERROR : EventLevel.WARNING, message: `${detail.method} ${detail.url} → ${detail.statusCode ?? 'ERR'}`, http_method: detail.method, http_url: detail.url, http_status: detail.statusCode, url }
|
|
163
|
-
case 'centrifugo':
|
|
164
|
-
return { event_type: EventType.ERROR, level: EventLevel.ERROR, message: `Centrifugo ${detail.method}: ${detail.error}`, url }
|
|
165
147
|
case 'runtime':
|
|
166
148
|
return { event_type: EventType.JS_ERROR, level: EventLevel.ERROR, message: detail.message, stack_trace: detail.error?.stack, url }
|
|
167
149
|
}
|
|
@@ -170,7 +152,7 @@ export function errorDetailToDevtoolsEvent(detail: ErrorDetail): DevtoolsEvent {
|
|
|
170
152
|
/**
|
|
171
153
|
* Format error title based on type (dev/debug mode — full details)
|
|
172
154
|
*/
|
|
173
|
-
export function formatErrorTitle(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail |
|
|
155
|
+
export function formatErrorTitle(detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail | RuntimeErrorDetail): string {
|
|
174
156
|
switch (detail.type) {
|
|
175
157
|
case 'validation':
|
|
176
158
|
return `❌ Validation Error in ${detail.operation}`;
|
|
@@ -180,10 +162,6 @@ export function formatErrorTitle(detail: ValidationErrorDetail | CORSErrorDetail
|
|
|
180
162
|
return detail.statusCode
|
|
181
163
|
? `⚠️ Network Error (${detail.statusCode})`
|
|
182
164
|
: '⚠️ Network Error';
|
|
183
|
-
case 'centrifugo':
|
|
184
|
-
return detail.code !== undefined
|
|
185
|
-
? `🔌 Centrifugo Error (${detail.code})`
|
|
186
|
-
: '🔌 Centrifugo Error';
|
|
187
165
|
case 'runtime':
|
|
188
166
|
return `⚠️ ${detail.source} Error`;
|
|
189
167
|
default:
|
|
@@ -242,11 +220,6 @@ export function getProductionToast(detail: ErrorDetail, errorId: string): { titl
|
|
|
242
220
|
description: `Please check your connection and try again. Reference: ${errorId}`,
|
|
243
221
|
};
|
|
244
222
|
}
|
|
245
|
-
case 'centrifugo':
|
|
246
|
-
return {
|
|
247
|
-
title: 'Real-time connection error',
|
|
248
|
-
description: `Live updates may be delayed. Reference: ${errorId}`,
|
|
249
|
-
};
|
|
250
223
|
case 'runtime':
|
|
251
224
|
return {
|
|
252
225
|
title: 'Something went wrong',
|