@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.
Files changed (97) hide show
  1. package/README.md +3 -17
  2. package/package.json +18 -18
  3. package/src/components/errors/ErrorLayout.tsx +2 -2
  4. package/src/components/errors/ErrorsTracker/index.ts +1 -0
  5. package/src/components/errors/ErrorsTracker/utils/formatters.ts +23 -1
  6. package/src/hooks/useLogout.ts +9 -12
  7. package/src/layouts/AppLayout/AppLayout.tsx +20 -8
  8. package/src/layouts/AppLayout/BaseApp.tsx +5 -28
  9. package/src/layouts/AuthLayout/AuthLayout.tsx +51 -22
  10. package/src/layouts/AuthLayout/README.md +78 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +2 -2
  12. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -2
  13. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +2 -2
  14. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +3 -2
  15. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +4 -1
  16. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +2 -2
  17. package/src/layouts/AuthLayout/components/shared/index.ts +0 -2
  18. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +25 -80
  19. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +8 -13
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +2 -2
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +2 -2
  22. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +2 -2
  23. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +61 -42
  24. package/src/layouts/AuthLayout/context.tsx +0 -2
  25. package/src/layouts/AuthLayout/index.ts +9 -6
  26. package/src/layouts/AuthLayout/styles/auth.css +265 -120
  27. package/src/layouts/AuthLayout/types.ts +60 -7
  28. package/src/layouts/ProfileLayout/.claude/.sidecar/activity.jsonl +2 -0
  29. package/src/layouts/ProfileLayout/.claude/.sidecar/history/2026-03-15.md +35 -0
  30. package/src/layouts/ProfileLayout/.claude/.sidecar/review.md +35 -0
  31. package/src/layouts/ProfileLayout/.claude/.sidecar/scan.log +3 -0
  32. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-001.md +18 -0
  33. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-002.md +19 -0
  34. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-003.md +18 -0
  35. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-004.md +18 -0
  36. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-005.md +18 -0
  37. package/src/layouts/ProfileLayout/.claude/.sidecar/usage.json +5 -0
  38. package/src/layouts/ProfileLayout/ProfileLayout.tsx +52 -403
  39. package/src/layouts/ProfileLayout/components/ActionButton.tsx +38 -0
  40. package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +109 -148
  41. package/src/layouts/ProfileLayout/components/EditableField.tsx +119 -0
  42. package/src/layouts/ProfileLayout/components/Section.tsx +22 -0
  43. package/src/layouts/ProfileLayout/components/index.ts +4 -1
  44. package/src/layouts/ProfileLayout/context.tsx +31 -0
  45. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
  46. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
  47. package/src/layouts/_components/UserMenu.tsx +2 -2
  48. package/src/layouts/types/README.md +0 -20
  49. package/src/layouts/types/index.ts +2 -2
  50. package/src/layouts/types/layout.types.ts +2 -5
  51. package/src/layouts/types/providers.types.ts +0 -27
  52. package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
  53. package/src/snippets/Breadcrumbs.tsx +2 -2
  54. package/src/snippets/index.ts +0 -67
  55. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +0 -56
  56. package/src/snippets/McpChat/README.md +0 -441
  57. package/src/snippets/McpChat/components/AIChatWidget.tsx +0 -361
  58. package/src/snippets/McpChat/components/AskAIButton.tsx +0 -92
  59. package/src/snippets/McpChat/components/ChatMessages.tsx +0 -138
  60. package/src/snippets/McpChat/components/ChatPanel.tsx +0 -131
  61. package/src/snippets/McpChat/components/ChatSidebar.tsx +0 -156
  62. package/src/snippets/McpChat/components/ChatWidget.tsx +0 -115
  63. package/src/snippets/McpChat/components/MessageBubble.tsx +0 -142
  64. package/src/snippets/McpChat/components/MessageInput.tsx +0 -140
  65. package/src/snippets/McpChat/components/index.ts +0 -24
  66. package/src/snippets/McpChat/config.ts +0 -94
  67. package/src/snippets/McpChat/context/AIChatContext.tsx +0 -327
  68. package/src/snippets/McpChat/context/ChatContext.tsx +0 -361
  69. package/src/snippets/McpChat/context/index.ts +0 -7
  70. package/src/snippets/McpChat/hooks/index.ts +0 -6
  71. package/src/snippets/McpChat/hooks/useAIChat.ts +0 -503
  72. package/src/snippets/McpChat/hooks/useChatLayout.ts +0 -442
  73. package/src/snippets/McpChat/hooks/useMcpChat.ts +0 -90
  74. package/src/snippets/McpChat/index.ts +0 -79
  75. package/src/snippets/McpChat/types.ts +0 -189
  76. package/src/snippets/PWAInstall/@docs/README.md +0 -92
  77. package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +0 -576
  78. package/src/snippets/PWAInstall/README.md +0 -235
  79. package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -236
  80. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +0 -234
  81. package/src/snippets/PWAInstall/components/IOSGuide.tsx +0 -29
  82. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +0 -103
  83. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +0 -103
  84. package/src/snippets/PWAInstall/components/PWAPageResumeManager.tsx +0 -33
  85. package/src/snippets/PWAInstall/context/InstallContext.tsx +0 -102
  86. package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +0 -168
  87. package/src/snippets/PWAInstall/hooks/useIsPWA.ts +0 -116
  88. package/src/snippets/PWAInstall/hooks/usePWAPageResume.ts +0 -163
  89. package/src/snippets/PWAInstall/index.ts +0 -80
  90. package/src/snippets/PWAInstall/types/components.ts +0 -95
  91. package/src/snippets/PWAInstall/types/config.ts +0 -29
  92. package/src/snippets/PWAInstall/types/index.ts +0 -26
  93. package/src/snippets/PWAInstall/types/install.ts +0 -38
  94. package/src/snippets/PWAInstall/types/platform.ts +0 -29
  95. package/src/snippets/PWAInstall/utils/localStorage.ts +0 -181
  96. package/src/snippets/PWAInstall/utils/logger.ts +0 -149
  97. package/src/snippets/PWAInstall/utils/platform.ts +0 -151
