@djangocfg/layouts 2.1.9 → 2.1.14

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 (106) hide show
  1. package/README.md +53 -161
  2. package/package.json +6 -6
  3. package/src/components/RedirectPage/RedirectPage.tsx +1 -1
  4. package/src/index.ts +0 -6
  5. package/src/layouts/AppLayout/AppLayout.tsx +1 -1
  6. package/src/layouts/AppLayout/BaseApp.tsx +1 -1
  7. package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
  8. package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
  9. package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
  10. package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
  11. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
  12. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
  13. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  14. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
  15. package/src/layouts/_components/UserMenu.tsx +1 -1
  16. package/src/layouts/index.ts +0 -2
  17. package/src/snippets/Analytics/useAnalytics.ts +1 -1
  18. package/src/snippets/McpChat/hooks/useAIChat.ts +16 -3
  19. package/src/snippets/index.ts +0 -3
  20. package/src/auth/README.md +0 -962
  21. package/src/auth/context/AccountsContext.tsx +0 -240
  22. package/src/auth/context/AuthContext.tsx +0 -604
  23. package/src/auth/context/index.ts +0 -4
  24. package/src/auth/context/types.ts +0 -68
  25. package/src/auth/hooks/index.ts +0 -17
  26. package/src/auth/hooks/useAuthForm.ts +0 -332
  27. package/src/auth/hooks/useAuthGuard.ts +0 -25
  28. package/src/auth/hooks/useAuthRedirect.ts +0 -51
  29. package/src/auth/hooks/useAutoAuth.ts +0 -49
  30. package/src/auth/hooks/useGithubAuth.ts +0 -184
  31. package/src/auth/hooks/useLocalStorage.ts +0 -214
  32. package/src/auth/hooks/useProfileCache.ts +0 -146
  33. package/src/auth/hooks/useSessionStorage.ts +0 -189
  34. package/src/auth/index.ts +0 -10
  35. package/src/auth/middlewares/index.ts +0 -1
  36. package/src/auth/middlewares/proxy.ts +0 -32
  37. package/src/auth/server.ts +0 -6
  38. package/src/auth/utils/errors.ts +0 -34
  39. package/src/auth/utils/index.ts +0 -2
  40. package/src/auth/utils/validation.ts +0 -14
  41. package/src/contexts/LeadsContext.tsx +0 -156
  42. package/src/contexts/NewsletterContext.tsx +0 -263
  43. package/src/contexts/SupportContext.tsx +0 -256
  44. package/src/contexts/index.ts +0 -59
  45. package/src/contexts/knowbase/ChatContext.tsx +0 -174
  46. package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
  47. package/src/contexts/knowbase/SessionsContext.tsx +0 -174
  48. package/src/contexts/knowbase/index.ts +0 -61
  49. package/src/contexts/payments/BalancesContext.tsx +0 -65
  50. package/src/contexts/payments/CurrenciesContext.tsx +0 -66
  51. package/src/contexts/payments/OverviewContext.tsx +0 -174
  52. package/src/contexts/payments/PaymentsContext.tsx +0 -132
  53. package/src/contexts/payments/README.md +0 -201
  54. package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
  55. package/src/contexts/payments/index.ts +0 -50
  56. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
  57. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
  58. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
  59. package/src/layouts/PaymentsLayout/components/index.ts +0 -2
  60. package/src/layouts/PaymentsLayout/events.ts +0 -47
  61. package/src/layouts/PaymentsLayout/index.ts +0 -16
  62. package/src/layouts/PaymentsLayout/types.ts +0 -6
  63. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
  64. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
  65. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  66. package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
  67. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
  68. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  69. package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
  70. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
  71. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
  72. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
  73. package/src/layouts/SupportLayout/README.md +0 -91
  74. package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
  75. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
  76. package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
  77. package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
  78. package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
  79. package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
  80. package/src/layouts/SupportLayout/components/index.ts +0 -6
  81. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
  82. package/src/layouts/SupportLayout/context/index.ts +0 -2
  83. package/src/layouts/SupportLayout/events.ts +0 -33
  84. package/src/layouts/SupportLayout/hooks/index.ts +0 -2
  85. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
  86. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
  87. package/src/layouts/SupportLayout/index.ts +0 -8
  88. package/src/layouts/SupportLayout/types.ts +0 -21
  89. package/src/snippets/Chat/ChatUIContext.tsx +0 -110
  90. package/src/snippets/Chat/ChatWidget.tsx +0 -476
  91. package/src/snippets/Chat/README.md +0 -122
  92. package/src/snippets/Chat/components/MessageInput.tsx +0 -124
  93. package/src/snippets/Chat/components/MessageList.tsx +0 -169
  94. package/src/snippets/Chat/components/SessionList.tsx +0 -192
  95. package/src/snippets/Chat/components/index.ts +0 -9
  96. package/src/snippets/Chat/hooks/index.ts +0 -6
  97. package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
  98. package/src/snippets/Chat/index.tsx +0 -45
  99. package/src/snippets/Chat/types.ts +0 -80
  100. package/src/snippets/ContactForm/ContactForm.tsx +0 -346
  101. package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
  102. package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
  103. package/src/snippets/ContactForm/ContactPage.tsx +0 -131
  104. package/src/snippets/ContactForm/dynamic.tsx +0 -55
  105. package/src/snippets/ContactForm/index.ts +0 -34
  106. package/src/snippets/ContactForm/types.ts +0 -110
