@djangocfg/layouts 2.1.226 → 2.1.228

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 (97) hide show
  1. package/README.md +3 -17
  2. package/package.json +18 -18
  3. package/src/components/errors/ErrorLayout.tsx +2 -2
  4. package/src/components/errors/ErrorsTracker/index.ts +1 -0
  5. package/src/components/errors/ErrorsTracker/utils/formatters.ts +23 -1
  6. package/src/hooks/useLogout.ts +9 -12
  7. package/src/layouts/AppLayout/AppLayout.tsx +20 -8
  8. package/src/layouts/AppLayout/BaseApp.tsx +5 -28
  9. package/src/layouts/AuthLayout/AuthLayout.tsx +51 -22
  10. package/src/layouts/AuthLayout/README.md +78 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +2 -2
  12. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -2
  13. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +2 -2
  14. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +3 -2
  15. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +4 -1
  16. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +2 -2
  17. package/src/layouts/AuthLayout/components/shared/index.ts +0 -2
  18. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +25 -80
  19. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +8 -13
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +2 -2
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +2 -2
  22. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +2 -2
  23. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +61 -42
  24. package/src/layouts/AuthLayout/context.tsx +0 -2
  25. package/src/layouts/AuthLayout/index.ts +9 -6
  26. package/src/layouts/AuthLayout/styles/auth.css +265 -120
  27. package/src/layouts/AuthLayout/types.ts +60 -7
  28. package/src/layouts/ProfileLayout/.claude/.sidecar/activity.jsonl +2 -0
  29. package/src/layouts/ProfileLayout/.claude/.sidecar/history/2026-03-15.md +35 -0
  30. package/src/layouts/ProfileLayout/.claude/.sidecar/review.md +35 -0
  31. package/src/layouts/ProfileLayout/.claude/.sidecar/scan.log +3 -0
  32. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-001.md +18 -0
  33. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-002.md +19 -0
  34. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-003.md +18 -0
  35. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-004.md +18 -0
  36. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-005.md +18 -0
  37. package/src/layouts/ProfileLayout/.claude/.sidecar/usage.json +5 -0
  38. package/src/layouts/ProfileLayout/ProfileLayout.tsx +52 -403
  39. package/src/layouts/ProfileLayout/components/ActionButton.tsx +38 -0
  40. package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +109 -148
  41. package/src/layouts/ProfileLayout/components/EditableField.tsx +119 -0
  42. package/src/layouts/ProfileLayout/components/Section.tsx +22 -0
  43. package/src/layouts/ProfileLayout/components/index.ts +4 -1
  44. package/src/layouts/ProfileLayout/context.tsx +31 -0
  45. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
  46. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
  47. package/src/layouts/_components/UserMenu.tsx +2 -2
  48. package/src/layouts/types/README.md +0 -20
  49. package/src/layouts/types/index.ts +2 -2
  50. package/src/layouts/types/layout.types.ts +2 -5
  51. package/src/layouts/types/providers.types.ts +0 -27
  52. package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
  53. package/src/snippets/Breadcrumbs.tsx +2 -2
  54. package/src/snippets/index.ts +0 -67
  55. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +0 -56
  56. package/src/snippets/McpChat/README.md +0 -441
  57. package/src/snippets/McpChat/components/AIChatWidget.tsx +0 -361
  58. package/src/snippets/McpChat/components/AskAIButton.tsx +0 -92
  59. package/src/snippets/McpChat/components/ChatMessages.tsx +0 -138
  60. package/src/snippets/McpChat/components/ChatPanel.tsx +0 -131
  61. package/src/snippets/McpChat/components/ChatSidebar.tsx +0 -156
  62. package/src/snippets/McpChat/components/ChatWidget.tsx +0 -115
  63. package/src/snippets/McpChat/components/MessageBubble.tsx +0 -142
  64. package/src/snippets/McpChat/components/MessageInput.tsx +0 -140
  65. package/src/snippets/McpChat/components/index.ts +0 -24
  66. package/src/snippets/McpChat/config.ts +0 -94
  67. package/src/snippets/McpChat/context/AIChatContext.tsx +0 -327
  68. package/src/snippets/McpChat/context/ChatContext.tsx +0 -361
  69. package/src/snippets/McpChat/context/index.ts +0 -7
  70. package/src/snippets/McpChat/hooks/index.ts +0 -6
  71. package/src/snippets/McpChat/hooks/useAIChat.ts +0 -503
  72. package/src/snippets/McpChat/hooks/useChatLayout.ts +0 -442
  73. package/src/snippets/McpChat/hooks/useMcpChat.ts +0 -90
  74. package/src/snippets/McpChat/index.ts +0 -79
  75. package/src/snippets/McpChat/types.ts +0 -189
  76. package/src/snippets/PWAInstall/@docs/README.md +0 -92
  77. package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +0 -576
  78. package/src/snippets/PWAInstall/README.md +0 -235
  79. package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -236
  80. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +0 -234
  81. package/src/snippets/PWAInstall/components/IOSGuide.tsx +0 -29
  82. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +0 -103
  83. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +0 -103
  84. package/src/snippets/PWAInstall/components/PWAPageResumeManager.tsx +0 -33
  85. package/src/snippets/PWAInstall/context/InstallContext.tsx +0 -102
  86. package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +0 -168
  87. package/src/snippets/PWAInstall/hooks/useIsPWA.ts +0 -116
  88. package/src/snippets/PWAInstall/hooks/usePWAPageResume.ts +0 -163
  89. package/src/snippets/PWAInstall/index.ts +0 -80
  90. package/src/snippets/PWAInstall/types/components.ts +0 -95
  91. package/src/snippets/PWAInstall/types/config.ts +0 -29
  92. package/src/snippets/PWAInstall/types/index.ts +0 -26
  93. package/src/snippets/PWAInstall/types/install.ts +0 -38
  94. package/src/snippets/PWAInstall/types/platform.ts +0 -29
  95. package/src/snippets/PWAInstall/utils/localStorage.ts +0 -181
  96. package/src/snippets/PWAInstall/utils/logger.ts +0 -149
  97. package/src/snippets/PWAInstall/utils/platform.ts +0 -151