@@ -1,503 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback, useEffect, useRef, useState } from 'react';
4
-
5
- import {
6
- type AIChatMessage, type AIChatSource, mcpEndpoints, type UseAIChatOptions, type UseAIChatReturn
7
- } from '../types';
8
-
9
- function generateId(): string {
10
- return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
11
- }
12
-
13
- function generateThreadId(): string {
14
- return `thread_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
15
- }
16
-
17
- function generateUserId(): string {
18
- return `user_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
19
- }
20
-
21
- const STORAGE_KEY = 'djangocfg_chat';
22
-
23
- /**
24
- * Get or create persistent chat IDs from localStorage
25
- */
26
- function getPersistedIds(): { threadId: string; userId: string } {
27
- if (typeof window === 'undefined') {
28
- return { threadId: generateThreadId(), userId: generateUserId() };
29
- }
30
-
31
- try {
32
- const stored = localStorage.getItem(STORAGE_KEY);
33
- if (stored) {
34
- const data = JSON.parse(stored);
35
- if (data.threadId && data.userId) {
36
- return data;
37
- }
38
- }
39
- } catch {
40
- // Ignore parse errors
41
- }
42
-
43
- // Generate new IDs and persist
44
- const ids = { threadId: generateThreadId(), userId: generateUserId() };
45
- try {
46
- localStorage.setItem(STORAGE_KEY, JSON.stringify(ids));
47
- } catch {
48
- // Ignore storage errors
49
- }
50
- return ids;
51
- }
52
-
53
- /**
54
- * Update persisted thread ID (after clear)
55
- */
56
- function persistThreadId(threadId: string, userId: string): void {
57
- if (typeof window === 'undefined') return;
58
- try {
59
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ threadId, userId }));
60
- } catch {
61
- // Ignore storage errors
62
- }
63
- }
64
-
65
- /**
66
- * Save message to server
67
- */
68
- async function saveMessageToServer(threadId: string, userId: string, message: AIChatMessage): Promise<void> {
69
- try {
70
- await fetch(`${mcpEndpoints.conversations}/${threadId}/messages`, {
71
- method: 'POST',
72
- headers: { 'Content-Type': 'application/json' },
73
- body: JSON.stringify({
74
- userId,
75
- message: {
76
- id: message.id,
77
- role: message.role,
78
- content: message.content,
79
- timestamp: message.timestamp.getTime(),
80
- sources: message.sources,
81
- },
82
- }),
83
- });
84
- } catch (error) {
85
- console.warn('[Chat] Failed to save message to server:', error);
86
- }
87
- }
88
-
89
- /**
90
- * Load conversation from server
91
- */
92
- async function loadConversationFromServer(threadId: string): Promise<AIChatMessage[] | null> {
93
- try {
94
- const response = await fetch(`${mcpEndpoints.conversations}/${threadId}`);
95
-
96
- // If conversation not found (404), return null to signal reset needed
97
- if (response.status === 404) return null;
98
-
99
- if (!response.ok) return [];
100
-
101
- const data = await response.json();
102
- if (!data.messages || !Array.isArray(data.messages)) return [];
103
-
104
- return data.messages.map((m: {
105
- id: string;
106
- role: 'user' | 'assistant';
107
- content: string;
108
- timestamp: number;
109
- sources?: AIChatSource[];
110
- }) => ({
111
- id: m.id,
112
- role: m.role,
113
- content: m.content,
114
- timestamp: new Date(m.timestamp),
115
- sources: m.sources,
116
- }));
117
- } catch (error) {
118
- console.warn('[Chat] Failed to load conversation from server:', error);
119
- return [];
120
- }
121
- }
122
-
123
- /**
124
- * Delete conversation from server
125
- */
126
- async function deleteConversationFromServer(threadId: string): Promise<void> {
127
- try {
128
- await fetch(`${mcpEndpoints.conversations}/${threadId}`, {
129
- method: 'DELETE',
130
- });
131
- } catch (error) {
132
- console.warn('[Chat] Failed to delete conversation from server:', error);
133
- }
134
- }
135
-
136
- /**
137
- * AI Chat hook with streaming support and server-side history
138
- * All persistence is handled through API endpoints - no localStorage
139
- */
140
- export function useAIChat(options: UseAIChatOptions): UseAIChatReturn {
141
- const {
142
- apiEndpoint = mcpEndpoints.chat,
143
- initialMessages = [],
144
- onError,
145
- enableStreaming = true,
146
- threadId: initialThreadId,
147
- userId: initialUserId,
148
- } = options;
149
-
150
- const [messages, setMessages] = useState<AIChatMessage[]>(initialMessages);
151
- const [isLoadingHistory, setIsLoadingHistory] = useState(true);
152
-
153
- // Get persisted IDs from localStorage (or generate new ones)
154
- const persistedIds = useRef<{ threadId: string; userId: string } | null>(null);
155
- if (persistedIds.current === null && typeof window !== 'undefined') {
156
- persistedIds.current = getPersistedIds();
157
- }
158
-
159
- const [threadId, setThreadId] = useState<string>(() =>
160
- initialThreadId || persistedIds.current?.threadId || generateThreadId()
161
- );
162
- const [userId] = useState<string>(() =>
163
- initialUserId || persistedIds.current?.userId || generateUserId()
164
- );
165
-
166
- const [isLoading, setIsLoading] = useState(false);
167
- const [error, setError] = useState<Error | null>(null);
168
-
169
- const abortControllerRef = useRef<AbortController | null>(null);
170
-
171
- // Load conversation from server on mount (always try to restore from persisted threadId)
172
- useEffect(() => {
173
- if (typeof window === 'undefined') {
174
- setIsLoadingHistory(false);
175
- return;
176
- }
177
-
178
- const loadHistory = async () => {
179
- const serverMessages = await loadConversationFromServer(threadId);
180
-
181
- // If server returned null (404), session is expired/invalid
182
- if (serverMessages === null) {
183
- console.log('[Chat] Session expired or invalid, starting new session');
184
- const newThreadId = generateThreadId();
185
- setThreadId(newThreadId);
186
- persistThreadId(newThreadId, userId);
187
- setMessages([]); // Ensure empty state
188
- } else if (serverMessages.length > 0) {
189
- setMessages(serverMessages);
190
- }
191
-
192
- setIsLoadingHistory(false);
193
- };
194
- loadHistory();
195
- }, [threadId, userId]);
196
-
197
- /**
198
- * Send message with streaming support
199
- */
200
- const sendMessage = useCallback(
201
- async (content: string) => {
202
- if (!content.trim() || isLoading) return;
203
-
204
- // Abort any previous request
205
- if (abortControllerRef.current) {
206
- abortControllerRef.current.abort();
207
- }
208
- abortControllerRef.current = new AbortController();
209
-
210
- // Add user message
211
- const userMessage: AIChatMessage = {
212
- id: generateId(),
213
- role: 'user',
214
- content: content.trim(),
215
- timestamp: new Date(),
216
- };
217
-
218
- // Add placeholder for assistant message
219
- const assistantMessageId = generateId();
220
- const assistantMessage: AIChatMessage = {
221
- id: assistantMessageId,
222
- role: 'assistant',
223
- content: '',
224
- timestamp: new Date(),
225
- isStreaming: true,
226
- };
227
-
228
- setMessages((prev) => [...prev, userMessage, assistantMessage]);
229
- setIsLoading(true);
230
- setError(null);
231
-
232
- // Save user message to server
233
- saveMessageToServer(threadId, userId, userMessage);
234
-
235
- try {
236
- // Build messages array from history + current message (for OpenAI format)
237
- const chatMessages = [
238
- ...messages
239
- .filter((m) => m.role !== 'system')
240
- .slice(-10) // Keep last 10 messages for context
241
- .map((m) => ({
242
- role: m.role as 'user' | 'assistant',
243
- content: m.content,
244
- })),
245
- { role: 'user' as const, content },
246
- ];
247
-
248
- const response = await fetch(apiEndpoint, {
249
- method: 'POST',
250
- headers: {
251
- 'Content-Type': 'application/json',
252
- },
253
- body: JSON.stringify({
254
- messages: chatMessages,
255
- stream: enableStreaming,
256
- }),
257
- signal: abortControllerRef.current.signal,
258
- });
259
-
260
- if (!response.ok) {
261
- throw new Error(`HTTP error: ${response.status}`);
262
- }
263
-
264
- if (enableStreaming && response.headers.get('content-type')?.includes('text/event-stream')) {
265
- // Handle streaming response
266
- await handleStreamingResponse(response, assistantMessageId);
267
- } else {
268
- // Handle non-streaming response
269
- const data = await response.json();
270
-
271
- if (!data.success) {
272
- throw new Error(data.error || 'Failed to get response');
273
- }
274
-
275
- // Update thread ID if returned
276
- if (data.threadId && data.threadId !== threadId) {
277
- setThreadId(data.threadId);
278
- }
279
-
280
- // Extract sources
281
- const sources: AIChatSource[] =
282
- data.sources?.map((s: { title: string; path: string; url?: string; section?: string; score?: number }) => ({
283
- title: s.title,
284
- path: s.path,
285
- url: s.url,
286
- section: s.section,
287
- score: s.score,
288
- })) || [];
289
-
290
- const finalContent = data.content || 'I found some relevant documentation.';
291
-
292
- // Update assistant message
293
- setMessages((prev) =>
294
- prev.map((m) =>
295
- m.id === assistantMessageId
296
- ? {
297
- ...m,
298
- content: finalContent,
299
- sources,
300
- isStreaming: false,
301
- }
302
- : m
303
- )
304
- );
305
-
306
- // Save assistant message to server
307
- saveMessageToServer(threadId, userId, {
308
- id: assistantMessageId,
309
- role: 'assistant',
310
- content: finalContent,
311
- timestamp: new Date(),
312
- sources,
313
- });
314
- }
315
- } catch (err) {
316
- if (err instanceof Error && err.name === 'AbortError') {
317
- // Request was aborted, remove the assistant message
318
- setMessages((prev) => prev.filter((m) => m.id !== assistantMessageId));
319
- return;
320
- }
321
-
322
- const error = err instanceof Error ? err : new Error('Unknown error');
323
- setError(error);
324
- onError?.(error);
325
-
326
- // Update assistant message with error
327
- setMessages((prev) =>
328
- prev.map((m) =>
329
- m.id === assistantMessageId
330
- ? {
331
- ...m,
332
- content: `Sorry, I encountered an error: ${error.message}. Please try again.`,
333
- isStreaming: false,
334
- }
335
- : m
336
- )
337
- );
338
- } finally {
339
- setIsLoading(false);
340
- abortControllerRef.current = null;
341
- }
342
- },
343
- [apiEndpoint, isLoading, messages, threadId, userId, enableStreaming, onError]
344
- );
345
-
346
- /**
347
- * Handle streaming SSE response
348
- */
349
- const handleStreamingResponse = async (response: Response, messageId: string) => {
350
- const reader = response.body?.getReader();
351
- if (!reader) {
352
- throw new Error('No response body');
353
- }
354
-
355
- const decoder = new TextDecoder();
356
- let buffer = '';
357
- let fullContent = '';
358
- const sources: AIChatSource[] = [];
359
-
360
- try {
361
- while (true) {
362
- const { done, value } = await reader.read();
363
- if (done) break;
364
-
365
- buffer += decoder.decode(value, { stream: true });
366
-
367
- // Process complete events
368
- const lines = buffer.split('\n');
369
- buffer = lines.pop() || ''; // Keep incomplete line in buffer
370
-
371
- for (const line of lines) {
372
- if (line.startsWith('data: ')) {
373
- const data = line.slice(6);
374
-
375
- if (data === '[DONE]') {
376
- // Stream complete
377
- setMessages((prev) =>
378
- prev.map((m) =>
379
- m.id === messageId
380
- ? {
381
- ...m,
382
- content: fullContent,
383
- sources,
384
- isStreaming: false,
385
- }
386
- : m
387
- )
388
- );
389
-
390
- // Save final assistant message to server
391
- saveMessageToServer(threadId, userId, {
392
- id: messageId,
393
- role: 'assistant',
394
- content: fullContent,
395
- timestamp: new Date(),
396
- sources,
397
- });
398
- return;
399
- }
400
-
401
- try {
402
- const parsed = JSON.parse(data);
403
-
404
- if (parsed.type === 'text' && parsed.content) {
405
- fullContent += parsed.content;
406
- // Update message with current content
407
- setMessages((prev) =>
408
- prev.map((m) =>
409
- m.id === messageId
410
- ? {
411
- ...m,
412
- content: fullContent,
413
- isStreaming: true,
414
- }
415
- : m
416
- )
417
- );
418
- } else if (parsed.type === 'source' && parsed.source) {
419
- sources.push({
420
- title: parsed.source.title,
421
- path: parsed.source.path,
422
- url: parsed.source.url,
423
- section: parsed.source.section,
424
- score: parsed.source.score,
425
- });
426
- } else if (parsed.type === 'done') {
427
- // Update with final usage info if needed
428
- setMessages((prev) =>
429
- prev.map((m) =>
430
- m.id === messageId
431
- ? {
432
- ...m,
433
- content: fullContent,
434
- sources,
435
- isStreaming: false,
436
- }
437
- : m
438
- )
439
- );
440
-
441
- // Save final assistant message to server
442
- saveMessageToServer(threadId, userId, {
443
- id: messageId,
444
- role: 'assistant',
445
- content: fullContent,
446
- timestamp: new Date(),
447
- sources,
448
- });
449
- } else if (parsed.type === 'error') {
450
- throw new Error(parsed.error || 'Stream error');
451
- }
452
- } catch {
453
- // Ignore parse errors for individual events
454
- }
455
- }
456
- }
457
- }
458
- } finally {
459
- reader.releaseLock();
460
- }
461
- };
462
-
463
- /**
464
- * Clear all messages and start new conversation
465
- */
466
- const clearMessages = useCallback(async () => {
467
- // Abort any ongoing request
468
- if (abortControllerRef.current) {
469
- abortControllerRef.current.abort();
470
- }
471
-
472
- // Delete conversation from server
473
- await deleteConversationFromServer(threadId);
474
-
475
- setMessages([]);
476
- setError(null);
477
-
478
- // Generate new thread ID for fresh conversation and persist it
479
- const newThreadId = generateThreadId();
480
- setThreadId(newThreadId);
481
- persistThreadId(newThreadId, userId);
482
- }, [threadId, userId]);
483
-
484
- /**
485
- * Stop current streaming response
486
- */
487
- const stopStreaming = useCallback(() => {
488
- if (abortControllerRef.current) {
489
- abortControllerRef.current.abort();
490
- }
491
- }, []);
492
-
493
- return {
494
- messages,
495
- isLoading: isLoading || isLoadingHistory,
496
- error,
497
- threadId,
498
- userId,
499
- sendMessage,
500
- clearMessages,
501
- stopStreaming,
502
- };
503
- }