@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.
- package/README.md +3 -17
- package/package.json +18 -18
- package/src/components/errors/ErrorLayout.tsx +2 -2
- package/src/components/errors/ErrorsTracker/index.ts +1 -0
- package/src/components/errors/ErrorsTracker/utils/formatters.ts +23 -1
- package/src/hooks/useLogout.ts +9 -12
- package/src/layouts/AppLayout/AppLayout.tsx +20 -8
- package/src/layouts/AppLayout/BaseApp.tsx +5 -28
- package/src/layouts/AuthLayout/AuthLayout.tsx +51 -22
- package/src/layouts/AuthLayout/README.md +78 -0
- package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +2 -2
- package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -2
- package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +2 -2
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +3 -2
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +4 -1
- package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +2 -2
- package/src/layouts/AuthLayout/components/shared/index.ts +0 -2
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +25 -80
- package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +8 -13
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +2 -2
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +2 -2
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +2 -2
- package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +61 -42
- package/src/layouts/AuthLayout/context.tsx +0 -2
- package/src/layouts/AuthLayout/index.ts +9 -6
- package/src/layouts/AuthLayout/styles/auth.css +265 -120
- package/src/layouts/AuthLayout/types.ts +60 -7
- package/src/layouts/ProfileLayout/.claude/.sidecar/activity.jsonl +2 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/history/2026-03-15.md +35 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/review.md +35 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/scan.log +3 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-001.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-002.md +19 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-003.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-004.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-005.md +18 -0
- package/src/layouts/ProfileLayout/.claude/.sidecar/usage.json +5 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +52 -403
- package/src/layouts/ProfileLayout/components/ActionButton.tsx +38 -0
- package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +109 -148
- package/src/layouts/ProfileLayout/components/EditableField.tsx +119 -0
- package/src/layouts/ProfileLayout/components/Section.tsx +22 -0
- package/src/layouts/ProfileLayout/components/index.ts +4 -1
- package/src/layouts/ProfileLayout/context.tsx +31 -0
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
- package/src/layouts/_components/UserMenu.tsx +2 -2
- package/src/layouts/types/README.md +0 -20
- package/src/layouts/types/index.ts +2 -2
- package/src/layouts/types/layout.types.ts +2 -5
- package/src/layouts/types/providers.types.ts +0 -27
- package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
- package/src/snippets/Breadcrumbs.tsx +2 -2
- package/src/snippets/index.ts +0 -67
- package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +0 -56
- package/src/snippets/McpChat/README.md +0 -441
- package/src/snippets/McpChat/components/AIChatWidget.tsx +0 -361
- package/src/snippets/McpChat/components/AskAIButton.tsx +0 -92
- package/src/snippets/McpChat/components/ChatMessages.tsx +0 -138
- package/src/snippets/McpChat/components/ChatPanel.tsx +0 -131
- package/src/snippets/McpChat/components/ChatSidebar.tsx +0 -156
- package/src/snippets/McpChat/components/ChatWidget.tsx +0 -115
- package/src/snippets/McpChat/components/MessageBubble.tsx +0 -142
- package/src/snippets/McpChat/components/MessageInput.tsx +0 -140
- package/src/snippets/McpChat/components/index.ts +0 -24
- package/src/snippets/McpChat/config.ts +0 -94
- package/src/snippets/McpChat/context/AIChatContext.tsx +0 -327
- package/src/snippets/McpChat/context/ChatContext.tsx +0 -361
- package/src/snippets/McpChat/context/index.ts +0 -7
- package/src/snippets/McpChat/hooks/index.ts +0 -6
- package/src/snippets/McpChat/hooks/useAIChat.ts +0 -503
- package/src/snippets/McpChat/hooks/useChatLayout.ts +0 -442
- package/src/snippets/McpChat/hooks/useMcpChat.ts +0 -90
- package/src/snippets/McpChat/index.ts +0 -79
- package/src/snippets/McpChat/types.ts +0 -189
- package/src/snippets/PWAInstall/@docs/README.md +0 -92
- package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +0 -576
- package/src/snippets/PWAInstall/README.md +0 -235
- package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -236
- package/src/snippets/PWAInstall/components/DesktopGuide.tsx +0 -234
- package/src/snippets/PWAInstall/components/IOSGuide.tsx +0 -29
- package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +0 -103
- package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +0 -103
- package/src/snippets/PWAInstall/components/PWAPageResumeManager.tsx +0 -33
- package/src/snippets/PWAInstall/context/InstallContext.tsx +0 -102
- package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +0 -168
- package/src/snippets/PWAInstall/hooks/useIsPWA.ts +0 -116
- package/src/snippets/PWAInstall/hooks/usePWAPageResume.ts +0 -163
- package/src/snippets/PWAInstall/index.ts +0 -80
- package/src/snippets/PWAInstall/types/components.ts +0 -95
- package/src/snippets/PWAInstall/types/config.ts +0 -29
- package/src/snippets/PWAInstall/types/index.ts +0 -26
- package/src/snippets/PWAInstall/types/install.ts +0 -38
- package/src/snippets/PWAInstall/types/platform.ts +0 -29
- package/src/snippets/PWAInstall/utils/localStorage.ts +0 -181
- package/src/snippets/PWAInstall/utils/logger.ts +0 -149
- 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
|
|
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.
|
|
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.
|
|
78
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
79
|
-
"@djangocfg/i18n": "^2.1.
|
|
80
|
-
"@djangocfg/monitor": "^2.1.
|
|
81
|
-
"@djangocfg/debuger": "^2.1.
|
|
82
|
-
"@djangocfg/ui-core": "^2.1.
|
|
83
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
84
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
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.
|
|
113
|
-
"@djangocfg/i18n": "^2.1.
|
|
114
|
-
"@djangocfg/centrifugo": "^2.1.
|
|
115
|
-
"@djangocfg/monitor": "^2.1.
|
|
116
|
-
"@djangocfg/debuger": "^2.1.
|
|
117
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
118
|
-
"@djangocfg/ui-core": "^2.1.
|
|
119
|
-
"@djangocfg/ui-nextjs": "^2.1.
|
|
120
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
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 {
|
|
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 =
|
|
116
|
+
const t = useAppT();
|
|
117
117
|
|
|
118
118
|
const labels = useMemo(() => ({
|
|
119
119
|
goBack: t('layouts.errors.goBack'),
|
|
@@ -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
|
*/
|
package/src/hooks/useLogout.ts
CHANGED
|
@@ -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: '
|
|
29
|
-
message: '
|
|
30
|
-
confirmText: '
|
|
31
|
-
cancelText: '
|
|
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,
|
|
170
|
-
[pathname,
|
|
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 '
|
|
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)
|
|
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 {
|
|
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
|
-
|
|
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
|
-
{
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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=
|
|
41
|
+
size={size}
|
|
42
|
+
fluid
|
|
40
43
|
/>
|
|
41
44
|
</div>
|
|
42
45
|
);
|