@djangocfg/layouts 2.0.5 → 2.0.7

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 (58) hide show
  1. package/package.json +19 -13
  2. package/src/contexts/LeadsContext.tsx +156 -0
  3. package/src/contexts/NewsletterContext.tsx +263 -0
  4. package/src/contexts/SupportContext.tsx +256 -0
  5. package/src/contexts/index.ts +59 -0
  6. package/src/contexts/knowbase/ChatContext.tsx +174 -0
  7. package/src/contexts/knowbase/DocumentsContext.tsx +304 -0
  8. package/src/contexts/knowbase/SessionsContext.tsx +174 -0
  9. package/src/contexts/knowbase/index.ts +61 -0
  10. package/src/contexts/payments/BalancesContext.tsx +65 -0
  11. package/src/contexts/payments/CurrenciesContext.tsx +66 -0
  12. package/src/contexts/payments/OverviewContext.tsx +174 -0
  13. package/src/contexts/payments/PaymentsContext.tsx +132 -0
  14. package/src/contexts/payments/README.md +201 -0
  15. package/src/contexts/payments/RootPaymentsContext.tsx +68 -0
  16. package/src/contexts/payments/index.ts +50 -0
  17. package/src/index.ts +4 -1
  18. package/src/layouts/AppLayout/AppLayout.tsx +20 -10
  19. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +1 -1
  20. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +1 -1
  21. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +3 -22
  22. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +1 -1
  23. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +1 -1
  24. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +1 -1
  25. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +1 -1
  26. package/src/layouts/SupportLayout/SupportLayout.tsx +1 -1
  27. package/src/layouts/SupportLayout/components/MessageList.tsx +1 -1
  28. package/src/layouts/SupportLayout/components/TicketCard.tsx +1 -1
  29. package/src/layouts/SupportLayout/components/TicketList.tsx +1 -1
  30. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +1 -1
  31. package/src/layouts/SupportLayout/index.ts +2 -0
  32. package/src/layouts/SupportLayout/types.ts +2 -4
  33. package/src/snippets/Chat/ChatWidget.tsx +1 -1
  34. package/src/snippets/Chat/components/MessageList.tsx +1 -1
  35. package/src/snippets/Chat/components/SessionList.tsx +2 -2
  36. package/src/snippets/Chat/index.tsx +1 -1
  37. package/src/snippets/Chat/types.ts +7 -5
  38. package/src/snippets/ContactForm/ContactForm.tsx +20 -8
  39. package/src/snippets/McpChat/components/AIChatWidget.tsx +268 -0
  40. package/src/snippets/McpChat/components/ChatMessages.tsx +151 -0
  41. package/src/snippets/McpChat/components/ChatPanel.tsx +126 -0
  42. package/src/snippets/McpChat/components/ChatSidebar.tsx +119 -0
  43. package/src/snippets/McpChat/components/ChatWidget.tsx +134 -0
  44. package/src/snippets/McpChat/components/MessageBubble.tsx +125 -0
  45. package/src/snippets/McpChat/components/MessageInput.tsx +139 -0
  46. package/src/snippets/McpChat/components/index.ts +22 -0
  47. package/src/snippets/McpChat/config.ts +35 -0
  48. package/src/snippets/McpChat/context/AIChatContext.tsx +245 -0
  49. package/src/snippets/McpChat/context/ChatContext.tsx +350 -0
  50. package/src/snippets/McpChat/context/index.ts +7 -0
  51. package/src/snippets/McpChat/hooks/index.ts +5 -0
  52. package/src/snippets/McpChat/hooks/useAIChat.ts +487 -0
  53. package/src/snippets/McpChat/hooks/useChatLayout.ts +329 -0
  54. package/src/snippets/McpChat/index.ts +76 -0
  55. package/src/snippets/McpChat/types.ts +141 -0
  56. package/src/snippets/index.ts +32 -0
  57. package/src/utils/index.ts +0 -1
  58. package/src/utils/og-image.ts +0 -169