@@ -1,21 +0,0 @@
1
- /**
2
- * Support Layout Types
3
- * Types for SupportLayout - combines API types with UI state
4
- */
5
-
6
- // Note: Ticket and Message types are exported from @djangocfg/layouts/contexts
7
- // Import them directly: import type { Ticket, Message } from '@djangocfg/layouts/contexts';
8
-
9
- // UI State
10
- export interface SupportUIState {
11
- selectedTicketUuid: string | null;
12
- isCreateDialogOpen: boolean;
13
- viewMode: 'list' | 'grid';
14
- }
15
-
16
- // Form types
17
- export interface TicketFormData {
18
- subject: string;
19
- message: string;
20
- }
21
-
@@ -1,110 +0,0 @@
1
- /**
2
- * Chat UI Context
3
- * Manages UI state for chat widget (toggle, expand, minimize)
4
- */
5
-
6
- 'use client';
7
-
8
- import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
9
- import type { ChatUIState } from './types';
10
-
11
- // ─────────────────────────────────────────────────────────────────────────
12
- // Context Type
13
- // ─────────────────────────────────────────────────────────────────────────
14
-
15
- export interface ChatUIContextValue {
16
- uiState: ChatUIState;
17
- toggleChat: () => void;
18
- openChat: () => void;
19
- closeChat: () => void;
20
- expandChat: () => void;
21
- collapseChat: () => void;
22
- minimizeChat: () => void;
23
- toggleSources: () => void;
24
- toggleTimestamps: () => void;
25
- }
26
-
27
- // ─────────────────────────────────────────────────────────────────────────
28
- // Context
29
- // ─────────────────────────────────────────────────────────────────────────
30
-
31
- const ChatUIContext = createContext<ChatUIContextValue | undefined>(undefined);
32
-
33
- // ─────────────────────────────────────────────────────────────────────────
34
- // Provider
35
- // ─────────────────────────────────────────────────────────────────────────
36
-
37
- export interface ChatUIProviderProps {
38
- children: ReactNode;
39
- initialState?: Partial<ChatUIState>;
40
- }
41
-
42
- export function ChatUIProvider({ children, initialState }: ChatUIProviderProps) {
43
- const [uiState, setUIState] = useState<ChatUIState>({
44
- isOpen: false,
45
- isExpanded: false,
46
- isMinimized: false,
47
- showSources: true,
48
- showTimestamps: false,
49
- ...initialState,
50
- });
51
-
52
- const toggleChat = useCallback(() => {
53
- setUIState((prev) => ({ ...prev, isOpen: !prev.isOpen }));
54
- }, []);
55
-
56
- const openChat = useCallback(() => {
57
- setUIState((prev) => ({ ...prev, isOpen: true }));
58
- }, []);
59
-
60
- const closeChat = useCallback(() => {
61
- setUIState((prev) => ({ ...prev, isOpen: false }));
62
- }, []);
63
-
64
- const expandChat = useCallback(() => {
65
- setUIState((prev) => ({ ...prev, isExpanded: true, isMinimized: false }));
66
- }, []);
67
-
68
- const collapseChat = useCallback(() => {
69
- setUIState((prev) => ({ ...prev, isExpanded: false }));
70
- }, []);
71
-
72
- const minimizeChat = useCallback(() => {
73
- setUIState((prev) => ({ ...prev, isMinimized: true, isExpanded: false }));
74
- }, []);
75
-
76
- const toggleSources = useCallback(() => {
77
- setUIState((prev) => ({ ...prev, showSources: !prev.showSources }));
78
- }, []);
79
-
80
- const toggleTimestamps = useCallback(() => {
81
- setUIState((prev) => ({ ...prev, showTimestamps: !prev.showTimestamps }));
82
- }, []);
83
-
84
- const value: ChatUIContextValue = {
85
- uiState,
86
- toggleChat,
87
- openChat,
88
- closeChat,
89
- expandChat,
90
- collapseChat,
91
- minimizeChat,
92
- toggleSources,
93
- toggleTimestamps,
94
- };
95
-
96
- return <ChatUIContext.Provider value={value}>{children}</ChatUIContext.Provider>;
97
- }
98
-
99
- // ─────────────────────────────────────────────────────────────────────────
100
- // Hook
101
- // ─────────────────────────────────────────────────────────────────────────
102
-
103
- export function useChatUI(): ChatUIContextValue {
104
- const context = useContext(ChatUIContext);
105
- if (!context) {
106
- throw new Error('useChatUI must be used within ChatUIProvider');
107
- }
108
- return context;
109
- }
110
-
@@ -1,476 +0,0 @@
1
- /**
2
- * Chat Widget Component
3
- * RAG-powered chat interface with session management
4
- */
5
-
6
- 'use client';
7
-
8
- import React, { useCallback, useEffect, useState } from 'react';
9
- import { createPortal } from 'react-dom';
10
- import { usePathname } from 'next/navigation';
11
- import { Card, CardContent, CardHeader, Button, useIsMobile, useLocalStorage } from '@djangocfg/ui-nextjs';
12
- import {
13
- MessageSquare,
14
- X,
15
- Maximize2,
16
- Minimize2,
17
- Plus,
18
- List,
19
- } from 'lucide-react';
20
- import { useKnowbaseChatContext, useKnowbaseSessionsContext, type ChatSource } from '@djangocfg/layouts/contexts';
21
- import { Enums } from '@djangocfg/api/cfg/generated';
22
- import { chatLogger } from '../../utils/logger';
23
- import { useChatUI } from './ChatUIContext';
24
- import { MessageList, MessageInput, SessionList } from './components';
25
- import type { ChatWidgetProps, ChatMessageWithSources } from './types';
26
-
27
- const WIDGET_MAX_WIDTH = 800;
28
- const WIDGET_MAX_HEIGHT = 900;
29
-
30
- // ─────────────────────────────────────────────────────────────────────────
31
- // Chat Widget Component
32
- // ─────────────────────────────────────────────────────────────────────────
33
-
34
- export const ChatWidget: React.FC<ChatWidgetProps> = ({
35
- autoOpen = false,
36
- persistent = true,
37
- className = '',
38
- onToggle,
39
- onMessage,
40
- }) => {
41
- const { sendQuery, getChatHistory } = useKnowbaseChatContext();
42
- const { createSession, getSession } = useKnowbaseSessionsContext();
43
- const {
44
- uiState,
45
- toggleChat,
46
- expandChat,
47
- collapseChat,
48
- toggleSources,
49
- toggleTimestamps,
50
- } = useChatUI();
51
-
52
- const pathname = usePathname();
53
- const isMobile = useIsMobile();
54
-
55
- const [isLoading, setIsLoading] = useState(false);
56
- const [showSessions, setShowSessions] = useState(false);
57
- const [mounted, setMounted] = useState(false);
58
-
59
- // Persist session ID and messages in localStorage
60
- const [currentSessionId, setCurrentSessionId] = useLocalStorage<string | null>(
61
- 'chat_session_id',
62
- null
63
- );
64
- const [messages, setMessages] = useLocalStorage<ChatMessageWithSources[]>(
65
- 'chat_messages',
66
- []
67
- );
68
-
69
- const isSupport = pathname.startsWith('/private/support');
70
- const isContact = pathname.startsWith('/private/contact');
71
-
72
- // Mount portal target
73
- useEffect(() => {
74
- setMounted(true);
75
- }, []);
76
-
77
- // if isSupport or isContact, don't render
78
- useEffect(() => {
79
- if (isSupport || isContact) {
80
- setMounted(false);
81
- } else {
82
- setMounted(true);
83
- }
84
- }, [isSupport, isContact]);
85
-
86
- // Auto-open on mount if specified
87
- useEffect(() => {
88
- if (autoOpen && !uiState.isOpen) {
89
- toggleChat();
90
- }
91
- }, [autoOpen, uiState.isOpen, toggleChat]);
92
-
93
- // Notify parent of toggle changes
94
- useEffect(() => {
95
- if (onToggle) {
96
- onToggle(uiState.isOpen);
97
- }
98
- }, [uiState.isOpen, onToggle]);
99
-
100
- // Load chat history when session changes (only if it's different from what we had)
101
- useEffect(() => {
102
- if (currentSessionId) {
103
- // Only load from server if we don't have messages locally or session changed
104
- if (messages.length === 0) {
105
- loadChatHistory(currentSessionId);
106
- }
107
- }
108
- }, [currentSessionId]);
109
-
110
- // Load chat history
111
- const loadChatHistory = async (sessionId: string) => {
112
- try {
113
- const history = await getChatHistory(sessionId);
114
- if (history?.messages) {
115
- // Convert ChatMessage[] to ChatMessageWithSources[]
116
- const messagesWithSources: ChatMessageWithSources[] = history.messages.map(msg => ({
117
- ...msg,
118
- sources: undefined, // Sources come from ChatResponse, not ChatMessage
119
- }));
120
- setMessages(messagesWithSources);
121
- }
122
- } catch (error) {
123
- chatLogger.error('Failed to load chat history:', error);
124
- // If we can't load the session, clear it
125
- setCurrentSessionId(null);
126
- setMessages([]);
127
- }
128
- };
129
-
130
- // Handle message sending
131
- const handleSendMessage = useCallback(
132
- async (message: string) => {
133
- try {
134
- setIsLoading(true);
135
-
136
- // Create session if we don't have one
137
- let sessionId = currentSessionId;
138
- if (!sessionId) {
139
- try {
140
- const newSession = await createSession({
141
- title: message.substring(0, 50), // Use first 50 chars as title
142
- });
143
- sessionId = newSession.id;
144
- setCurrentSessionId(sessionId);
145
- chatLogger.info('Created new chat session:', sessionId);
146
- } catch (error) {
147
- chatLogger.error('Failed to create session:', error);
148
- // Continue without session if creation fails
149
- }
150
- }
151
-
152
- // Add user message to UI
153
- const userMessage: ChatMessageWithSources = {
154
- id: `temp-${Date.now()}`,
155
- role: Enums.ChatMessageRole.USER,
156
- content: message,
157
- cost_usd: 0,
158
- created_at: new Date().toISOString(),
159
- };
160
- // Don't use functional form with useLocalStorage - use direct value
161
- const updatedMessages = [...messages, userMessage];
162
- setMessages(updatedMessages);
163
-
164
- // Send query to backend with session ID
165
- const response = await sendQuery({
166
- query: message,
167
- session_id: sessionId || undefined,
168
- });
169
-
170
- // Add assistant response to UI
171
- if (response) {
172
- const assistantMessage: ChatMessageWithSources = {
173
- id: response.message_id,
174
- role: Enums.ChatMessageRole.ASSISTANT,
175
- content: response.content,
176
- cost_usd: response.cost_usd,
177
- tokens_used: response.tokens_used,
178
- processing_time_ms: response.processing_time_ms,
179
- created_at: new Date().toISOString(),
180
- sources: response.sources || undefined,
181
- };
182
- // Don't use functional form with useLocalStorage - use direct value
183
- const finalMessages = [...updatedMessages, assistantMessage];
184
- setMessages(finalMessages);
185
-
186
- if (onMessage) {
187
- onMessage(assistantMessage);
188
- }
189
- }
190
- } catch (error) {
191
- chatLogger.error('Failed to send message:', error);
192
- throw error;
193
- } finally {
194
- setIsLoading(false);
195
- }
196
- },
197
- [sendQuery, currentSessionId, createSession, setCurrentSessionId, messages, setMessages, onMessage]
198
- );
199
-
200
- // Handle new chat creation
201
- const handleNewChat = useCallback(() => {
202
- setMessages([]);
203
- setCurrentSessionId(null);
204
- }, [setMessages, setCurrentSessionId]);
205
-
206
- // Handle session selection
207
- const handleSelectSession = useCallback((sessionId: string) => {
208
- // Clear current messages before loading new session
209
- setMessages([]);
210
- setCurrentSessionId(sessionId);
211
- }, [setMessages, setCurrentSessionId]);
212
-
213
- // Don't render if not mounted (SSR safety)
214
- if (!mounted) {
215
- return null;
216
- }
217
-
218
- // Don't render if not open and not persistent
219
- if (!uiState.isOpen && !persistent) {
220
- return null;
221
- }
222
-
223
- // Mobile layout
224
- if (isMobile) {
225
- const mobileContent = (
226
- <>
227
- <div
228
- className={`fixed inset-0 z-9999 bg-background ${
229
- uiState.isOpen ? 'block' : 'hidden'
230
- } ${className}`}
231
- >
232
- <div className="flex flex-col h-full">
233
- {/* Mobile header */}
234
- <div className="flex items-center justify-between p-4 border-b bg-background shadow-sm">
235
- <div className="flex items-center gap-2">
236
- <MessageSquare className="h-5 w-5 text-primary" />
237
- <h2 className="font-semibold text-foreground">Knowledge Assistant</h2>
238
- </div>
239
-
240
- <div className="flex items-center gap-2">
241
- <Button
242
- variant="ghost"
243
- size="sm"
244
- onClick={() => setShowSessions(true)}
245
- className="text-muted-foreground hover:text-foreground"
246
- title="Sessions"
247
- >
248
- <List className="h-5 w-5" />
249
- </Button>
250
-
251
- <Button
252
- variant="ghost"
253
- size="sm"
254
- onClick={handleNewChat}
255
- className="text-muted-foreground hover:text-foreground"
256
- title="New Chat"
257
- >
258
- <Plus className="h-5 w-5" />
259
- </Button>
260
-
261
- <Button
262
- variant="ghost"
263
- size="sm"
264
- onClick={toggleChat}
265
- className="text-muted-foreground hover:text-foreground"
266
- >
267
- <X className="h-5 w-5" />
268
- </Button>
269
- </div>
270
- </div>
271
-
272
- {/* Chat area */}
273
- <div className="flex-1 flex flex-col overflow-hidden">
274
- <MessageList
275
- messages={messages}
276
- isLoading={isLoading}
277
- showSources={uiState.showSources}
278
- showTimestamps={uiState.showTimestamps}
279
- autoScroll={true}
280
- className="flex-1"
281
- />
282
-
283
- <MessageInput
284
- onSend={handleSendMessage}
285
- isLoading={isLoading}
286
- placeholder="Ask me anything..."
287
- />
288
- </div>
289
- </div>
290
- </div>
291
-
292
- {/* Session List Drawer */}
293
- <SessionList
294
- isOpen={showSessions}
295
- onClose={() => setShowSessions(false)}
296
- onSelectSession={handleSelectSession}
297
- />
298
- </>
299
- );
300
-
301
- return createPortal(mobileContent, document.body);
302
- }
303
-
304
- // Desktop layout
305
- const widgetWidth = uiState.isExpanded ? WIDGET_MAX_WIDTH : 400;
306
- const widgetHeight = uiState.isExpanded ? WIDGET_MAX_HEIGHT : 600;
307
-
308
- // When expanded, add top margin; when collapsed, stick to bottom
309
- const widgetStyle = uiState.isExpanded
310
- ? {
311
- position: 'fixed' as const,
312
- top: '24px',
313
- bottom: '24px',
314
- right: '24px',
315
- left: 'auto',
316
- width: widgetWidth,
317
- height: 'auto',
318
- zIndex: 40,
319
- transformOrigin: 'bottom right' as const,
320
- }
321
- : {
322
- position: 'fixed' as const,
323
- bottom: '24px',
324
- right: '24px',
325
- width: widgetWidth,
326
- height: widgetHeight,
327
- zIndex: 40,
328
- transformOrigin: 'bottom right' as const,
329
- };
330
-
331
- const content = (
332
- <>
333
- {/* Chat Window */}
334
- {uiState.isOpen && (
335
- <div
336
- className="fixed z-40
337
- animate-in fade-in zoom-in-95 slide-in-from-bottom-8
338
- duration-500 ease-out"
339
- style={widgetStyle}
340
- >
341
- <Card className="h-full flex flex-col shadow-2xl border-2 border-primary/20">
342
- {/* Header */}
343
- <CardHeader className="flex-row items-center justify-between space-y-0 pb-3 border-b">
344
- <div className="flex items-center gap-2">
345
- <MessageSquare className="h-5 w-5 text-primary" />
346
- <h3 className="font-semibold text-foreground">Support</h3>
347
- </div>
348
-
349
- <div className="flex items-center gap-1">
350
- <Button
351
- variant="ghost"
352
- size="sm"
353
- onClick={() => setShowSessions(true)}
354
- className="h-8 w-8 p-0"
355
- title="Sessions"
356
- >
357
- <List className="h-4 w-4" />
358
- </Button>
359
-
360
- <Button
361
- variant="ghost"
362
- size="sm"
363
- onClick={handleNewChat}
364
- className="h-8 w-8 p-0"
365
- title="New Chat"
366
- >
367
- <Plus className="h-4 w-4" />
368
- </Button>
369
-
370
- <Button
371
- variant="ghost"
372
- size="sm"
373
- onClick={uiState.isExpanded ? collapseChat : expandChat}
374
- className="h-8 w-8 p-0"
375
- title={uiState.isExpanded ? 'Collapse' : 'Expand'}
376
- >
377
- {uiState.isExpanded ? (
378
- <Minimize2 className="h-4 w-4" />
379
- ) : (
380
- <Maximize2 className="h-4 w-4" />
381
- )}
382
- </Button>
383
-
384
- <Button
385
- variant="ghost"
386
- size="sm"
387
- onClick={toggleChat}
388
- className="h-8 w-8 p-0"
389
- title="Close"
390
- >
391
- <X className="h-4 w-4" />
392
- </Button>
393
- </div>
394
- </CardHeader>
395
-
396
- {/* Content */}
397
- <CardContent className="flex-1 flex flex-col p-0 overflow-hidden">
398
- <MessageList
399
- messages={messages}
400
- isLoading={isLoading}
401
- showSources={uiState.showSources}
402
- showTimestamps={uiState.showTimestamps}
403
- autoScroll={true}
404
- className="flex-1"
405
- />
406
-
407
- <MessageInput
408
- onSend={handleSendMessage}
409
- isLoading={isLoading}
410
- placeholder="Ask me anything..."
411
- />
412
- </CardContent>
413
- </Card>
414
- </div>
415
- )}
416
-
417
- {/* Floating Action Button (when closed) */}
418
- {!uiState.isOpen && persistent && (
419
- <div
420
- className="fixed z-40 animate-in fade-in zoom-in-95 slide-in-from-bottom-8 duration-500"
421
- style={{
422
- bottom: '24px',
423
- right: '24px',
424
- zIndex: 40,
425
- width: '64px',
426
- height: '64px',
427
- borderRadius: '50%',
428
- animation: 'pulse-shadow 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
429
- }}
430
- >
431
- <style>{`
432
- @keyframes pulse-shadow {
433
- 0%, 100% {
434
- box-shadow: 0 0 0 0 hsl(var(--primary) / 0.7);
435
- }
436
- 50% {
437
- box-shadow: 0 0 0 20px hsl(var(--primary) / 0);
438
- }
439
- }
440
- `}</style>
441
- <Button
442
- onClick={toggleChat}
443
- className="w-full h-full rounded-full shadow-2xl
444
- hover:scale-110 hover:rotate-12 active:scale-95
445
- transition-all duration-300 ease-out
446
- flex items-center justify-center
447
- group"
448
- style={{
449
- backgroundColor: 'hsl(var(--primary))',
450
- borderRadius: '50%',
451
- padding: 0,
452
- }}
453
- title="Open Support Chat"
454
- aria-label="Open Support Chat"
455
- >
456
- <MessageSquare
457
- className="h-7 w-7 text-primary-foreground group-hover:scale-110 transition-transform duration-300"
458
- strokeWidth={2}
459
- />
460
- </Button>
461
- </div>
462
- )}
463
-
464
- {/* Session List Drawer */}
465
- <SessionList
466
- isOpen={showSessions}
467
- onClose={() => setShowSessions(false)}
468
- onSelectSession={handleSelectSession}
469
- />
470
- </>
471
- );
472
-
473
- // Render to portal
474
- return createPortal(content, document.body);
475
- };
476
-