@djangocfg/layouts 2.1.541 → 2.1.542

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 (33) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -17
  3. package/src/components/core/JsonLd.tsx +1 -1
  4. package/src/components/errors/ErrorsTracker/components/ErrorButtons.tsx +4 -7
  5. package/src/components/errors/ErrorsTracker/components/ErrorToast.tsx +3 -41
  6. package/src/components/errors/ErrorsTracker/index.ts +1 -4
  7. package/src/components/errors/ErrorsTracker/providers/ErrorTrackingProvider.tsx +40 -59
  8. package/src/components/errors/ErrorsTracker/types.ts +2 -53
  9. package/src/components/errors/ErrorsTracker/utils/curl-generator.ts +2 -2
  10. package/src/components/errors/ErrorsTracker/utils/formatters.ts +2 -29
  11. package/src/components/errors/README.md +1 -1
  12. package/src/configurator/auth/schema.ts +1 -1
  13. package/src/configurator/index.ts +1 -1
  14. package/src/configurator/private/schema.ts +1 -1
  15. package/src/layouts/AppLayout/BaseApp.tsx +25 -43
  16. package/src/layouts/AppLayout/README.md +1 -2
  17. package/src/layouts/AuthLayout/AuthLayout.tsx +6 -6
  18. package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +2 -1
  19. package/src/layouts/AuthLayout/context.tsx +0 -1
  20. package/src/layouts/AuthLayout/types.ts +6 -3
  21. package/src/layouts/PrivateLayout/PrivateLayout.tsx +2 -7
  22. package/src/layouts/PrivateLayout/components/PrivateSidebarAccount.tsx +1 -1
  23. package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +3 -8
  24. package/src/layouts/PrivateLayout/components/SidebarFeatured.tsx +10 -6
  25. package/src/layouts/PublicLayout/components/UserMenu.tsx +0 -1
  26. package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +1 -1
  27. package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +5 -1
  28. package/src/layouts/PublicLayout/shared/NavbarShell.tsx +6 -3
  29. package/src/layouts/types/index.ts +0 -1
  30. package/src/layouts/types/layout.types.ts +1 -4
  31. package/src/layouts/types/providers.types.ts +1 -14
  32. package/src/snippets/Breadcrumbs.tsx +3 -3
  33. package/src/testing/MockAuthFormProvider.tsx +0 -1
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/ui-tools`. |
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.541",
3
+ "version": "2.1.542",
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.541",
93
- "@djangocfg/api": "^2.1.541",
94
- "@djangocfg/centrifugo": "^2.1.541",
95
- "@djangocfg/devtools": "^2.1.541",
96
- "@djangocfg/i18n": "^2.1.541",
97
- "@djangocfg/ui-core": "^2.1.541",
93
+ "@djangocfg/analytics": "^2.1.542",
94
+ "@djangocfg/api": "^2.1.542",
95
+ "@djangocfg/devtools": "^2.1.542",
96
+ "@djangocfg/i18n": "^2.1.542",
97
+ "@djangocfg/ui-core": "^2.1.542",
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.2.4",
106
- "react-dom": "^19.2.4",
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.541",
128
- "@djangocfg/api": "^2.1.541",
129
- "@djangocfg/centrifugo": "^2.1.541",
130
- "@djangocfg/devtools": "^2.1.541",
131
- "@djangocfg/i18n": "^2.1.541",
132
- "@djangocfg/typescript-config": "^2.1.541",
133
- "@djangocfg/ui-core": "^2.1.541",
134
- "@djangocfg/ui-tools": "^2.1.541",
127
+ "@djangocfg/analytics": "^2.1.542",
128
+ "@djangocfg/api": "^2.1.542",
129
+ "@djangocfg/devtools": "^2.1.542",
130
+ "@djangocfg/i18n": "^2.1.542",
131
+ "@djangocfg/typescript-config": "^2.1.542",
132
+ "@djangocfg/ui-core": "^2.1.542",
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
  },
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
 
3
3
  interface JsonLdProps {
4
- data: Record<string, any> | Record<string, any>[];
4
+ data: Record<string, unknown> | Record<string, unknown>[];
5
5
  }
6
6
 
7
7
  /**
@@ -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, CentrifugoErrorDetail } from '../types';
22
+ import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail } from '../types';
24
23
  export interface ErrorButtonsProps {
25
- detail: ValidationErrorDetail | CORSErrorDetail | NetworkErrorDetail | CentrifugoErrorDetail;
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 | CentrifugoErrorDetail): string {
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 | CentrifugoErrorDetail): boolean {
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-amber-500/10 px-2 py-1 text-xs text-amber-600 dark:text-amber-400">
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 | CentrifugoErrorDetail | RuntimeErrorDetail,
202
- config: Required<ValidationErrorConfig | CORSErrorConfig | NetworkErrorConfig | CentrifugoErrorConfig | RuntimeErrorConfig>
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, Centrifugo, and runtime errors
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
- // Merge user configs with defaults
198
- const validationConfig: Required<ValidationErrorConfig> = {
199
- ...DEFAULT_VALIDATION_CONFIG,
200
- ...userValidationConfig,
201
- };
202
-
203
- const corsConfig: Required<CORSErrorConfig> = {
204
- ...DEFAULT_CORS_CONFIG,
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 networkConfig: Required<NetworkErrorConfig> = {
209
- ...DEFAULT_NETWORK_CONFIG,
210
- ...userNetworkConfig,
211
- };
199
+ const corsConfig: Required<CORSErrorConfig> = useMemo(
200
+ () => ({ ...DEFAULT_CORS_CONFIG, ...userCorsConfig }),
201
+ [userCorsConfig]
202
+ );
212
203
 
213
- const centrifugoConfig: Required<CentrifugoErrorConfig> = {
214
- ...DEFAULT_CENTRIFUGO_CONFIG,
215
- ...userCentrifugoConfig,
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
- ...userRuntimeConfig,
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 | CentrifugoErrorConfig | RuntimeErrorConfig>) => {
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
- [onError, onMonitorCapture, isProduction, isDebugMode]
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
- handleError(detail, validationConfig);
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
- handleError(detail, networkConfig);
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
- handleError(detail, runtimeConfig);
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
- }, [handleError, validationConfig, corsConfig, networkConfig, centrifugoConfig, runtimeConfig]);
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: any;
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 | CentrifugoErrorDetail | RuntimeErrorDetail;
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?: any;
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?: any;
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, CentrifugoErrorDetail, RuntimeErrorDetail, ErrorDetail } from '../types';
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 | CentrifugoErrorDetail | RuntimeErrorDetail): string {
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',
@@ -48,7 +48,7 @@ This is a *user-facing recovery* path. `ErrorsTracker` below is the
48
48
  the generated API client and runtime, then renders a Sonner toast per error.
49
49
 
50
50
  Tracked error types: `validation` (zod response mismatch), `network` (failed/4xx/5xx
51
- requests, incl. a CORS heuristic), `centrifugo` (WS), and `runtime` (uncaught JS).
51
+ requests, incl. a CORS heuristic), and `runtime` (uncaught JS).
52
52
 
53
53
  ```tsx