@@ -0,0 +1,350 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useState, useCallback, useMemo, useEffect, useRef, type ReactNode } from 'react';
4
+ import { useLocalStorage, useIsMobile } from '@djangocfg/ui/hooks';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ import type { AIChatMessage, ChatApiResponse, AIChatSource, ChatWidgetConfig, ChatDisplayMode } from '../types';
7
+
8
+ const STORAGE_KEY_MODE = 'djangocfg-chat-mode';
9
+ const STORAGE_KEY_USER_ID = 'djangocfg-chat-user-id';
10
+ const STORAGE_KEY_MESSAGES = 'djangocfg-chat-messages';
11
+ const MAX_STORED_MESSAGES = 50;
12
+
13
+ function generateMessageId(): string {
14
+ return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
15
+ }
16
+
17
+ /**
18
+ * Serialized message format for localStorage
19
+ */
20
+ interface SerializedMessage {
21
+ id: string;
22
+ role: 'user' | 'assistant' | 'system';
23
+ content: string;
24
+ timestamp: string; // ISO string
25
+ sources?: AIChatSource[];
26
+ }
27
+
28
+ /**
29
+ * Chat context state
30
+ */
31
+ export interface ChatContextState {
32
+ /** All chat messages */
33
+ messages: AIChatMessage[];
34
+ /** Whether a request is in progress */
35
+ isLoading: boolean;
36
+ /** Last error if any */
37
+ error: Error | null;
38
+ /** Whether chat panel is open */
39
+ isOpen: boolean;
40
+ /** Whether chat is minimized */
41
+ isMinimized: boolean;
42
+ /** Configuration */
43
+ config: ChatWidgetConfig;
44
+ /** Current display mode */
45
+ displayMode: ChatDisplayMode;
46
+ /** Is on mobile device */
47
+ isMobile: boolean;
48
+ /** User ID for this session */
49
+ userId: string;
50
+ }
51
+
52
+ /**
53
+ * Chat context actions
54
+ */
55
+ export interface ChatContextActions {
56
+ /** Send a message */
57
+ sendMessage: (content: string) => Promise<void>;
58
+ /** Clear all messages */
59
+ clearMessages: () => void;
60
+ /** Open chat panel */
61
+ openChat: () => void;
62
+ /** Close chat panel */
63
+ closeChat: () => void;
64
+ /** Toggle chat panel */
65
+ toggleChat: () => void;
66
+ /** Minimize/restore chat */
67
+ toggleMinimize: () => void;
68
+ /** Set display mode */
69
+ setDisplayMode: (mode: ChatDisplayMode) => void;
70
+ }
71
+
72
+ export type ChatContextValue = ChatContextState & ChatContextActions;
73
+
74
+ const ChatContext = createContext<ChatContextValue | null>(null);
75
+
76
+ /**
77
+ * Chat provider props
78
+ */
79
+ export interface ChatProviderProps {
80
+ children: ReactNode;
81
+ /** API endpoint for chat */
82
+ apiEndpoint?: string;
83
+ /** Widget configuration */
84
+ config?: Partial<ChatWidgetConfig>;
85
+ /** Callback on error */
86
+ onError?: (error: Error) => void;
87
+ }
88
+
89
+ /**
90
+ * Chat provider component
91
+ */
92
+ export function ChatProvider({
93
+ children,
94
+ apiEndpoint = '/api/chat',
95
+ config: userConfig = {},
96
+ onError,
97
+ }: ChatProviderProps) {
98
+ const [messages, setMessages] = useState<AIChatMessage[]>([]);
99
+ const [isLoading, setIsLoading] = useState(false);
100
+ const [error, setError] = useState<Error | null>(null);
101
+ const [isMinimized, setIsMinimized] = useState(false);
102
+ const isHydratedRef = useRef(false);
103
+
104
+ // Display mode with localStorage persistence
105
+ const [storedMode, setStoredMode] = useLocalStorage<ChatDisplayMode>(STORAGE_KEY_MODE, 'closed');
106
+
107
+ // User ID with localStorage persistence
108
+ const [userId, setUserId] = useLocalStorage<string>(STORAGE_KEY_USER_ID, '');
109
+
110
+ // Messages storage (serialized)
111
+ const [storedMessages, setStoredMessages] = useLocalStorage<SerializedMessage[]>(STORAGE_KEY_MESSAGES, []);
112
+
113
+ const isMobile = useIsMobile();
114
+
115
+ // Generate user ID if not exists
116
+ useEffect(() => {
117
+ if (!userId) {
118
+ setUserId(uuidv4());
119
+ }
120
+ }, [userId, setUserId]);
121
+
122
+ // Load messages from localStorage on mount (once)
123
+ useEffect(() => {
124
+ if (isHydratedRef.current) return;
125
+ isHydratedRef.current = true;
126
+
127
+ if (storedMessages.length > 0) {
128
+ const hydratedMessages: AIChatMessage[] = storedMessages.map((msg) => ({
129
+ ...msg,
130
+ timestamp: new Date(msg.timestamp),
131
+ }));
132
+ setMessages(hydratedMessages);
133
+ }
134
+ // eslint-disable-next-line react-hooks/exhaustive-deps
135
+ }, [storedMessages]); // Depend on storedMessages to wait for localStorage
136
+
137
+ // Save messages to localStorage when they change
138
+ useEffect(() => {
139
+ if (!isHydratedRef.current) return;
140
+
141
+ const toStore: SerializedMessage[] = messages.slice(-MAX_STORED_MESSAGES).map((msg) => ({
142
+ id: msg.id,
143
+ role: msg.role,
144
+ content: msg.content,
145
+ timestamp: msg.timestamp.toISOString(),
146
+ sources: msg.sources,
147
+ }));
148
+ setStoredMessages(toStore);
149
+ // eslint-disable-next-line react-hooks/exhaustive-deps
150
+ }, [messages]);
151
+
152
+ // On mobile, sidebar mode is not available - fallback to floating
153
+ const displayMode: ChatDisplayMode = useMemo(() => {
154
+ if (isMobile && storedMode === 'sidebar') {
155
+ return 'floating';
156
+ }
157
+ return storedMode;
158
+ }, [isMobile, storedMode]);
159
+
160
+ // Derived state: isOpen is true when not in 'closed' mode
161
+ const isOpen = displayMode !== 'closed';
162
+
163
+ const config: ChatWidgetConfig = useMemo(
164
+ () => ({
165
+ apiEndpoint,
166
+ title: 'DjangoCFG Assistant',
167
+ placeholder: 'Ask about DjangoCFG...',
168
+ greeting:
169
+ "Hi! I'm your DjangoCFG documentation assistant. Ask me anything about configuration, features, or how to use the library.",
170
+ position: 'bottom-right',
171
+ variant: 'default',
172
+ ...userConfig,
173
+ }),
174
+ [apiEndpoint, userConfig]
175
+ );
176
+
177
+ const sendMessage = useCallback(
178
+ async (content: string) => {
179
+ if (!content.trim() || isLoading) return;
180
+
181
+ const userMessage: AIChatMessage = {
182
+ id: generateMessageId(),
183
+ role: 'user',
184
+ content: content.trim(),
185
+ timestamp: new Date(),
186
+ };
187
+
188
+ setMessages((prev) => [...prev, userMessage]);
189
+ setIsLoading(true);
190
+ setError(null);
191
+
192
+ try {
193
+ const response = await fetch(config.apiEndpoint || apiEndpoint, {
194
+ method: 'POST',
195
+ headers: { 'Content-Type': 'application/json' },
196
+ body: JSON.stringify({
197
+ query: content,
198
+ userId: userId || undefined,
199
+ }),
200
+ });
201
+
202
+ if (!response.ok) {
203
+ throw new Error(`HTTP error: ${response.status}`);
204
+ }
205
+
206
+ const data: ChatApiResponse = await response.json();
207
+
208
+ if (!data.success) {
209
+ throw new Error(data.error || 'Failed to get response');
210
+ }
211
+
212
+ const sources: AIChatSource[] =
213
+ data.results?.map((r) => ({
214
+ title: r.chunk.title,
215
+ path: r.chunk.path,
216
+ url: r.chunk.url,
217
+ section: r.chunk.section,
218
+ score: r.score,
219
+ })) || [];
220
+
221
+ const assistantMessage: AIChatMessage = {
222
+ id: generateMessageId(),
223
+ role: 'assistant',
224
+ content: data.answer || 'I found some relevant documentation.',
225
+ timestamp: new Date(),
226
+ sources,
227
+ };
228
+
229
+ setMessages((prev) => [...prev, assistantMessage]);
230
+ } catch (err) {
231
+ const error = err instanceof Error ? err : new Error('Unknown error');
232
+ setError(error);
233
+ onError?.(error);
234
+
235
+ const errorMessage: AIChatMessage = {
236
+ id: generateMessageId(),
237
+ role: 'assistant',
238
+ content: `Sorry, I encountered an error: ${error.message}. Please try again.`,
239
+ timestamp: new Date(),
240
+ };
241
+
242
+ setMessages((prev) => [...prev, errorMessage]);
243
+ } finally {
244
+ setIsLoading(false);
245
+ }
246
+ },
247
+ [apiEndpoint, config.apiEndpoint, isLoading, onError, userId]
248
+ );
249
+
250
+ const clearMessages = useCallback(() => {
251
+ setMessages([]);
252
+ setStoredMessages([]);
253
+ setError(null);
254
+ }, [setStoredMessages]);
255
+
256
+ const openChat = useCallback(() => {
257
+ setStoredMode('floating');
258
+ setIsMinimized(false);
259
+ }, [setStoredMode]);
260
+
261
+ const closeChat = useCallback(() => {
262
+ setStoredMode('closed');
263
+ setIsMinimized(false);
264
+ }, [setStoredMode]);
265
+
266
+ const toggleChat = useCallback(() => {
267
+ if (displayMode === 'closed') {
268
+ setStoredMode('floating');
269
+ setIsMinimized(false);
270
+ } else {
271
+ setStoredMode('closed');
272
+ }
273
+ }, [displayMode, setStoredMode]);
274
+
275
+ const toggleMinimize = useCallback(() => {
276
+ setIsMinimized((prev) => !prev);
277
+ }, []);
278
+
279
+ const setDisplayMode = useCallback(
280
+ (mode: ChatDisplayMode) => {
281
+ // On mobile, sidebar is not available
282
+ if (isMobile && mode === 'sidebar') {
283
+ setStoredMode('floating');
284
+ } else {
285
+ setStoredMode(mode);
286
+ }
287
+ setIsMinimized(false);
288
+ },
289
+ [isMobile, setStoredMode]
290
+ );
291
+
292
+ const value = useMemo<ChatContextValue>(
293
+ () => ({
294
+ messages,
295
+ isLoading,
296
+ error,
297
+ isOpen,
298
+ isMinimized,
299
+ config,
300
+ displayMode,
301
+ isMobile,
302
+ userId: userId || '',
303
+ sendMessage,
304
+ clearMessages,
305
+ openChat,
306
+ closeChat,
307
+ toggleChat,
308
+ toggleMinimize,
309
+ setDisplayMode,
310
+ }),
311
+ [
312
+ messages,
313
+ isLoading,
314
+ error,
315
+ isOpen,
316
+ isMinimized,
317
+ config,
318
+ displayMode,
319
+ isMobile,
320
+ userId,
321
+ sendMessage,
322
+ clearMessages,
323
+ openChat,
324
+ closeChat,
325
+ toggleChat,
326
+ toggleMinimize,
327
+ setDisplayMode,
328
+ ]
329
+ );
330
+
331
+ return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
332
+ }
333
+
334
+ /**
335
+ * Hook to access chat context
336
+ */
337
+ export function useChatContext(): ChatContextValue {
338
+ const context = useContext(ChatContext);
339
+ if (!context) {
340
+ throw new Error('useChatContext must be used within a ChatProvider');
341
+ }
342
+ return context;
343
+ }
344
+
345
+ /**
346
+ * Hook to check if chat context is available
347
+ */
348
+ export function useChatContextOptional(): ChatContextValue | null {
349
+ return useContext(ChatContext);
350
+ }
@@ -0,0 +1,7 @@
1
+ 'use client';
2
+
3
+ export { ChatProvider, useChatContext, useChatContextOptional } from './ChatContext';
4
+ export type { ChatContextState, ChatContextActions, ChatContextValue, ChatProviderProps } from './ChatContext';
5
+
6
+ export { AIChatProvider, useAIChatContext, useAIChatContextOptional } from './AIChatContext';
7
+ export type { AIChatContextState, AIChatContextActions, AIChatContextValue, AIChatProviderProps } from './AIChatContext';
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+
3
+ export { useAIChat } from './useAIChat';
4
+ export { useChatLayout } from './useChatLayout';
5
+ export type { ChatLayoutConfig, UseChatLayoutReturn } from './useChatLayout';