package/README.md CHANGED
@@ -57,15 +57,15 @@ export default function RootLayout({ children }) {
57
57
  | `errorTracking` | `ErrorTrackingConfig` | — | Validation/CORS/network error capture |
58
58
  | `errorBoundary` | `ErrorBoundaryConfig` | enabled | React error boundary |
59
59
  | `swr` | `SWRConfigOptions` | — | SWR data fetching config |
60
- | `pwaInstall` | `PwaInstallConfig` | — | PWA install prompt |
61
- | `mcpChat` | `McpChatConfig` | — | AI chat widget |
60
+ | `pwaInstall` | `PwaInstallConfig` | — | PWA install prompt (from `@djangocfg/ui-nextjs/pwa`) |
62
61
  | `monitor` | `MonitorConfig` | — | Override monitor config (project/environment come from `project` prop by default) |
63
62
  | `debug` | `DebugConfig` | enabled | Debug panel config — see below |
64
63
 
65
64
  **Included automatically:**
66
65
  - ThemeProvider, TooltipProvider, SWRConfig, DialogProvider
67
66
  - AuthProvider + AuthDialog
68
- - AnalyticsProvider, CentrifugoProvider, PwaProvider
67
+ - AnalyticsProvider, CentrifugoProvider
68
+ - PwaProvider (from `@djangocfg/ui-nextjs/pwa`)
69
69
  - ErrorTrackingProvider, ErrorBoundary
70
70
  - MonitorProvider (auto-enabled via `project` prop)
71
71
  - DebugButton from `@djangocfg/debuger`
@@ -224,20 +224,6 @@ export default function NotFound() {
224
224
  }
225
225
  ```
226
226
 
227
- ## PWA
228
-
229
- ```tsx
230
- <BaseApp
231
- pwaInstall={{
232
- enabled: true,
233
- showInstallHint: true,
234
- logo: '/logo192.png',
235
- delayMs: 3000,
236
- resetAfterDays: 7,
237
- resumeLastPage: true,
238
- }}
239
- >
240
- ```
241
227
 
242
228
  ## Redirect
243
229
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.226",
3
+ "version": "2.1.228",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -74,14 +74,14 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.226",
78
- "@djangocfg/centrifugo": "^2.1.226",
79
- "@djangocfg/i18n": "^2.1.226",
80
- "@djangocfg/monitor": "^2.1.226",
81
- "@djangocfg/debuger": "^2.1.226",
82
- "@djangocfg/ui-core": "^2.1.226",
83
- "@djangocfg/ui-nextjs": "^2.1.226",
84
- "@djangocfg/ui-tools": "^2.1.226",
77
+ "@djangocfg/api": "^2.1.228",
78
+ "@djangocfg/centrifugo": "^2.1.228",
79
+ "@djangocfg/i18n": "^2.1.228",
80
+ "@djangocfg/monitor": "^2.1.228",
81
+ "@djangocfg/debuger": "^2.1.228",
82
+ "@djangocfg/ui-core": "^2.1.228",
83
+ "@djangocfg/ui-nextjs": "^2.1.228",
84
+ "@djangocfg/ui-tools": "^2.1.228",
85
85
  "@hookform/resolvers": "^5.2.2",
86
86
  "consola": "^3.4.2",
87
87
  "lucide-react": "^0.545.0",
@@ -109,15 +109,15 @@
109
109
  "uuid": "^11.1.0"
110
110
  },
111
111
  "devDependencies": {
112
- "@djangocfg/api": "^2.1.226",
113
- "@djangocfg/i18n": "^2.1.226",
114
- "@djangocfg/centrifugo": "^2.1.226",
115
- "@djangocfg/monitor": "^2.1.226",
116
- "@djangocfg/debuger": "^2.1.226",
117
- "@djangocfg/typescript-config": "^2.1.226",
118
- "@djangocfg/ui-core": "^2.1.226",
119
- "@djangocfg/ui-nextjs": "^2.1.226",
120
- "@djangocfg/ui-tools": "^2.1.226",
112
+ "@djangocfg/api": "^2.1.228",
113
+ "@djangocfg/i18n": "^2.1.228",
114
+ "@djangocfg/centrifugo": "^2.1.228",
115
+ "@djangocfg/monitor": "^2.1.228",
116
+ "@djangocfg/debuger": "^2.1.228",
117
+ "@djangocfg/typescript-config": "^2.1.228",
118
+ "@djangocfg/ui-core": "^2.1.228",
119
+ "@djangocfg/ui-nextjs": "^2.1.228",
120
+ "@djangocfg/ui-tools": "^2.1.228",
121
121
  "@types/node": "^24.7.2",
122
122
  "@types/react": "^19.1.0",
123
123
  "@types/react-dom": "^19.1.0",
@@ -20,7 +20,7 @@
20
20
  import React, { useMemo } from 'react';
21
21
 
22
22
  import { Button } from '@djangocfg/ui-core/components';
23
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
23
+ import { useAppT } from '@djangocfg/i18n';
24
24
 
25
25
  import { getErrorContent } from './errorConfig';
26
26
 
@@ -113,7 +113,7 @@ export function ErrorLayout({
113
113
  illustration,
114
114
  supportEmail = 'support@example.com',
115
115
  }: ErrorLayoutProps) {
116
- const t = useTypedT<I18nTranslations>();
116
+ const t = useAppT();
117
117
 
118
118
  const labels = useMemo(() => ({
119
119
  goBack: t('layouts.errors.goBack'),
@@ -50,6 +50,7 @@ export {
50
50
  formatRuntimeErrorForClipboard,
51
51
  formatErrorTitle,
52
52
  extractDomain,
53
+ errorDetailToMonitorEvent,
53
54
  } from './utils/formatters';
54
55
 
55
56
  export {
@@ -5,7 +5,9 @@
5
5
  */
6
6
 
7
7
  import type { ZodError } from 'zod';
8
- import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail, CentrifugoErrorDetail, RuntimeErrorDetail } from '../types';
8
+ import type { ValidationErrorDetail, CORSErrorDetail, NetworkErrorDetail, CentrifugoErrorDetail, RuntimeErrorDetail, ErrorDetail } from '../types';
9
+ import type { MonitorEvent } from '@djangocfg/monitor';
10
+ import { EventType, EventLevel } from '@djangocfg/monitor';
9
11
 
10
12
  /**
11
13
  * Format Zod error issues for display
@@ -131,6 +133,26 @@ export function formatRuntimeErrorForClipboard(detail: RuntimeErrorDetail): stri
131
133
  return JSON.stringify(errorData, null, 2);
132
134
  }
133
135
 
136
+ /**
137
+ * Convert ErrorDetail to MonitorEvent for forwarding to @djangocfg/monitor backend.
138
+ * Use via ErrorTrackingProvider.onMonitorCapture.
139
+ */
140
+ export function errorDetailToMonitorEvent(detail: ErrorDetail): MonitorEvent {
141
+ const url = typeof window !== 'undefined' ? window.location.href : ''
142
+ switch (detail.type) {
143
+ case 'validation':
144
+ return { event_type: EventType.ERROR, level: EventLevel.WARNING, message: `Validation: ${detail.operation} ${detail.path}`, url }
145
+ case 'cors':
146
+ return { event_type: EventType.NETWORK_ERROR, level: EventLevel.ERROR, message: `CORS: ${detail.method} ${detail.url}`, http_method: detail.method, http_url: detail.url, url }
147
+ case 'network':
148
+ 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 }
149
+ case 'centrifugo':
150
+ return { event_type: EventType.ERROR, level: EventLevel.ERROR, message: `Centrifugo ${detail.method}: ${detail.error}`, url }
151
+ case 'runtime':
152
+ return { event_type: EventType.JS_ERROR, level: EventLevel.ERROR, message: detail.message, stack_trace: detail.error?.stack, url }
153
+ }
154
+ }
155
+
134
156
  /**
135
157
  * Format error title based on type
136
158
  */
@@ -3,39 +3,36 @@
3
3
  import { useCallback } from 'react';
4
4
 
5
5
  import { useAuth } from '@djangocfg/api/auth';
6
+ import { useAppT } from '@djangocfg/i18n';
6
7
 
7
8
  /**
8
- * Hook for logout with confirmation dialog
9
- *
10
- * Uses window.dialog.confirm for beautiful shadcn dialog
9
+ * Hook for logout with i18n confirmation dialog.
11
10
  *
12
11
  * @example
13
12
  * ```tsx
14
13
  * function UserMenu() {
15
14
  * const handleLogout = useLogout();
16
- *
17
- * return (
18
- * <Button onClick={handleLogout}>Logout</Button>
19
- * );
15
+ * return <Button onClick={handleLogout}>Logout</Button>;
20
16
  * }
21
17
  * ```
22
18
  */
23
19
  export function useLogout() {
24
20
  const { logout } = useAuth();
21
+ const t = useAppT();
25
22
 
26
23
  const handleLogout = useCallback(async () => {
27
24
  const confirmed = await window.dialog.confirm({
28
- title: 'Sign out',
29
- message: 'Are you sure you want to sign out?',
30
- confirmText: 'Sign out',
31
- cancelText: 'Cancel',
25
+ title: t('api.logout.title'),
26
+ message: t('api.logout.message'),
27
+ confirmText: t('api.logout.confirm'),
28
+ cancelText: t('api.logout.cancel'),
32
29
  variant: 'destructive',
33
30
  });
34
31
 
35
32
  if (confirmed) {
36
33
  logout();
37
34
  }
38
- }, [logout]);
35
+ }, [logout, t]);
39
36
 
40
37
  return handleLogout;
41
38
  }
@@ -46,7 +46,6 @@ import type {
46
46
  ErrorTrackingConfig,
47
47
  ErrorBoundaryConfig,
48
48
  SWRConfigOptions,
49
- McpChatConfig,
50
49
  PwaInstallConfig,
51
50
  DebugConfig,
52
51
  } from '../types';
@@ -111,6 +110,15 @@ export interface AppLayoutProps {
111
110
  */
112
111
  noLayoutPaths?: string | string[];
113
112
 
113
+ /**
114
+ * Auth path prefix. When set, all routes starting with this path
115
+ * (and their localized variants like /ru/auth, /en/auth) render without
116
+ * any layout wrapper — fullscreen, no navbar/sidebar.
117
+ * @default '/auth'
118
+ * @example authPath="/auth" → skips layout for /auth, /auth/*, /ru/auth, /ru/auth/*, etc.
119
+ */
120
+ authPath?: string;
121
+
114
122
  /** Theme configuration */
115
123
  theme?: ThemeConfig;
116
124
 
@@ -132,9 +140,6 @@ export interface AppLayoutProps {
132
140
  /** Error boundary configuration */
133
141
  errorBoundary?: ErrorBoundaryConfig;
134
142
 
135
- /** MCP Chat configuration */
136
- mcpChat?: McpChatConfig;
137
-
138
143
  /** PWA Install configuration */
139
144
  pwaInstall?: PwaInstallConfig;
140
145
 
@@ -159,15 +164,24 @@ function AppLayoutContent({
159
164
  privateLayout,
160
165
  adminLayout,
161
166
  noLayoutPaths,
167
+ authPath = '/auth',
162
168
  i18n,
163
169
  }: AppLayoutProps) {
164
170
  // Use pathname without locale prefix for route matching
165
171
  const pathname = usePathnameWithoutLocale();
166
172
 
173
+ // Merge authPath into noLayoutPaths — auth pages are always fullscreen
174
+ const effectiveNoLayoutPaths = useMemo(() => {
175
+ const base = noLayoutPaths
176
+ ? (Array.isArray(noLayoutPaths) ? noLayoutPaths : [noLayoutPaths])
177
+ : []
178
+ return [...base, authPath]
179
+ }, [noLayoutPaths, authPath])
180
+
167
181
  // Check if current path should skip layout
168
182
  const shouldSkipLayout = useMemo(
169
- () => matchesPath(pathname, noLayoutPaths),
170
- [pathname, noLayoutPaths]
183
+ () => matchesPath(pathname, effectiveNoLayoutPaths),
184
+ [pathname, effectiveNoLayoutPaths]
171
185
  );
172
186
 
173
187
  const layoutMode = useMemo(
@@ -265,7 +279,6 @@ export function AppLayout(props: AppLayoutProps) {
265
279
  errorTracking,
266
280
  errorBoundary,
267
281
  swr,
268
- mcpChat,
269
282
  pwaInstall,
270
283
  monitor,
271
284
  debug,
@@ -281,7 +294,6 @@ export function AppLayout(props: AppLayoutProps) {
281
294
  errorTracking={errorTracking}
282
295
  errorBoundary={errorBoundary}
283
296
  swr={swr}
284
- mcpChat={mcpChat}
285
297
  pwaInstall={pwaInstall}
286
298
  monitor={monitor}
287
299
  debug={debug}
@@ -12,7 +12,6 @@
12
12
  * - PwaProvider (PWA installation, optional)
13
13
  * - ErrorTrackingProvider (error handling)
14
14
  * - ErrorBoundary (React error boundary, enabled by default)
15
- * - MCP Chat Widget (optional, lazy loaded)
16
15
  *
17
16
  * @example
18
17
  * ```tsx
@@ -24,7 +23,6 @@
24
23
  * auth={{ loginPath: '/auth/login' }}
25
24
  * analytics={{ googleTrackingId: 'G-XXXXXXXXXX' }}
26
25
  * pwaInstall={{ enabled: true, logo: '/logo192.png' }}
27
- * mcpChat={{ enabled: true, autoDetectEnvironment: true }}
28
26
  * >
29
27
  * {children}
30
28
  * </BaseApp>
@@ -42,7 +40,8 @@ import dynamic from 'next/dynamic';
42
40
  import NextTopLoader from 'nextjs-toploader';
43
41
  import { SWRConfig } from 'swr';
44
42
 
45
- import { MonitorProvider } from '@djangocfg/monitor/client';
43
+ import { MonitorProvider, FrontendMonitor } from '@djangocfg/monitor/client';
44
+ import { errorDetailToMonitorEvent } from '../../components/errors/ErrorsTracker';
46
45
  import { getCentrifugoAuthTokenRetrieve } from '@djangocfg/api';
47
46
  import { AuthProvider } from '@djangocfg/api/auth';
48
47
  import { CentrifugoProvider } from '@djangocfg/centrifugo';
@@ -53,7 +52,7 @@ import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
53
52
  import { ErrorTrackingProvider } from '../../components/errors/ErrorsTracker';
54
53
  import { AnalyticsProvider } from '../../snippets/Analytics';
55
54
  import { AuthDialog } from '../../snippets/AuthDialog';
56
- import { A2HSHint, PWAPageResumeManager, PwaProvider } from '../../snippets/PWAInstall';
55
+ import { A2HSHint, PWAPageResumeManager, PwaProvider } from '@djangocfg/ui-nextjs/pwa';
57
56
 
58
57
  import type { BaseLayoutProps } from '../types/layout.types';
59
58
 
@@ -63,13 +62,6 @@ const DebugButton = dynamic(
63
62
  { ssr: false }
64
63
  );
65
64
 
66
- // Lazy load MCP Chat Widget with dynamic import
67
- const AIChatWidget = dynamic(
68
- () => import('../../snippets/McpChat/components/AIChatWidget').then(mod => ({ default: mod.AIChatWidget })),
69
- { ssr: false }
70
- );
71
-
72
-
73
65
  // For backwards compatibility, re-export as BaseAppProps
74
66
  export type BaseAppProps = BaseLayoutProps;
75
67
 
@@ -78,7 +70,7 @@ export type BaseAppProps = BaseLayoutProps;
78
70
  *
79
71
  * Includes: ThemeProvider, TooltipProvider, SWRConfig, AuthProvider, AnalyticsProvider,
80
72
  * CentrifugoProvider, PwaProvider, ErrorTrackingProvider,
81
- * ErrorBoundary (optional), MCP Chat (optional)
73
+ * ErrorBoundary (optional)
82
74
  * Also renders global Toaster and PageProgress components
83
75
  */
84
76
  export function BaseApp({
@@ -91,7 +83,6 @@ export function BaseApp({
91
83
  errorTracking,
92
84
  errorBoundary,
93
85
  swr,
94
- mcpChat,
95
86
  pwaInstall,
96
87
  monitor,
97
88
  debug,
@@ -149,6 +140,7 @@ export function BaseApp({
149
140
  cors={errorTracking?.cors}
150
141
  network={errorTracking?.network}
151
142
  onError={errorTracking?.onError}
143
+ onMonitorCapture={(d) => FrontendMonitor.capture(errorDetailToMonitorEvent(d))}
152
144
  >
153
145
  <MonitorProvider {...monitorConfig} />
154
146
  {children}
@@ -174,21 +166,6 @@ export function BaseApp({
174
166
  <PWAPageResumeManager enabled={true} />
175
167
  )}
176
168
 
177
- {/* MCP Chat Widget - lazy loaded */}
178
- {mcpChat?.enabled && (
179
- <AIChatWidget
180
- apiEndpoint={mcpChat.apiEndpoint}
181
- title={mcpChat.title}
182
- placeholder={mcpChat.placeholder}
183
- greeting={mcpChat.greeting}
184
- position={mcpChat.position}
185
- variant={mcpChat.variant}
186
- enableStreaming={mcpChat.enableStreaming}
187
- autoDetectEnvironment={mcpChat.autoDetectEnvironment}
188
- className={mcpChat.className}
189
- />
190
- )}
191
-
192
169
  {/* Auth Dialog - global auth prompt */}
193
170
  <AuthDialog authPath={auth?.routes?.auth} />
194
171
 
@@ -7,10 +7,12 @@
7
7
 
8
8
  'use client';
9
9
 
10
- import React, { useEffect, useState } from 'react';
10
+ import React, { createContext, useContext, useEffect, useState } from 'react';
11
11
 
12
12
  import { useCfgRouter } from '@djangocfg/api/auth';
13
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
13
+ import { useAppT } from '@djangocfg/i18n';
14
+
15
+ import { GlowBackground } from '@djangocfg/ui-core/components';
14
16
 
15
17
  import { Suspense } from '../../components';
16
18
  import { OAuthCallback } from './components/oauth';
@@ -22,37 +24,64 @@ import './styles/auth.css';
22
24
 
23
25
  import type { AuthLayoutProps } from './types';
24
26
 
25
- export type { AuthLayoutProps };
27
+
28
+ // ─── Layout-level context (header suppression, etc.) ─────────────────────────
29
+
30
+ interface AuthLayoutContextValue {
31
+ /** True when the consumer passed custom children — step headers should be hidden */
32
+ hideHeader: boolean;
33
+ }
34
+
35
+ const AuthLayoutContext = createContext<AuthLayoutContextValue>({ hideHeader: false });
36
+
37
+ export const useAuthLayoutContext = (): AuthLayoutContextValue => useContext(AuthLayoutContext);
38
+
39
+ // ─── Layout ──────────────────────────────────────────────────────────────────
26
40
 
27
41
  export const AuthLayout: React.FC<AuthLayoutProps> = (props) => {
28
42
  const { enableGithubAuth, redirectUrl = AUTH.DEFAULT_REDIRECT, onOAuthSuccess, onError, className } = props;
43
+ const hideHeader = Boolean(props.children);
29
44
 
30
45
  return (
31
46
  <Suspense>
32
47
  <AuthFormProvider {...props}>
33
- {/* Full-screen success overlay */}
34
- <AuthSuccessOverlay />
35
-
36
- <div className={`auth-layout ${className || ''}`}>
37
- {/* Handle OAuth callback when GitHub auth is enabled */}
38
- {enableGithubAuth && (
39
- <Suspense fallback={null}>
40
- <OAuthCallback
41
- redirectUrl={redirectUrl}
42
- onSuccess={onOAuthSuccess ? (user, isNewUser) => onOAuthSuccess(user, isNewUser, 'github') : undefined}
43
- onError={onError}
44
- />
45
- </Suspense>
46
- )}
47
-
48
- {props.children}
49
- <AuthContent />
50
- </div>
48
+ <AuthLayoutContext.Provider value={{ hideHeader }}>
49
+ {/* Full-screen success overlay */}
50
+ <AuthSuccessOverlay />
51
+
52
+ <div className={`auth-layout ${className || ''}`}>
53
+ <GlowBackground />
54
+
55
+ {/* Content layer above glow */}
56
+ <div style={{ position: 'relative', zIndex: 1, width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
57
+ {/* Handle OAuth callback when GitHub auth is enabled */}
58
+ {enableGithubAuth && (
59
+ <Suspense fallback={null}>
60
+ <OAuthCallback
61
+ redirectUrl={redirectUrl}
62
+ onSuccess={onOAuthSuccess ? (user, isNewUser) => onOAuthSuccess(user, isNewUser, 'github') : undefined}
63
+ onError={onError}
64
+ />
65
+ </Suspense>
66
+ )}
67
+
68
+ <AuthHeaderSlot>{props.children}</AuthHeaderSlot>
69
+ <AuthContent />
70
+ </div>
71
+ </div>
72
+ </AuthLayoutContext.Provider>
51
73
  </AuthFormProvider>
52
74
  </Suspense>
53
75
  );
54
76
  };
55
77
 
78
+ /** Renders custom children only on the identifier step — hides them on otp / 2fa / etc. */
79
+ const AuthHeaderSlot: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
80
+ const { step } = useAuthFormContext();
81
+ if (!children || step !== 'identifier') return null;
82
+ return <>{children}</>;
83
+ };
84
+
56
85
  const AuthContent: React.FC = () => {
57
86
  const { step, setStep } = useAuthFormContext();
58
87
 
@@ -101,7 +130,7 @@ const AuthSuccess: React.FC<AuthSuccessInlineProps> = ({
101
130
  redirectDelay = AUTH.REDIRECT_DELAY,
102
131
  }) => {
103
132
  const router = useCfgRouter();
104
- const t = useTypedT<I18nTranslations>();
133
+ const t = useAppT();
105
134
  const [isVisible, setIsVisible] = useState(false);
106
135
 
107
136
  const successMessage = React.useMemo(() => t('layouts.auth.success.message'), [t]);
@@ -0,0 +1,78 @@
1
+ # AuthLayout
2
+
3
+ Apple HIG-style authentication layout with animated mesh gradient background.
4
+
5
+ Supports: email OTP, phone OTP, GitHub OAuth, 2FA (TOTP + backup codes).
6
+
7
+ ## Usage
8
+
9
+ ```tsx
10
+ import { AuthLayout } from '@djangocfg/layouts';
11
+
12
+ <AuthLayout
13
+ sourceUrl="https://myapp.com"
14
+ redirectUrl="/dashboard"
15
+ enableGithubAuth
16
+ termsUrl="/terms"
17
+ privacyUrl="/privacy"
18
+ >
19
+ {/* Optional: custom header shown only on the identifier step */}
20
+ <div>
21
+ <h1>Welcome to MyApp</h1>
22
+ <p>Sign in to continue</p>
23
+ </div>
24
+ </AuthLayout>
25
+ ```
26
+
27
+ ## Props
28
+
29
+ | Prop | Type | Default | Description |
30
+ |---|---|---|---|
31
+ | `sourceUrl` | `string` | — | App URL for analytics/tracking |
32
+ | `redirectUrl` | `string` | `/dashboard` | Where to redirect after auth |
33
+ | `enableGithubAuth` | `boolean` | `false` | Show GitHub OAuth button |
34
+ | `enablePhoneAuth` | `boolean` | `false` | Allow phone number input |
35
+ | `enable2FASetup` | `boolean` | `true` | Prompt 2FA setup after login |
36
+ | `logoUrl` | `string` | — | Logo shown on success screen |
37
+ | `termsUrl` | `string` | — | Terms of service link |
38
+ | `privacyUrl` | `string` | — | Privacy policy link |
39
+ | `supportUrl` | `string` | — | Support page link |
40
+ | `className` | `string` | — | Extra class on root element |
41
+ | `children` | `ReactNode` | — | Custom header (identifier step only) |
42
+
43
+ ## Callbacks
44
+
45
+ ```tsx
46
+ <AuthLayout
47
+ onIdentifierSuccess={(identifier) => console.log('identifier:', identifier)}
48
+ onOTPSuccess={() => console.log('otp verified')}
49
+ onOAuthSuccess={(user, isNewUser, provider) => console.log(provider, user)}
50
+ onError={(message) => console.error(message)}
51
+ />
52
+ ```
53
+
54
+ ## Auth Steps
55
+
56
+ | Step | Description |
57
+ |---|---|
58
+ | `identifier` | Email or phone input |
59
+ | `otp` | 6-digit OTP code entry |
60
+ | `2fa` | TOTP or backup code verification |
61
+ | `2fa-setup` | QR code + backup codes setup |
62
+ | `success` | Full-screen success overlay → redirect |
63
+
64
+ `children` (custom header) are only rendered on the `identifier` step — hidden on all others.
65
+
66
+ ## Background
67
+
68
+ The animated mesh gradient background (`GlowBackground`) is always rendered. It uses `position: absolute inset-0` inside the layout container and works on both light and dark themes.
69
+
70
+ ## Context
71
+
72
+ Step components can access form state via `useAuthFormContext()`:
73
+
74
+ ```tsx
75
+ import { useAuthFormContext } from '@djangocfg/layouts';
76
+
77
+ const { step, identifier, isLoading } = useAuthFormContext();
78
+ ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useMemo } from 'react';
4
4
 
5
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
5
+ import { useAppT } from '@djangocfg/i18n';
6
6
 
7
7
  export interface AuthDividerProps {
8
8
  text?: string;
@@ -16,7 +16,7 @@ export const AuthDivider: React.FC<AuthDividerProps> = ({
16
16
  text,
17
17
  className = '',
18
18
  }) => {
19
- const t = useTypedT<I18nTranslations>();
19
+ const t = useAppT();
20
20
  const defaultText = useMemo(() => t('layouts.auth.divider.or'), [t]);
21
21
  const dividerText = text ?? defaultText;
22
22
 
@@ -19,8 +19,16 @@ export const AuthError: React.FC<AuthErrorProps> = ({
19
19
  }
20
20
 
21
21
  return (
22
- <div className={`auth-error ${className}`} role="alert">
23
- {message}
22
+ <div className={`auth-error ${className}`} role="alert" aria-live="assertive">
23
+ <svg
24
+ className="auth-error-icon"
25
+ viewBox="0 0 16 16"
26
+ fill="currentColor"
27
+ aria-hidden="true"
28
+ >
29
+ <path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 3.5a.75.75 0 0 1 .75.75v3a.75.75 0 0 1-1.5 0v-3A.75.75 0 0 1 8 4.5zm0 6.5a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5z" />
30
+ </svg>
31
+ <span>{message}</span>
24
32
  </div>
25
33
  );
26
34
  };
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useMemo } from 'react';
4
4
 
5
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
5
+ import { useAppT } from '@djangocfg/i18n';
6
6
 
7
7
  export interface AuthFooterProps {
8
8
  termsUrl?: string;
@@ -20,7 +20,7 @@ export const AuthFooter: React.FC<AuthFooterProps> = ({
20
20
  supportUrl,
21
21
  className = '',
22
22
  }) => {
23
- const t = useTypedT<I18nTranslations>();
23
+ const t = useAppT();
24
24
  const labels = useMemo(() => ({
25
25
  terms: t('layouts.auth.footer.terms'),
26
26
  privacy: t('layouts.auth.footer.privacy'),
@@ -34,15 +34,16 @@ export const AuthHeader: React.FC<AuthHeaderProps> = ({
34
34
  alt=""
35
35
  className="auth-logo"
36
36
  aria-hidden="true"
37
+ draggable={false}
37
38
  />
38
39
  )}
39
40
  <h1 className="auth-title">{title}</h1>
40
41
  {subtitle && (
41
- <p className="auth-subtitle">
42
+ <p className="auth-subtitle" aria-live="polite">
42
43
  {subtitle}
43
44
  {identifier && (
44
45
  <>
45
- <br />
46
+ {' '}
46
47
  <span className="auth-identifier">{identifier}</span>
47
48
  </>
48
49
  )}
@@ -12,6 +12,7 @@ export interface AuthOTPInputProps {
12
12
  onComplete?: (value: string) => void;
13
13
  disabled?: boolean;
14
14
  autoFocus?: boolean;
15
+ size?: 'sm' | 'default' | 'lg';
15
16
  }
16
17
 
17
18
  /**
@@ -25,6 +26,7 @@ export const AuthOTPInput: React.FC<AuthOTPInputProps> = ({
25
26
  onComplete,
26
27
  disabled = false,
27
28
  autoFocus = true,
29
+ size,
28
30
  }) => (
29
31
  <div className="auth-otp-container auth-otp-wrapper">
30
32
  <OTPInput
@@ -36,7 +38,8 @@ export const AuthOTPInput: React.FC<AuthOTPInputProps> = ({
36
38
  onComplete={onComplete}
37
39
  disabled={disabled}
38
40
  autoFocus={autoFocus}
39
- size="lg"
41
+ size={size}
42
+ fluid
40
43
  />
41
44
  </div>
42
45
  );