54
54
  import { ErrorTrackingProvider } from '@djangocfg/layouts'; // (or the module path)
@@ -6,7 +6,7 @@
6
6
  * - `authSplitSchema` / `authSplitUiSchema` — two-column card with sidebar
7
7
  *
8
8
  * Pair with `<JsonSchemaForm density="compact" schema={...} uiSchema={...}>`
9
- * from `@djangocfg/ui-tools` to get a ready-made sidebar configurator.
9
+ * from `@djangocfg/widget-forms` to get a ready-made sidebar configurator.
10
10
  */
11
11
 
12
12
  import type { CustomJsonSchema7, CustomJsonUiSchema7 } from '@djangocfg/ui-core/lib';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * JSON Schema descriptors for `<JsonSchemaForm>`-driven layout configurators.
3
3
  *
4
- * Drop a schema + uiSchema into `@djangocfg/ui-tools`'s `<JsonSchemaForm>` and
4
+ * Drop a schema + uiSchema into `@djangocfg/widget-forms`'s `<JsonSchemaForm>` and
5
5
  * you get a ready-made sidebar that edits the matching `<Layout>` props live.
6
6
  *
7
7
  * Public surface today:
@@ -8,7 +8,7 @@
8
8
  * - `header.*` → fields consumed by `HeaderConfig`
