@djangocfg/layouts 2.0.8 → 2.0.10
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/package.json +5 -5
- package/src/auth/context/AuthContext.tsx +11 -6
- package/src/auth/hooks/useAuthGuard.ts +2 -2
- package/src/auth/hooks/useAutoAuth.ts +2 -2
- package/src/auth/hooks/useGithubAuth.ts +4 -3
- package/src/components/RedirectPage/RedirectPage.tsx +2 -2
- package/src/components/core/ClientOnly.tsx +73 -0
- package/src/components/core/index.ts +2 -0
- package/src/components/errors/ErrorLayout.tsx +6 -7
- package/src/layouts/AppLayout/AppLayout.tsx +25 -20
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +9 -21
- package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
- package/src/snippets/McpChat/components/AIChatWidget.tsx +3 -39
- package/src/snippets/McpChat/components/ChatMessages.tsx +2 -2
- package/src/snippets/McpChat/components/ChatPanel.tsx +84 -110
- package/src/snippets/McpChat/components/ChatSidebar.tsx +66 -60
- package/src/snippets/McpChat/components/ChatWidget.tsx +4 -37
- package/src/snippets/McpChat/components/MessageBubble.tsx +5 -5
- package/src/snippets/McpChat/components/index.ts +0 -2
- package/src/snippets/McpChat/config.ts +42 -0
- package/src/snippets/McpChat/context/ChatContext.tsx +5 -7
- package/src/snippets/McpChat/hooks/useChatLayout.ts +134 -23
- package/src/snippets/McpChat/index.ts +0 -1
- package/src/snippets/index.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/layouts",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"description": "Simple, straightforward layout components for Next.js - import and use with props",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"layouts",
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
"check": "tsc --noEmit"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
95
|
-
"@djangocfg/api": "^1.4.
|
|
96
|
-
"@djangocfg/centrifugo": "^1.4.
|
|
97
|
-
"@djangocfg/ui": "^1.4.
|
|
95
|
+
"@djangocfg/api": "^1.4.40",
|
|
96
|
+
"@djangocfg/centrifugo": "^1.4.40",
|
|
97
|
+
"@djangocfg/ui": "^1.4.40",
|
|
98
98
|
"@hookform/resolvers": "^5.2.0",
|
|
99
99
|
"consola": "^3.4.2",
|
|
100
100
|
"lucide-react": "^0.545.0",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"uuid": "^11.1.0"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
|
-
"@djangocfg/typescript-config": "^1.4.
|
|
117
|
+
"@djangocfg/typescript-config": "^1.4.40",
|
|
118
118
|
"@types/node": "^24.7.2",
|
|
119
119
|
"@types/react": "19.2.2",
|
|
120
120
|
"@types/react-dom": "19.2.1",
|
|
@@ -8,7 +8,7 @@ import React, {
|
|
|
8
8
|
|
|
9
9
|
import { api, Enums } from '@djangocfg/api';
|
|
10
10
|
import { useAccountsContext, AccountsProvider } from './AccountsContext';
|
|
11
|
-
import { useLocalStorage, useQueryParams,
|
|
11
|
+
import { useLocalStorage, useQueryParams, useCfgRouter } from '@djangocfg/ui/hooks';
|
|
12
12
|
import { getCachedProfile, clearProfileCache } from '../hooks/useProfileCache';
|
|
13
13
|
|
|
14
14
|
import { authLogger } from '../../utils/logger';
|
|
@@ -50,7 +50,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
const [initialized, setInitialized] = useState(false);
|
|
53
|
-
const router =
|
|
53
|
+
const router = useCfgRouter();
|
|
54
54
|
const pathname = usePathname();
|
|
55
55
|
const queryParams = useQueryParams();
|
|
56
56
|
|
|
@@ -362,13 +362,14 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
// Handle redirect logic here
|
|
365
|
+
// Use hardPush for full page reload - ensures all React contexts reinitialize
|
|
365
366
|
const defaultCallback = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
|
|
366
367
|
|
|
367
368
|
if (redirectUrl && redirectUrl !== defaultCallback) {
|
|
368
369
|
clearRedirectUrl();
|
|
369
|
-
router.
|
|
370
|
+
router.hardPush(redirectUrl);
|
|
370
371
|
} else {
|
|
371
|
-
router.
|
|
372
|
+
router.hardPush(defaultCallback);
|
|
372
373
|
}
|
|
373
374
|
|
|
374
375
|
return {
|
|
@@ -460,7 +461,11 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
460
461
|
accounts.logout(); // Clear tokens and profile
|
|
461
462
|
setInitialized(true);
|
|
462
463
|
setIsLoading(false);
|
|
463
|
-
|
|
464
|
+
|
|
465
|
+
// Use hardReplace for full page reload + replace history
|
|
466
|
+
// This ensures contexts reinitialize AND back button won't return to protected page
|
|
467
|
+
const authCallbackUrl = config?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
|
|
468
|
+
router.hardReplace(authCallbackUrl);
|
|
464
469
|
};
|
|
465
470
|
|
|
466
471
|
// Use config.onConfirm if provided, otherwise use a simple confirm
|
|
@@ -482,7 +487,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
|
|
|
482
487
|
performLogout();
|
|
483
488
|
}
|
|
484
489
|
}
|
|
485
|
-
}, [accounts,
|
|
490
|
+
}, [accounts, config?.routes?.defaultAuthCallback, router]);
|
|
486
491
|
|
|
487
492
|
// Redirect URL methods
|
|
488
493
|
const getSavedRedirectUrl = useCallback((): string | null => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useAuth } from '../context';
|
|
6
|
-
import {
|
|
6
|
+
import { useCfgRouter } from '@djangocfg/ui/hooks';
|
|
7
7
|
|
|
8
8
|
interface UseAuthGuardOptions {
|
|
9
9
|
redirectTo?: string;
|
|
@@ -13,7 +13,7 @@ interface UseAuthGuardOptions {
|
|
|
13
13
|
export const useAuthGuard = (options: UseAuthGuardOptions = {}) => {
|
|
14
14
|
const { redirectTo = '/auth', requireAuth = true } = options;
|
|
15
15
|
const { isAuthenticated, isLoading } = useAuth();
|
|
16
|
-
const router =
|
|
16
|
+
const router = useCfgRouter();
|
|
17
17
|
|
|
18
18
|
useEffect(() => {
|
|
19
19
|
if (!isLoading && requireAuth && !isAuthenticated) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
import { usePathname } from 'next/navigation';
|
|
5
|
-
import { useQueryParams,
|
|
5
|
+
import { useQueryParams, useCfgRouter } from '@djangocfg/ui/hooks';
|
|
6
6
|
import { authLogger } from '../../utils/logger';
|
|
7
7
|
|
|
8
8
|
export interface UseAutoAuthOptions {
|
|
@@ -18,7 +18,7 @@ export const useAutoAuth = (options: UseAutoAuthOptions = {}) => {
|
|
|
18
18
|
const { onOTPDetected, cleanupUrl = true } = options;
|
|
19
19
|
const queryParams = useQueryParams();
|
|
20
20
|
const pathname = usePathname();
|
|
21
|
-
const router =
|
|
21
|
+
const router = useCfgRouter();
|
|
22
22
|
|
|
23
23
|
const isReady = !!pathname && !!queryParams.get('otp');
|
|
24
24
|
const hasOTP = !!(queryParams.get('otp'));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState } from 'react';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
4
|
|
|
6
5
|
import { api } from '@djangocfg/api';
|
|
6
|
+
import { useCfgRouter } from '@djangocfg/ui/hooks';
|
|
7
7
|
import { authLogger } from '../../utils/logger';
|
|
8
8
|
import { Analytics, AnalyticsEvent, AnalyticsCategory } from '../../snippets/Analytics';
|
|
9
9
|
|
|
@@ -42,7 +42,7 @@ export interface UseGithubAuthReturn {
|
|
|
42
42
|
*/
|
|
43
43
|
export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuthReturn => {
|
|
44
44
|
const { sourceUrl, onSuccess, onError, redirectUrl } = options;
|
|
45
|
-
const router =
|
|
45
|
+
const router = useCfgRouter();
|
|
46
46
|
|
|
47
47
|
const [isLoading, setIsLoading] = useState(false);
|
|
48
48
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -155,8 +155,9 @@ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuth
|
|
|
155
155
|
onSuccess?.(response.user, response.is_new_user || false);
|
|
156
156
|
|
|
157
157
|
// Redirect to dashboard or specified URL
|
|
158
|
+
// Use hardPush for full page reload - ensures all React contexts reinitialize
|
|
158
159
|
const finalRedirectUrl = redirectUrl || '/dashboard';
|
|
159
|
-
router.
|
|
160
|
+
router.hardPush(finalRedirectUrl);
|
|
160
161
|
|
|
161
162
|
} catch (err) {
|
|
162
163
|
const errorMessage = err instanceof Error ? err.message : 'GitHub authentication failed';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
import { useAuth } from '../../auth';
|
|
5
|
-
import {
|
|
5
|
+
import { useCfgRouter } from '@djangocfg/ui/hooks';
|
|
6
6
|
import { Preloader } from '@djangocfg/ui/components';
|
|
7
7
|
|
|
8
8
|
export interface RedirectPageProps {
|
|
@@ -49,7 +49,7 @@ export function RedirectPage({
|
|
|
49
49
|
loadingText = 'Loading...',
|
|
50
50
|
}: RedirectPageProps) {
|
|
51
51
|
const { isAuthenticated } = useAuth();
|
|
52
|
-
const router =
|
|
52
|
+
const router = useCfgRouter();
|
|
53
53
|
|
|
54
54
|
useEffect(() => {
|
|
55
55
|
if (!isAuthenticated) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClientOnly Component
|
|
3
|
+
*
|
|
4
|
+
* Renders children only on the client side to prevent SSR hydration mismatch.
|
|
5
|
+
* Shows a fallback (loading state) during SSR and initial client mount.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { ClientOnly } from '@djangocfg/layouts/components';
|
|
10
|
+
*
|
|
11
|
+
* // With default loading fallback
|
|
12
|
+
* <ClientOnly>
|
|
13
|
+
* <ComponentThatUsesClientOnlyAPIs />
|
|
14
|
+
* </ClientOnly>
|
|
15
|
+
*
|
|
16
|
+
* // With custom fallback
|
|
17
|
+
* <ClientOnly fallback={<MyCustomLoader />}>
|
|
18
|
+
* <ComponentThatUsesClientOnlyAPIs />
|
|
19
|
+
* </ClientOnly>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use client';
|
|
24
|
+
|
|
25
|
+
import { useState, useEffect, ReactNode } from 'react';
|
|
26
|
+
import { Preloader } from '@djangocfg/ui/components';
|
|
27
|
+
|
|
28
|
+
export interface ClientOnlyProps {
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Fallback to show during SSR and initial mount
|
|
32
|
+
* @default Preloader with "Loading..." text
|
|
33
|
+
*/
|
|
34
|
+
fallback?: ReactNode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Default fallback - fullscreen preloader
|
|
39
|
+
*/
|
|
40
|
+
const defaultFallback = (
|
|
41
|
+
<Preloader
|
|
42
|
+
variant="fullscreen"
|
|
43
|
+
text="Loading..."
|
|
44
|
+
size="lg"
|
|
45
|
+
backdrop={true}
|
|
46
|
+
backdropOpacity={80}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* ClientOnly - Prevents SSR hydration mismatch
|
|
52
|
+
*
|
|
53
|
+
* Use this to wrap components that:
|
|
54
|
+
* - Access browser-only APIs (window, localStorage, etc.)
|
|
55
|
+
* - Have different initial state on server vs client
|
|
56
|
+
* - Use authentication state that differs between SSR and CSR
|
|
57
|
+
*/
|
|
58
|
+
export function ClientOnly({
|
|
59
|
+
children,
|
|
60
|
+
fallback = defaultFallback,
|
|
61
|
+
}: ClientOnlyProps) {
|
|
62
|
+
const [mounted, setMounted] = useState(false);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
setMounted(true);
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
if (!mounted) {
|
|
69
|
+
return <>{fallback}</>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return <>{children}</>;
|
|
73
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Core components exports
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
export { ClientOnly } from './ClientOnly';
|
|
6
|
+
export type { ClientOnlyProps } from './ClientOnly';
|
|
5
7
|
export { JsonLd } from './JsonLd';
|
|
6
8
|
export { LucideIcon } from './LucideIcon';
|
|
7
9
|
export type { LucideIconProps } from './LucideIcon';
|
|
@@ -73,13 +73,12 @@ function getErrorIcon(code?: string | number): React.ReactNode {
|
|
|
73
73
|
viewBox="0 0 24 24"
|
|
74
74
|
aria-hidden="true"
|
|
75
75
|
>
|
|
76
|
-
{/* Server Error Icon */}
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
/>
|
|
76
|
+
{/* Server Error Icon - Server with X */}
|
|
77
|
+
<rect x="2" y="3" width="20" height="7" rx="1" strokeWidth={1.5} />
|
|
78
|
+
<rect x="2" y="14" width="20" height="7" rx="1" strokeWidth={1.5} />
|
|
79
|
+
<circle cx="6" cy="6.5" r="1" fill="currentColor" />
|
|
80
|
+
<circle cx="6" cy="17.5" r="1" fill="currentColor" />
|
|
81
|
+
<path strokeLinecap="round" strokeWidth={1.5} d="M22 2L2 22" />
|
|
83
82
|
</svg>
|
|
84
83
|
);
|
|
85
84
|
case '403':
|
|
@@ -47,7 +47,7 @@ import { ErrorTrackingProvider, type ValidationErrorConfig, type CORSErrorConfig
|
|
|
47
47
|
import { AnalyticsProvider } from '../../snippets/Analytics';
|
|
48
48
|
import { PageProgress } from '../../components/core/PageProgress';
|
|
49
49
|
import { UpdateNotifier } from '../../components/UpdateNotifier';
|
|
50
|
-
import { Suspense } from '../../components/core';
|
|
50
|
+
import { Suspense, ClientOnly } from '../../components/core';
|
|
51
51
|
|
|
52
52
|
export type LayoutMode = 'public' | 'private' | 'admin';
|
|
53
53
|
|
|
@@ -148,22 +148,22 @@ export interface AppLayoutProps {
|
|
|
148
148
|
|
|
149
149
|
/**
|
|
150
150
|
* AppLayout Content - Renders layout with all providers
|
|
151
|
+
*
|
|
152
|
+
* SSR is only enabled for publicLayout.
|
|
153
|
+
* Private and admin layouts are wrapped in ClientOnly to avoid hydration mismatch.
|
|
151
154
|
*/
|
|
152
155
|
function AppLayoutContent({
|
|
153
156
|
children,
|
|
154
157
|
publicLayout,
|
|
155
158
|
privateLayout,
|
|
156
159
|
adminLayout,
|
|
157
|
-
theme,
|
|
158
|
-
auth,
|
|
159
160
|
analytics,
|
|
160
161
|
centrifugo,
|
|
161
|
-
errorTracking,
|
|
162
162
|
errorBoundary,
|
|
163
163
|
updateNotifier,
|
|
164
164
|
}: AppLayoutProps) {
|
|
165
165
|
const pathname = usePathname();
|
|
166
|
-
|
|
166
|
+
|
|
167
167
|
const layoutMode = useMemo(
|
|
168
168
|
() => determineLayoutMode(
|
|
169
169
|
pathname,
|
|
@@ -173,46 +173,51 @@ function AppLayoutContent({
|
|
|
173
173
|
),
|
|
174
174
|
[pathname, adminLayout, privateLayout, publicLayout]
|
|
175
175
|
);
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
const enableErrorBoundary = errorBoundary?.enabled !== false;
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
// Render appropriate layout based on mode
|
|
180
180
|
const renderLayout = () => {
|
|
181
181
|
switch (layoutMode) {
|
|
182
182
|
case 'admin':
|
|
183
183
|
if (!adminLayout && privateLayout) {
|
|
184
|
-
// Fallback to private layout if no admin layout provided
|
|
185
184
|
return (
|
|
186
|
-
<
|
|
187
|
-
<
|
|
188
|
-
|
|
185
|
+
<ClientOnly>
|
|
186
|
+
<Suspense>
|
|
187
|
+
<privateLayout.component>{children}</privateLayout.component>
|
|
188
|
+
</Suspense>
|
|
189
|
+
</ClientOnly>
|
|
189
190
|
);
|
|
190
191
|
}
|
|
191
192
|
if (!adminLayout) {
|
|
192
193
|
return children;
|
|
193
194
|
}
|
|
194
195
|
return (
|
|
195
|
-
<
|
|
196
|
-
<
|
|
197
|
-
|
|
196
|
+
<ClientOnly>
|
|
197
|
+
<Suspense>
|
|
198
|
+
<adminLayout.component>{children}</adminLayout.component>
|
|
199
|
+
</Suspense>
|
|
200
|
+
</ClientOnly>
|
|
198
201
|
);
|
|
199
|
-
|
|
202
|
+
|
|
200
203
|
case 'private':
|
|
201
204
|
if (!privateLayout) {
|
|
202
|
-
// Fallback to public if no private layout provided
|
|
203
205
|
if (publicLayout) {
|
|
204
206
|
return <publicLayout.component>{children}</publicLayout.component>;
|
|
205
207
|
}
|
|
206
208
|
return children;
|
|
207
209
|
}
|
|
208
210
|
return (
|
|
209
|
-
<
|
|
210
|
-
<
|
|
211
|
-
|
|
211
|
+
<ClientOnly>
|
|
212
|
+
<Suspense>
|
|
213
|
+
<privateLayout.component>{children}</privateLayout.component>
|
|
214
|
+
</Suspense>
|
|
215
|
+
</ClientOnly>
|
|
212
216
|
);
|
|
213
|
-
|
|
217
|
+
|
|
214
218
|
case 'public':
|
|
215
219
|
default:
|
|
220
|
+
// Public layout renders with SSR (no ClientOnly wrapper)
|
|
216
221
|
if (!publicLayout) {
|
|
217
222
|
return children;
|
|
218
223
|
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Private Layout
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Layout for authenticated user pages (dashboard, profile, etc.)
|
|
5
5
|
* Import and use directly with props - no complex configs needed!
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Responsive sidebar with mobile burger menu
|
|
9
9
|
* - Keyboard shortcut (Ctrl/Cmd + B) to toggle sidebar
|
|
10
10
|
* - Header with sidebar trigger and user menu
|
|
11
11
|
* - Configurable content padding
|
|
12
|
-
*
|
|
12
|
+
* - NO SSR - renders only on client to avoid hydration mismatch
|
|
13
|
+
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```tsx
|
|
15
16
|
* import { PrivateLayout } from '@djangocfg/layouts';
|
|
16
|
-
*
|
|
17
|
+
*
|
|
17
18
|
* <PrivateLayout
|
|
18
19
|
* sidebar={{
|
|
19
20
|
* items: [
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
* >
|
|
29
30
|
* {children}
|
|
30
31
|
* </PrivateLayout>
|
|
31
|
-
*
|
|
32
|
+
*
|
|
32
33
|
* Note: User data (name, email, avatar) is automatically loaded from useAuth() context
|
|
33
34
|
* Keyboard shortcut: Ctrl/Cmd + B to toggle sidebar
|
|
34
35
|
* ```
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
'use client';
|
|
38
39
|
|
|
39
40
|
import React, { ReactNode } from 'react';
|
|
40
|
-
import { SidebarProvider, SidebarInset, Preloader,
|
|
41
|
+
import { SidebarProvider, SidebarInset, Preloader, ButtonLink } from '@djangocfg/ui/components';
|
|
41
42
|
import { useAuth } from '../../auth';
|
|
42
43
|
import { PrivateSidebar, PrivateHeader, PrivateContent } from './components';
|
|
43
44
|
import type { LucideIcon as LucideIconType } from 'lucide-react';
|
|
@@ -76,20 +77,10 @@ export function PrivateLayout({
|
|
|
76
77
|
header,
|
|
77
78
|
contentPadding = 'default',
|
|
78
79
|
}: PrivateLayoutProps) {
|
|
79
|
-
const { isAuthenticated, isLoading
|
|
80
|
-
|
|
81
|
-
// Debug logging in development
|
|
82
|
-
if (process.env.NODE_ENV === 'development') {
|
|
83
|
-
console.log('[PrivateLayout] Render state:', {
|
|
84
|
-
isLoading,
|
|
85
|
-
isAuthenticated,
|
|
86
|
-
hasUser: !!user,
|
|
87
|
-
hasSidebar: !!sidebar,
|
|
88
|
-
hasHeader: !!header,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
80
|
+
const { isAuthenticated, isLoading } = useAuth();
|
|
91
81
|
|
|
92
82
|
// Show loading state while auth is being checked
|
|
83
|
+
// Note: SSR hydration is handled by ClientOnly wrapper in AppLayout
|
|
93
84
|
if (isLoading) {
|
|
94
85
|
return (
|
|
95
86
|
<Preloader
|
|
@@ -104,9 +95,6 @@ export function PrivateLayout({
|
|
|
104
95
|
|
|
105
96
|
// Don't render if user is not authenticated
|
|
106
97
|
if (!isAuthenticated) {
|
|
107
|
-
if (process.env.NODE_ENV === 'development') {
|
|
108
|
-
console.warn('[PrivateLayout] User not authenticated, returning null');
|
|
109
|
-
}
|
|
110
98
|
return (
|
|
111
99
|
<div className="flex flex-col items-center justify-center min-h-screen gap-4">
|
|
112
100
|
<h3 className="text-2xl font-bold">
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DialogHeader,
|
|
11
11
|
DialogTitle,
|
|
12
12
|
} from '@djangocfg/ui/components';
|
|
13
|
-
import { useEventListener,
|
|
13
|
+
import { useEventListener, useCfgRouter } from '@djangocfg/ui/hooks';
|
|
14
14
|
|
|
15
15
|
// Re-export events for backwards compatibility
|
|
16
16
|
export const DIALOG_EVENTS = {
|
|
@@ -31,7 +31,7 @@ export const AuthDialog: React.FC<AuthDialogProps> = ({
|
|
|
31
31
|
}) => {
|
|
32
32
|
const [open, setOpen] = useState(false);
|
|
33
33
|
const [message, setMessage] = useState<string>('Please sign in to continue');
|
|
34
|
-
const router =
|
|
34
|
+
const router = useCfgRouter();
|
|
35
35
|
|
|
36
36
|
// Listen for open auth dialog event
|
|
37
37
|
useEventListener(DIALOG_EVENTS.OPEN_AUTH_DIALOG, (payload: any) => {
|
|
@@ -66,20 +66,7 @@ export interface AIChatWidgetProps extends ChatWidgetConfig {
|
|
|
66
66
|
* Internal AI chat widget that uses context
|
|
67
67
|
*/
|
|
68
68
|
const AIChatWidgetInternal = React.memo<{ className?: string }>(({ className }) => {
|
|
69
|
-
const {
|
|
70
|
-
messages,
|
|
71
|
-
isLoading,
|
|
72
|
-
isMinimized,
|
|
73
|
-
config,
|
|
74
|
-
displayMode,
|
|
75
|
-
isMobile,
|
|
76
|
-
sendMessage,
|
|
77
|
-
openChat,
|
|
78
|
-
closeChat,
|
|
79
|
-
toggleMinimize,
|
|
80
|
-
setDisplayMode,
|
|
81
|
-
stopStreaming,
|
|
82
|
-
} = useAIChatContext();
|
|
69
|
+
const { config, displayMode, openChat } = useAIChatContext();
|
|
83
70
|
|
|
84
71
|
// Use layout hook for consistent positioning
|
|
85
72
|
const { getFabStyles, getFloatingStyles } = useChatLayout();
|
|
@@ -173,17 +160,7 @@ const AIChatWidgetInternal = React.memo<{ className?: string }>(({ className })
|
|
|
173
160
|
if (displayMode === 'sidebar') {
|
|
174
161
|
return (
|
|
175
162
|
<Portal>
|
|
176
|
-
<ChatSidebar
|
|
177
|
-
messages={messages}
|
|
178
|
-
isLoading={isLoading}
|
|
179
|
-
onSendMessage={sendMessage}
|
|
180
|
-
onClose={closeChat}
|
|
181
|
-
onModeChange={setDisplayMode}
|
|
182
|
-
onStopStreaming={stopStreaming}
|
|
183
|
-
title={config.title}
|
|
184
|
-
placeholder={config.placeholder}
|
|
185
|
-
greeting={config.greeting}
|
|
186
|
-
/>
|
|
163
|
+
<ChatSidebar />
|
|
187
164
|
</Portal>
|
|
188
165
|
);
|
|
189
166
|
}
|
|
@@ -192,20 +169,7 @@ const AIChatWidgetInternal = React.memo<{ className?: string }>(({ className })
|
|
|
192
169
|
return (
|
|
193
170
|
<Portal>
|
|
194
171
|
<div style={floatingStyles} className={className || ''}>
|
|
195
|
-
<ChatPanel
|
|
196
|
-
messages={messages}
|
|
197
|
-
isLoading={isLoading}
|
|
198
|
-
onSendMessage={sendMessage}
|
|
199
|
-
onClose={closeChat}
|
|
200
|
-
onMinimize={toggleMinimize}
|
|
201
|
-
onModeChange={setDisplayMode}
|
|
202
|
-
onStopStreaming={stopStreaming}
|
|
203
|
-
isMinimized={isMinimized}
|
|
204
|
-
isMobile={isMobile}
|
|
205
|
-
title={config.title}
|
|
206
|
-
placeholder={config.placeholder}
|
|
207
|
-
greeting={config.greeting}
|
|
208
|
-
/>
|
|
172
|
+
<ChatPanel />
|
|
209
173
|
</div>
|
|
210
174
|
</Portal>
|
|
211
175
|
);
|
|
@@ -93,8 +93,8 @@ export const ChatMessages = forwardRef<ChatMessagesHandle, ChatMessagesProps>(
|
|
|
93
93
|
const padding = largeGreetingIcon ? 'py-12' : 'py-8';
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
|
-
<ScrollArea ref={scrollAreaRef} className="h-full">
|
|
97
|
-
<div className={`${isCompact ? 'p-3' : 'p-4'} space-y-4`}>
|
|
96
|
+
<ScrollArea ref={scrollAreaRef} className="h-full w-full">
|
|
97
|
+
<div className={`${isCompact ? 'p-3' : 'p-4'} space-y-4 max-w-full overflow-x-hidden`}>
|
|
98
98
|
{/* Greeting */}
|
|
99
99
|
{messages.length === 0 && greeting && (
|
|
100
100
|
<div className={`text-center ${padding}`}>
|