@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,139 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
4
+ import { Button } from '@djangocfg/ui';
5
+ import { Send, Loader2 } from 'lucide-react';
6
+
7
+ export interface AIMessageInputProps {
8
+ onSend: (message: string) => void;
9
+ disabled?: boolean;
10
+ isLoading?: boolean;
11
+ placeholder?: string;
12
+ maxRows?: number;
13
+ }
14
+
15
+ export const AIMessageInput = React.memo<AIMessageInputProps>(
16
+ ({
17
+ onSend,
18
+ disabled = false,
19
+ isLoading = false,
20
+ placeholder = 'Ask about DjangoCFG...',
21
+ maxRows = 5,
22
+ }) => {
23
+ const [value, setValue] = useState('');
24
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
25
+
26
+ // Auto-resize textarea
27
+ const adjustHeight = useCallback(() => {
28
+ const textarea = textareaRef.current;
29
+ if (!textarea) return;
30
+
31
+ // Reset height to auto to get the correct scrollHeight
32
+ textarea.style.height = 'auto';
33
+
34
+ // Calculate line height and max height
35
+ const lineHeight = 24; // ~1.5rem
36
+ const minHeight = 44; // Single line height with padding
37
+ const maxHeight = lineHeight * maxRows + 20; // padding
38
+
39
+ // Set new height
40
+ const newHeight = Math.min(Math.max(textarea.scrollHeight, minHeight), maxHeight);
41
+ textarea.style.height = `${newHeight}px`;
42
+ }, [maxRows]);
43
+
44
+ // Adjust height when value changes
45
+ useEffect(() => {
46
+ adjustHeight();
47
+ }, [value, adjustHeight]);
48
+
49
+ const handleSubmit = useCallback(
50
+ (e?: React.FormEvent) => {
51
+ e?.preventDefault();
52
+
53
+ const trimmed = value.trim();
54
+ if (!trimmed || disabled || isLoading) return;
55
+
56
+ onSend(trimmed);
57
+ setValue('');
58
+
59
+ // Reset height after sending
60
+ if (textareaRef.current) {
61
+ textareaRef.current.style.height = 'auto';
62
+ }
63
+ textareaRef.current?.focus();
64
+ },
65
+ [value, disabled, isLoading, onSend]
66
+ );
67
+
68
+ const handleKeyDown = useCallback(
69
+ (e: React.KeyboardEvent) => {
70
+ if (e.key === 'Enter' && !e.shiftKey) {
71
+ e.preventDefault();
72
+ handleSubmit();
73
+ }
74
+ },
75
+ [handleSubmit]
76
+ );
77
+
78
+ const canSend = value.trim().length > 0 && !disabled && !isLoading;
79
+
80
+ return (
81
+ <form onSubmit={handleSubmit} className="w-full">
82
+ <div
83
+ className="relative flex items-end rounded-2xl border border-input bg-background transition-colors focus-within:ring-1 focus-within:ring-ring focus-within:border-ring"
84
+ style={{ minHeight: '44px' }}
85
+ >
86
+ {/* Textarea */}
87
+ <textarea
88
+ ref={textareaRef}
89
+ value={value}
90
+ onChange={(e) => setValue(e.target.value)}
91
+ onKeyDown={handleKeyDown}
92
+ placeholder={placeholder}
93
+ disabled={disabled || isLoading}
94
+ rows={1}
95
+ className="flex-1 resize-none bg-transparent px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 pr-12"
96
+ style={{
97
+ minHeight: '44px',
98
+ maxHeight: `${24 * maxRows + 20}px`,
99
+ lineHeight: '1.5rem',
100
+ }}
101
+ autoComplete="off"
102
+ />
103
+
104
+ {/* Send Button - positioned inside, bottom right */}
105
+ <div
106
+ className="absolute flex items-center justify-center"
107
+ style={{
108
+ right: '6px',
109
+ bottom: '6px',
110
+ }}
111
+ >
112
+ <Button
113
+ type="submit"
114
+ size="icon"
115
+ disabled={!canSend}
116
+ className="h-8 w-8 rounded-full transition-all"
117
+ style={{
118
+ opacity: canSend ? 1 : 0.5,
119
+ }}
120
+ >
121
+ {isLoading ? (
122
+ <Loader2 className="h-4 w-4 animate-spin" />
123
+ ) : (
124
+ <Send className="h-4 w-4" />
125
+ )}
126
+ </Button>
127
+ </div>
128
+ </div>
129
+
130
+ {/* Helper text */}
131
+ <p className="mt-1.5 text-xs text-muted-foreground text-center">
132
+ Press Enter to send, Shift+Enter for new line
133
+ </p>
134
+ </form>
135
+ );
136
+ }
137
+ );
138
+
139
+ AIMessageInput.displayName = 'AIMessageInput';
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ export { ChatWidget } from './ChatWidget';
4
+ export type { ChatWidgetProps } from './ChatWidget';
5
+
6
+ export { AIChatWidget } from './AIChatWidget';
7
+ export type { AIChatWidgetProps } from './AIChatWidget';
8
+
9
+ export { ChatPanel } from './ChatPanel';
10
+ export type { ChatPanelProps } from './ChatPanel';
11
+
12
+ export { ChatMessages } from './ChatMessages';
13
+ export type { ChatMessagesProps, ChatMessagesHandle } from './ChatMessages';
14
+
15
+ export { MessageBubble } from './MessageBubble';
16
+ export type { MessageBubbleProps } from './MessageBubble';
17
+
18
+ export { AIMessageInput } from './MessageInput';
19
+ export type { AIMessageInputProps } from './MessageInput';
20
+
21
+ export { ChatSidebar } from './ChatSidebar';
22
+ export type { ChatSidebarProps } from './ChatSidebar';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * McpChat configuration
3
+ * Environment-aware configuration for MCP server endpoints
4
+ */
5
+
6
+ // Hosts
7
+ const PROD_HOST = 'https://mcp.djangocfg.com';
8
+ const DEV_HOST = 'http://localhost:3002';
9
+
10
+ // Current host based on environment
11
+ const HOST = process.env.NODE_ENV === 'development' ? DEV_HOST : PROD_HOST;
12
+
13
+ /**
14
+ * MCP Server endpoints
15
+ */
16
+ export const mcpEndpoints = {
17
+ /** Base URL */
18
+ baseUrl: HOST,
19
+ /** Chat API endpoint */
20
+ chat: `${HOST}/api/chat`,
21
+ /** Search API endpoint */
22
+ search: `${HOST}/api/search`,
23
+ /** Conversations API endpoint */
24
+ conversations: `${HOST}/api/conversations`,
25
+ /** Health check endpoint */
26
+ health: `${HOST}/health`,
27
+ /** MCP protocol endpoint (Streamable HTTP) */
28
+ mcp: `${HOST}/mcp`,
29
+ /** SSE endpoint for legacy clients */
30
+ sse: `${HOST}/mcp/sse`,
31
+ };
32
+
33
+ // Export defaults for backwards compatibility
34
+ export const DEFAULT_CHAT_API_ENDPOINT = PROD_HOST + '/api/chat';
35
+ export const DEFAULT_MCP_BASE_URL = PROD_HOST;
@@ -0,0 +1,245 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, useState, useCallback, useMemo, useEffect, type ReactNode } from 'react';
4
+ import { useLocalStorage, useIsMobile } from '@djangocfg/ui/hooks';
5
+ import { useAIChat } from '../hooks/useAIChat';
6
+ import { mcpEndpoints, type AIChatMessage, type ChatWidgetConfig, type ChatDisplayMode } from '../types';
7
+
8
+ const STORAGE_KEY_MODE = 'djangocfg-ai-chat-mode';
9
+
10
+ /**
11
+ * AI Chat context state
12
+ */
13
+ export interface AIChatContextState {
14
+ /** All chat messages */
15
+ messages: AIChatMessage[];
16
+ /** Whether a request is in progress */
17
+ isLoading: boolean;
18
+ /** Last error if any */
19
+ error: Error | null;
20
+ /** Whether chat panel is open */
21
+ isOpen: boolean;
22
+ /** Whether chat is minimized */
23
+ isMinimized: boolean;
24
+ /** Configuration */
25
+ config: ChatWidgetConfig;
26
+ /** Current display mode */
27
+ displayMode: ChatDisplayMode;
28
+ /** Is on mobile device */
29
+ isMobile: boolean;
30
+ /** Thread ID for conversation */
31
+ threadId: string;
32
+ /** User ID for conversation */
33
+ userId: string;
34
+ }
35
+
36
+ /**
37
+ * AI Chat context actions
38
+ */
39
+ export interface AIChatContextActions {
40
+ /** Send a message */
41
+ sendMessage: (content: string) => Promise<void>;
42
+ /** Clear all messages */
43
+ clearMessages: () => void;
44
+ /** Open chat panel */
45
+ openChat: () => void;
46
+ /** Close chat panel */
47
+ closeChat: () => void;
48
+ /** Toggle chat panel */
49
+ toggleChat: () => void;
50
+ /** Minimize/restore chat */
51
+ toggleMinimize: () => void;
52
+ /** Set display mode */
53
+ setDisplayMode: (mode: ChatDisplayMode) => void;
54
+ /** Stop streaming response */
55
+ stopStreaming: () => void;
56
+ }
57
+
58
+ export type AIChatContextValue = AIChatContextState & AIChatContextActions;
59
+
60
+ const AIChatContext = createContext<AIChatContextValue | null>(null);
61
+
62
+ /**
63
+ * AI Chat provider props
64
+ */
65
+ export interface AIChatProviderProps {
66
+ children: ReactNode;
67
+ /** API endpoint for AI chat (default: /api/ai/chat) */
68
+ apiEndpoint?: string;
69
+ /** Widget configuration */
70
+ config?: Partial<ChatWidgetConfig>;
71
+ /** Callback on error */
72
+ onError?: (error: Error) => void;
73
+ /** Enable streaming (default: true) */
74
+ enableStreaming?: boolean;
75
+ }
76
+
77
+ /**
78
+ * AI Chat provider component
79
+ * Uses useAIChat hook with server-side persistence
80
+ */
81
+ export function AIChatProvider({
82
+ children,
83
+ apiEndpoint = mcpEndpoints.chat,
84
+ config: userConfig = {},
85
+ onError,
86
+ enableStreaming = true,
87
+ }: AIChatProviderProps) {
88
+ // Use AI chat hook
89
+ const {
90
+ messages,
91
+ isLoading,
92
+ error,
93
+ threadId,
94
+ userId,
95
+ sendMessage: sendAIMessage,
96
+ clearMessages: clearAIMessages,
97
+ stopStreaming,
98
+ } = useAIChat({
99
+ apiEndpoint,
100
+ onError,
101
+ enableStreaming,
102
+ });
103
+
104
+ const [isMinimized, setIsMinimized] = useState(false);
105
+
106
+ // Display mode with localStorage persistence
107
+ const [storedMode, setStoredMode] = useLocalStorage<ChatDisplayMode>(STORAGE_KEY_MODE, 'closed');
108
+
109
+ const isMobile = useIsMobile();
110
+
111
+ // On mobile, sidebar mode is not available - fallback to floating
112
+ const displayMode: ChatDisplayMode = useMemo(() => {
113
+ if (isMobile && storedMode === 'sidebar') {
114
+ return 'floating';
115
+ }
116
+ return storedMode;
117
+ }, [isMobile, storedMode]);
118
+
119
+ // Derived state: isOpen is true when not in 'closed' mode
120
+ const isOpen = displayMode !== 'closed';
121
+
122
+ const config: ChatWidgetConfig = useMemo(
123
+ () => ({
124
+ apiEndpoint,
125
+ title: 'DjangoCFG AI Assistant',
126
+ placeholder: 'Ask about DjangoCFG...',
127
+ greeting:
128
+ "Hi! I'm your DjangoCFG AI assistant powered by GPT. Ask me anything about configuration, features, or how to use the library.",
129
+ position: 'bottom-right',
130
+ variant: 'default',
131
+ ...userConfig,
132
+ }),
133
+ [apiEndpoint, userConfig]
134
+ );
135
+
136
+ const sendMessage = useCallback(
137
+ async (content: string) => {
138
+ await sendAIMessage(content);
139
+ },
140
+ [sendAIMessage]
141
+ );
142
+
143
+ const clearMessages = useCallback(() => {
144
+ clearAIMessages();
145
+ }, [clearAIMessages]);
146
+
147
+ const openChat = useCallback(() => {
148
+ setStoredMode('floating');
149
+ setIsMinimized(false);
150
+ }, [setStoredMode]);
151
+
152
+ const closeChat = useCallback(() => {
153
+ setStoredMode('closed');
154
+ setIsMinimized(false);
155
+ }, [setStoredMode]);
156
+
157
+ const toggleChat = useCallback(() => {
158
+ if (displayMode === 'closed') {
159
+ setStoredMode('floating');
160
+ setIsMinimized(false);
161
+ } else {
162
+ setStoredMode('closed');
163
+ }
164
+ }, [displayMode, setStoredMode]);
165
+
166
+ const toggleMinimize = useCallback(() => {
167
+ setIsMinimized((prev) => !prev);
168
+ }, []);
169
+
170
+ const setDisplayMode = useCallback(
171
+ (mode: ChatDisplayMode) => {
172
+ // On mobile, sidebar is not available
173
+ if (isMobile && mode === 'sidebar') {
174
+ setStoredMode('floating');
175
+ } else {
176
+ setStoredMode(mode);
177
+ }
178
+ setIsMinimized(false);
179
+ },
180
+ [isMobile, setStoredMode]
181
+ );
182
+
183
+ const value = useMemo<AIChatContextValue>(
184
+ () => ({
185
+ messages,
186
+ isLoading,
187
+ error,
188
+ isOpen,
189
+ isMinimized,
190
+ config,
191
+ displayMode,
192
+ isMobile,
193
+ threadId,
194
+ userId,
195
+ sendMessage,
196
+ clearMessages,
197
+ openChat,
198
+ closeChat,
199
+ toggleChat,
200
+ toggleMinimize,
201
+ setDisplayMode,
202
+ stopStreaming,
203
+ }),
204
+ [
205
+ messages,
206
+ isLoading,
207
+ error,
208
+ isOpen,
209
+ isMinimized,
210
+ config,
211
+ displayMode,
212
+ isMobile,
213
+ threadId,
214
+ userId,
215
+ sendMessage,
216
+ clearMessages,
217
+ openChat,
218
+ closeChat,
219
+ toggleChat,
220
+ toggleMinimize,
221
+ setDisplayMode,
222
+ stopStreaming,
223
+ ]
224
+ );
225
+
226
+ return <AIChatContext.Provider value={value}>{children}</AIChatContext.Provider>;
227
+ }
228
+
229
+ /**
230
+ * Hook to access AI chat context
231
+ */
232
+ export function useAIChatContext(): AIChatContextValue {
233
+ const context = useContext(AIChatContext);
234
+ if (!context) {
235
+ throw new Error('useAIChatContext must be used within an AIChatProvider');
236
+ }
237
+ return context;
238
+ }
239
+
240
+ /**
241
+ * Hook to check if AI chat context is available
242
+ */
243
+ export function useAIChatContextOptional(): AIChatContextValue | null {
244
+ return useContext(AIChatContext);
245
+ }