9
9
  *
10
10
  * Pair with `<JsonSchemaForm density="compact" schema={...} uiSchema={...}>`
11
- * from `@djangocfg/ui-tools` to get a ready-made sidebar configurator. The
11
+ * from `@djangocfg/widget-forms` to get a ready-made sidebar configurator. The
12
12
  * `defaultPrivateLayoutConfiguratorData` is a safe initial value that already
13
13
  * matches the schema (no nullable fields, no missing keys).
14
14
  *
@@ -15,7 +15,6 @@
15
15
  * - AuthProvider (authentication context)
16
16
  * - AuthDialog (global auth prompt dialog)
17
17
  * - AnalyticsProvider (Google Analytics, optional)
18
- * - CentrifugoProvider (WebSocket real-time, optional)
19
18
  * - PwaProvider (PWA installation, optional)
20
19
  * - ErrorTrackingProvider (error handling)
21
20
  * - ErrorBoundary (React error boundary, enabled by default)
@@ -49,9 +48,7 @@ import { SWRConfig } from 'swr';
49
48
 
50
49
  import { Devtools, devtools } from '@djangocfg/devtools';
51
50
  import { errorDetailToDevtoolsEvent } from '../../components/errors/ErrorsTracker';
52
- import { CfgCentrifugo } from '@djangocfg/api';
53
51
  import { AuthProvider } from '@djangocfg/api/auth';
54
- import { CentrifugoProvider } from '@djangocfg/centrifugo';
55
52
  import { UiProviders } from '@djangocfg/ui-core/providers';
56
53
  import { NextRouterAdapter, NextLinkProvider } from '@djangocfg/ui-core/adapters/nextjs';
57
54
  import { NextIntlLinkBridge } from './NextIntlLinkBridge';
@@ -119,7 +116,7 @@ function AppErrorFallback({
119
116
  * BaseApp - Core providers wrapper for any React/Next.js app
120
117
  *
121
118
  * Includes: ThemeProvider, TooltipProvider, SWRConfig, AuthProvider, AnalyticsProvider,
122
- * CentrifugoProvider, ErrorTrackingProvider, ErrorBoundary (optional)
119
+ * ErrorTrackingProvider, ErrorBoundary (optional)
123
120
  * Also renders global Toaster and PageProgress components
124
121
  */
125
122
  export function BaseApp({
@@ -128,7 +125,6 @@ export function BaseApp({
128
125
  theme,
129
126
  auth,
130
127
  analytics,
131
- centrifugo,
132
128
  errorTracking,
133
129
  errorBoundary,
134
130
  swr,
@@ -142,10 +138,6 @@ export function BaseApp({
142
138
  // ErrorBoundary is enabled by default
143
139
  const enableErrorBoundary = errorBoundary?.enabled !== false;
144
140
 
145
- // Centrifugo configuration
146
- const centrifugoUrl = centrifugo?.url || process.env.NEXT_PUBLIC_CENTRIFUGO_URL;
147
- const centrifugoEnabled = centrifugo?.enabled !== false;
148
-
149
141
  // Devtools — capture + ingest + debug panel in one component.
150
142
  // Project prop is the default; the devtools prop overrides field by field.
151
143
  const devtoolsProps = {
@@ -164,7 +156,7 @@ export function BaseApp({
164
156
  * (useNavigate, useQueryState, etc.) to next/navigation so
165
157
  * server components, route loaders, and prefetch fire
166
158
  * correctly. Placed near the top so every provider below
167
- * (Auth, Centrifugo, anything that may navigate) gets it.
159
+ * (Auth, anything that may navigate) gets it.
168
160
  */}
169
161
  <NextRouterAdapter>
170
162
  <NextLinkProvider>
@@ -192,41 +184,31 @@ export function BaseApp({
192
184
  >
193
185
  <AuthProvider config={authEnabled ? authConfig : undefined} enabled={authEnabled}>
194
186
  <AnalyticsProvider trackingId={analytics?.googleTrackingId}>
195
- <CentrifugoProvider
196
- enabled={centrifugoEnabled}
197
- autoConnect={centrifugoEnabled && centrifugo?.autoConnect}
198
- url={centrifugoUrl}
199
- onTokenRefresh={async () => {
200
- const result = await CfgCentrifugo.cfgCentrifugoAuthTokenRetrieve({ throwOnError: true });
201
- return result.data.token;
202
- }}
187
+ <ErrorTrackingProvider
188
+ validation={errorTracking?.validation}
189
+ cors={errorTracking?.cors}
190
+ network={errorTracking?.network}
191
+ onError={errorTracking?.onError}
192
+ onMonitorCapture={(d) => devtools.capture(errorDetailToDevtoolsEvent(d))}
203
193
  >
204
- <ErrorTrackingProvider
205
- validation={errorTracking?.validation}
206
- cors={errorTracking?.cors}
207
- network={errorTracking?.network}
208
- onError={errorTracking?.onError}
209
- onMonitorCapture={(d) => devtools.capture(errorDetailToDevtoolsEvent(d))}
210
- >
211
- <Devtools {...devtoolsProps} />
212
- <LayoutI18nProvider value={i18n}>
213
- {i18n?.routing ? (
214
- <NextIntlLinkBridge routing={i18n.routing}>{children}</NextIntlLinkBridge>
215
- ) : (
216
- children
217
- )}
218
- </LayoutI18nProvider>
219
- <NextTopLoader
220
- color="var(--primary)"
221
- height={3}
222
- showSpinner={false}
223
- shadow="0 0 10px var(--primary), 0 0 5px var(--primary)"
224
- />
194
+ <Devtools {...devtoolsProps} />
195
+ <LayoutI18nProvider value={i18n}>
196
+ {i18n?.routing ? (
197
+ <NextIntlLinkBridge routing={i18n.routing}>{children}</NextIntlLinkBridge>
198
+ ) : (
199
+ children
200
+ )}
201
+ </LayoutI18nProvider>
202
+ <NextTopLoader
203
+ color="var(--primary)"
204
+ height={3}
205
+ showSpinner={false}
206
+ shadow="0 0 10px var(--primary), 0 0 5px var(--primary)"
207
+ />
225
208
 
226
- {/* Auth Dialog - only when auth is enabled */}
227
- {authEnabled && <AuthDialog authPath={authConfig?.routes?.auth} />}
228
- </ErrorTrackingProvider>
229
- </CentrifugoProvider>
209
+ {/* Auth Dialog - only when auth is enabled */}
210
+ {authEnabled && <AuthDialog authPath={authConfig?.routes?.auth} />}
211
+ </ErrorTrackingProvider>
230
212
  </AnalyticsProvider>
231
213
  </AuthProvider>
232
214
  </SWRConfig>
@@ -25,7 +25,7 @@ export default function RootLayout({ children }) {
25
25
  theme={{ defaultTheme: 'system', storageKey: 'myapp-theme' }}
26
26
  auth={{ apiUrl: process.env.NEXT_PUBLIC_API_URL,
27
27
  routes: { auth: '/auth', defaultCallback: '/private' } }}
28
- // analytics / centrifugo / monitor / errorTracking / debug — all optional
28
+ // analytics / monitor / errorTracking / debug — all optional
29
29
  >
30
30
  {children}
31
31
  </BaseApp>
@@ -78,7 +78,6 @@ interface BaseAppProps {
78
78
  theme?: ThemeConfig; // defaultTheme, storageKey, style preset
79
79
  auth?: AuthConfig | false; // false = no AuthProvider (fully public app)
80
80
  analytics?: AnalyticsConfig; // Google Analytics
81
- centrifugo?: CentrifugoConfig;
82
81
  errorTracking?: ErrorTrackingConfig;
83
82
  errorBoundary?: ErrorBoundaryConfig; // enabled by default
84
83
  swr?: SWRConfigOptions;
@@ -104,7 +104,7 @@ export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
104
104
  * where a person stopped in the layout, and quietly becomes a no-op when an
105
105
  * app intentionally does not mount AnalyticsProvider.
106
106
  */
107
- const AuthFunnelTracker: React.FC = memo(() => {
107
+ const AuthFunnelTracker: React.FC = memo(function AuthFunnelTracker() {
108
108
  const { step } = useAuthFormContext();
109
109
  const { event, isEnabled } = useAnalytics();
110
110
  const lastStep = React.useRef<string | null>(null);
@@ -119,13 +119,13 @@ const AuthFunnelTracker: React.FC = memo(() => {
119
119
  });
120
120
 
121
121
  /** Renders custom children only on the identifier step — hides them on otp / 2fa / etc. */
122
- const AuthHeaderSlot: React.FC<{ children?: React.ReactNode }> = memo(({ children }) => {
122
+ const AuthHeaderSlot: React.FC<{ children?: React.ReactNode }> = memo(function AuthHeaderSlot({ children }) {
123
123
  const { step } = useAuthFormContext();
124
124
  if (!children || step !== 'identifier') return null;
125
125
  return <>{children}</>;
126
126
  });
127
127
 
128
- const AuthContent: React.FC = memo(() => {
128
+ const AuthContent: React.FC = memo(function AuthContent() {
129
129
  const { step } = useAuthFormContext();
130
130
 
131
131
  switch (step) {
@@ -145,7 +145,7 @@ const AuthContent: React.FC = memo(() => {
145
145
  }
146
146
  });
147
147
 
148
- const AuthSuccessOverlay: React.FC = memo(() => {
148
+ const AuthSuccessOverlay: React.FC = memo(function AuthSuccessOverlay() {
149
149
  const { step, logo, redirectUrl } = useAuthFormContext();
150
150
 
151
151
  if (step !== 'success') {
@@ -162,11 +162,11 @@ interface AuthSuccessInlineProps {
162
162
  redirectDelay?: number;
163
163
  }
164
164
 
165
- const AuthSuccess: React.FC<AuthSuccessInlineProps> = memo(({
165
+ const AuthSuccess: React.FC<AuthSuccessInlineProps> = memo(function AuthSuccess({
166
166
  logo,
167
167
  redirectUrl,
168
168
  redirectDelay = AUTH.REDIRECT_DELAY,
169
- }) => {
169
+ }) {
170
170
  const router = useCfgRouter();
171
171
  const { routes } = useAuth();
172
172
  const t = useAppT();
@@ -12,7 +12,8 @@ import {
12
12
  import { useAuthFormContext } from '../../context';
13
13
 
14
14
  export interface OAuthCallbackProps {
15
- onSuccess?: (user: any, isNewUser: boolean, provider: string) => void;
15
+ /** `user` is the provider callback's user payload, untyped upstream — narrow it before reading fields. */
16
+ onSuccess?: (user: unknown, isNewUser: boolean, provider: string) => void;
16
17
  onError?: (error: string) => void;
17
18
  redirectUrl?: string;
18
19
  }
@@ -67,7 +67,6 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
67
67
  marketingConsentConfig: marketingConsent,
68
68
  rememberMeConfig: rememberMe,
69
69
  }),
70
- // eslint-disable-next-line react-hooks/exhaustive-deps
71
70
  [
72
71
  authForm,
73
72
  sourceUrl,
@@ -20,7 +20,7 @@ export type {
20
20
 
21
21
  import type { AuthFormReturn } from '@djangocfg/api/auth';
22
22
 
23
- import type { AuthBackgroundConfig, AuthShellVariant, AuthShellContextValue } from './shells';
23
+ import type { AuthBackgroundConfig, AuthShellVariant } from './shells';
24
24
 
25
25
  // Re-export shell types so consumers can import everything from ./types
26
26
  export type { AuthBackgroundConfig, AuthShellVariant, AuthShellContextValue } from './shells';
@@ -114,8 +114,11 @@ export interface AuthLayoutProps extends AuthLayoutConfig {
114
114
  onIdentifierSuccess?: (identifier: string) => void;
115
115
  /** Callback when OTP verification succeeds */
116
116
  onOTPSuccess?: () => void;
117
- /** Callback when OAuth succeeds */
118
- onOAuthSuccess?: (user: any, isNewUser: boolean, provider: string) => void;
117
+ /**
118
+ * Callback when OAuth succeeds. `user` is the provider callback's user
119
+ * payload, untyped upstream — narrow it before reading fields.
120
+ */
121
+ onOAuthSuccess?: (user: unknown, isNewUser: boolean, provider: string) => void;
119
122
  /** Callback on any error */
120
123
  onError?: (message: string) => void;
121
124
  }
@@ -8,22 +8,17 @@
8
8
 
9
9
  'use client';
10
10
 
11
- import React, { ReactNode } from 'react';
11
+ import React from 'react';
12
12
 
13
13
  import { Preloader } from '@djangocfg/ui-core/components';
14
14
  import { SidebarInset, SidebarProvider } from '@djangocfg/ui-core/components';
15
15
 
16
- import type { LayoutVisualConfig } from '../types';
17
16
  import { PrivateContent, PrivateSidebar } from './components';
18
17
  import { useAuthGuard } from './hooks';
19
18
  import { useLayoutVisual } from './hooks';
20
19
  import { useSidebarDefaultOpen } from './hooks';
21
20
 
22
- import type {
23
- HeaderConfig,
24
- PrivateLayoutProps,
25
- SidebarConfig,
26
- } from './types';
21
+ import type { PrivateLayoutProps } from './types';
27
22
 
28
23
  export type {
29
24
  SidebarItem,
@@ -219,7 +219,7 @@ function PrivateSidebarAccountRaw({ header }: PrivateSidebarAccountProps) {
219
219
  // Always open UPWARD over the avatar (Claude pattern). The footer sits at the
220
220
  // bottom of the rail in both expanded and collapsed states, so `top` reads
221
221
  // correctly either way (vs. `right`, which floated off to the side).
222
- const dropdownSide: 'top' = 'top';
222
+ const dropdownSide = 'top' as const;
223
223
  const avatarClass = cn(
224
224
  'h-9 w-9 shrink-0 border border-transparent transition-colors',
225
225
  'group-hover:border-sidebar-border/70',
@@ -17,6 +17,8 @@ import { usePrivateLayoutContext } from '../context';
17
17
  import { useShellVisualState } from '../hooks';
18
18
  import { SidebarBrandSwitcher } from './SidebarBrandSwitcher';
19
19
 
20
+ const HEADER_ROW_CLASS = 'flex items-center gap-2 mb-2';
21
+
20
22
  function SidebarBrandRaw() {
21
23
  const { header, homeHref, brandTitle, brandMonogram, isMobile } =
22
24
  usePrivateLayoutContext();
@@ -39,14 +41,7 @@ function SidebarBrandRaw() {
39
41
 
40
42
  const customBrand = header?.brand;
41
43
 
42
- const headerRowClass = useMemo(
43
- () =>
44
- cn(
45
- 'flex items-center gap-2 mb-2',
46
- // content.showLabels ? 'px-2' : 'px-1.5',
47
- ),
48
- [content.showLabels],
49
- );
44
+ const headerRowClass = HEADER_ROW_CLASS;
50
45
 
51
46
  const expandedHeader = useMemo(
52
47
  () => (
@@ -15,17 +15,21 @@ import { LucideIcon } from '../../../components';
15
15
 
16
16
  import type { SidebarFeaturedConfig } from '../types';
17
17
 
18
+ // The accent names are the public prop's vocabulary; the status tokens they map
19
+ // to carry the meaning and already define both themes, so no `dark:` pair here.
18
20
  const ACCENT_CLASS: Record<NonNullable<SidebarFeaturedConfig['accent']>, string> = {
19
- green: 'bg-emerald-500/10 text-emerald-700 hover:bg-emerald-500/15 dark:text-emerald-300 dark:bg-emerald-400/10 dark:hover:bg-emerald-400/15',
20
- blue: 'bg-sky-500/10 text-sky-700 hover:bg-sky-500/15 dark:text-sky-300 dark:bg-sky-400/10 dark:hover:bg-sky-400/15',
21
- amber: 'bg-amber-500/10 text-amber-800 hover:bg-amber-500/15 dark:text-amber-300 dark:bg-amber-400/10 dark:hover:bg-amber-400/15',
21
+ green: 'bg-success/10 text-success-foreground hover:bg-success/15',
22
+ blue: 'bg-info/10 text-info-foreground hover:bg-info/15',
23
+ amber: 'bg-warning/10 text-warning-foreground hover:bg-warning/15',
22
24
  primary: 'bg-primary/10 text-primary hover:bg-primary/15',
23
25
  };
24
26
 
27
+ // Solid fill, so the text is the on-fill contrast color — `*-foreground` is the
28
+ // tint for the soft surface above and fails contrast on the fill.
25
29
  const BADGE_ACCENT_CLASS: Record<NonNullable<SidebarFeaturedConfig['accent']>, string> = {
26
- green: 'bg-emerald-500 text-white dark:bg-emerald-400 dark:text-emerald-950',
27
- blue: 'bg-sky-500 text-white dark:bg-sky-400 dark:text-sky-950',
28
- amber: 'bg-amber-500 text-white dark:bg-amber-400 dark:text-amber-950',
30
+ green: 'bg-success text-on-success',
31
+ blue: 'bg-info text-on-info',
32
+ amber: 'bg-warning text-on-warning',
29
33
  primary: 'bg-primary text-primary-foreground',
30
34
  };
31
35
 
@@ -156,7 +156,6 @@ export function UserMenu({
156
156
 
157
157
  // Authenticated user
158
158
  const displayName = user.display_username || user.email || 'User';
159
- const userInitial = displayName.charAt(0).toUpperCase();
160
159
  const userAvatar = user.avatar || '';
161
160
 
162
161
  if (variant === 'mobile') {
@@ -6,7 +6,7 @@
6
6
 
7
7
  'use client';
8
8
 
9
- import React, { memo, useEffect, useMemo, useState } from 'react';
9
+ import React, { memo, useEffect, useState } from 'react';
10
10
  import { Laptop, Moon, Sun } from 'lucide-react';
11
11
 
12
12
  import { Button } from '@djangocfg/ui-core/components';
@@ -40,11 +40,15 @@ export interface MobileDrawerShellProps {
40
40
  };
41
41
  }
42
42
 
43
+ // Module-level so the "no navigation" case keeps one identity: a fresh `[]` per
44
+ // render would make `mobileNavigation`'s memo recompute on every render.
45
+ const NO_NAVIGATION: NavigationItem[] = [];
46
+
43
47
  export function MobileDrawerShell(props: MobileDrawerShellProps) {
44
48
  const context = usePublicLayoutOptional();
45
49
  const mobileMenuOpen = props.isOpen ?? context?.mobileMenuOpen ?? false;
46
50
  const closeMobileMenu = props.onClose ?? context?.closeMobileMenu ?? (() => {});
47
- const navigation = props.navigation ?? [];
51
+ const navigation = props.navigation ?? NO_NAVIGATION;
48
52
  const userMenu = props.userMenu;
49
53
 
50
54
  const { isAuthenticated, user, routes } = useAuth();
@@ -259,10 +259,13 @@ function NavbarShellRaw(props: NavbarShellProps) {
259
259
  // this being present — same contract `NavControls` applies internally.
260
260
  const i18n = useLayoutI18nOptional();
261
261
 
262
- // Close dropdowns when switching to mobile
262
+ // Close dropdowns when switching to mobile. Depends on `closeDropdown` alone,
263
+ // not on `dropdown`: the object changes identity whenever a menu opens, and
264
+ // depending on it would close the dropdown the moment it is opened.
265
+ const { closeDropdown } = dropdown;
263
266
  useEffect(() => {
264
- if (isTabletOrBelow) dropdown.closeDropdown();
265
- }, [isTabletOrBelow, dropdown.closeDropdown]);
267
+ if (isTabletOrBelow) closeDropdown();
268
+ }, [isTabletOrBelow, closeDropdown]);
266
269
 
267
270
  // Sync navbar surface into context
268
271
  const setNavbarSurface = context?.setNavbarSurface;
@@ -14,7 +14,6 @@ export type {
14
14
  ThemeConfig,
15
15
  ThemeStylePresetId,
16
16
  SWRConfigOptions,
17
- CentrifugoConfig,
18
17
  } from './providers.types';
19
18
 
20
19
  // External provider configs (re-export for convenience)
@@ -21,7 +21,7 @@ import type { ErrorBoundaryConfig, ErrorTrackingConfig } from '../../components/
21
21
  export type NextIntlRouting = object & { __nextIntlRouting?: never };
22
22
 
23
23
  // Import local provider configs
24
- import type { ThemeConfig, SWRConfigOptions, CentrifugoConfig } from './providers.types';
24
+ import type { ThemeConfig, SWRConfigOptions } from './providers.types';
25
25
  import type { DevtoolsProps } from '@djangocfg/devtools';
26
26
 
27
27
  // ============================================================================
@@ -49,9 +49,6 @@ export interface BaseLayoutProps {
49
49
  /** Analytics configuration (from snippets/Analytics) */
50
50
  analytics?: AnalyticsConfig;
51
51
 
52
- /** Centrifugo configuration */
53
- centrifugo?: CentrifugoConfig;
54
-
55
52
  /** Error tracking configuration (from components/errors) */
56
53
  errorTracking?: ErrorTrackingConfig;
57
54
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Provider Configuration Types
3
3
  *
4
- * Configuration types for layout providers (theme, SWR, MCP chat, Centrifugo)
4
+ * Configuration types for layout providers (theme, SWR, MCP chat)
5
5
  * Note: Analytics, PWA, Push, and Error types are defined in their respective modules
6
6
  */
7
7
 
@@ -25,16 +25,3 @@ export interface SWRConfigOptions {
25
25
  revalidateOnReconnect?: boolean;
26
26
  dedupingInterval?: number;
27
27
  }
28
-
29
- // ============================================================================
30
- // Centrifugo Configuration
31
- // ============================================================================
32
-
33
- export interface CentrifugoConfig {
34
- /** Enable Centrifugo (default: true) */
35
- enabled?: boolean;
36
- /** Auto-connect on mount (default: false) */
37
- autoConnect?: boolean;
38
- /** Centrifugo server URL */
39
- url?: string;
40
- }
@@ -42,18 +42,18 @@ const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ items, className = "" }) => {
42
42
  {breadcrumbs.map((item: BreadcrumbItem, index: number) => (
43
43
  <li key={item.path} className="flex items-center">
44
44
  {index > 0 && (
45
- <ChevronRight className="w-4 h-4 text-gray-400 mx-2" />
45
+ <ChevronRight className="w-4 h-4 text-muted-foreground mx-2" />
46
46
  )}
47
47
 
48
48
  {item.isActive ? (
49
- <span className="text-gray-900 dark:text-white font-medium flex items-center gap-1">
49
+ <span className="text-foreground font-medium flex items-center gap-1">
50
50
  {index === 0 && <Home className="w-4 h-4" />}
51
51
  {item.label}
52
52
  </span>
53
53
  ) : (
54
54
  <Link
55
55
  href={item.path}
56
- className="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200
56
+ className="text-muted-foreground hover:text-foreground
57
57
  transition-colors duration-200 flex items-center gap-1 hover:underline"
58
58
  >
59
59
  {index === 0 && <Home className="w-4 h-4" />}
@@ -3,7 +3,6 @@
3
3
  import React, { useMemo, useRef, useState } from 'react';
4
4
 
5
5
  import type {
6
- AuthFormReturn,
7
6
  AuthStep,
8
7
  WebmailLink,
9
8
  } from '@djangocfg/api/auth';