@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,153 +0,0 @@
1
- /**
2
- * Ticket List Component
3
- * Displays a list of support tickets with infinite scroll
4
- */
5
-
6
- 'use client';
7
-
8
- import React, { useEffect, useRef } from 'react';
9
- import { ScrollArea, Skeleton, Button } from '@djangocfg/ui-nextjs';
10
- import { TicketCard } from './TicketCard';
11
- import { useSupportLayoutContext } from '../context';
12
- import { MessageSquare, Loader2 } from 'lucide-react';
13
- import { useInfiniteTickets } from '../hooks';
14
- import { SUPPORT_LAYOUT_EVENTS } from '../events';
15
-
16
- export const TicketList: React.FC = () => {
17
- const { selectedTicket, selectTicket } = useSupportLayoutContext();
18
- const {
19
- tickets,
20
- isLoading,
21
- isLoadingMore,
22
- hasMore,
23
- loadMore,
24
- totalCount,
25
- refresh
26
- } = useInfiniteTickets();
27
-
28
- const scrollRef = useRef<HTMLDivElement>(null);
29
- const observerRef = useRef<IntersectionObserver | null>(null);
30
- const loadMoreRef = useRef<HTMLDivElement>(null);
31
-
32
- // Listen for ticket creation events to refresh the list
33
- useEffect(() => {
34
- const handleTicketCreated = () => {
35
- // Refresh the tickets list when a new ticket is created
36
- refresh();
37
- };
38
-
39
- window.addEventListener(SUPPORT_LAYOUT_EVENTS.TICKET_CREATED, handleTicketCreated);
40
-
41
- return () => {
42
- window.removeEventListener(SUPPORT_LAYOUT_EVENTS.TICKET_CREATED, handleTicketCreated);
43
- };
44
- }, [refresh]);
45
-
46
- // Set up intersection observer for infinite scroll
47
- useEffect(() => {
48
- if (observerRef.current) {
49
- observerRef.current.disconnect();
50
- }
51
-
52
- observerRef.current = new IntersectionObserver(
53
- (entries) => {
54
- if (entries[0]?.isIntersecting && hasMore && !isLoadingMore) {
55
- loadMore();
56
- }
57
- },
58
- { threshold: 0.1 }
59
- );
60
-
61
- if (loadMoreRef.current) {
62
- observerRef.current.observe(loadMoreRef.current);
63
- }
64
-
65
- return () => {
66
- if (observerRef.current) {
67
- observerRef.current.disconnect();
68
- }
69
- };
70
- }, [hasMore, isLoadingMore, loadMore]);
71
-
72
- if (isLoading) {
73
- return (
74
- <div className="p-4 space-y-2">
75
- {[1, 2, 3, 4, 5].map((i) => (
76
- <div key={i}>
77
- <Skeleton
78
- className="h-24 w-full animate-pulse"
79
- style={{ animationDelay: `${i * 100}ms` }}
80
- />
81
- </div>
82
- ))}
83
- </div>
84
- );
85
- }
86
-
87
- if (!tickets || tickets.length === 0) {
88
- return (
89
- <div className="flex flex-col items-center justify-center h-full p-8 text-center animate-in fade-in zoom-in-95 duration-300">
90
- <MessageSquare className="h-16 w-16 text-muted-foreground mb-4 animate-bounce" />
91
- <h3 className="text-lg font-semibold mb-2">No tickets yet</h3>
92
- <p className="text-sm text-muted-foreground max-w-sm">
93
- Create your first support ticket to get help from our team
94
- </p>
95
- </div>
96
- );
97
- }
98
-
99
- return (
100
- <ScrollArea className="h-full" viewportRef={scrollRef}>
101
- <div className="p-4 space-y-2">
102
- {tickets.map((ticket, index) => (
103
- <div
104
- key={ticket.uuid}
105
- className="animate-in fade-in slide-in-from-left-2 duration-300"
106
- style={{ animationDelay: `${Math.min(index, 10) * 50}ms` }}
107
- >
108
- <TicketCard
109
- ticket={ticket}
110
- isSelected={selectedTicket?.uuid === ticket.uuid}
111
- onClick={() => selectTicket(ticket)}
112
- />
113
- </div>
114
- ))}
115
-
116
- {/* Load more trigger */}
117
- <div ref={loadMoreRef} className="h-2" />
118
-
119
- {/* Loading indicator */}
120
- {isLoadingMore && (
121
- <div className="flex justify-center py-4">
122
- <div className="flex items-center gap-2 text-muted-foreground">
123
- <Loader2 className="h-4 w-4 animate-spin" />
124
- <span className="text-sm">Loading more tickets...</span>
125
- </div>
126
- </div>
127
- )}
128
-
129
- {/* Manual load button if needed */}
130
- {hasMore && !isLoadingMore && (
131
- <div className="flex justify-center pt-2 pb-4">
132
- <Button
133
- variant="outline"
134
- size="sm"
135
- onClick={loadMore}
136
- className="text-xs"
137
- >
138
- Load more ({totalCount > 0 ? `${tickets.length}/${totalCount}` : ''})
139
- </Button>
140
- </div>
141
- )}
142
-
143
- {/* End message */}
144
- {!hasMore && tickets.length > 0 && (
145
- <div className="text-center py-4 text-sm text-muted-foreground">
146
- All {totalCount} tickets loaded
147
- </div>
148
- )}
149
- </div>
150
- </ScrollArea>
151
- );
152
- };
153
-
@@ -1,6 +0,0 @@
1
- export * from './TicketCard';
2
- export * from './TicketList';
3
- export * from './MessageList';
4
- export * from './MessageInput';
5
- export * from './CreateTicketDialog';
6
-
@@ -1,263 +0,0 @@
1
- /**
2
- * Support Layout Context
3
- * Wrapper around SupportContext with UI state, event handling and infinite scroll
4
- */
5
-
6
- 'use client';
7
-
8
- import React, { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
9
- import { useSupportContext, type Ticket } from '@djangocfg/layouts/contexts';
10
- import { CfgSupportTypes } from '@djangocfg/api';
11
- import { supportLogger } from '../../../utils/logger';
12
-
13
- type Message = CfgSupportTypes.Message;
14
- type MessageCreateRequest = CfgSupportTypes.MessageCreateRequest;
15
- import { useAuth } from '../../../auth';
16
- import { SUPPORT_LAYOUT_EVENTS } from '../events';
17
- import { useInfiniteMessages } from '../hooks';
18
- import type { SupportUIState, TicketFormData } from '../types';
19
-
20
- // ─────────────────────────────────────────────────────────────────────────
21
- // Context Type
22
- // ─────────────────────────────────────────────────────────────────────────
23
-
24
- export interface SupportLayoutContextValue {
25
- // From API context
26
- tickets: Ticket[] | undefined;
27
- isLoadingTickets: boolean;
28
- ticketsError: Error | undefined;
29
-
30
- // Selected ticket data
31
- selectedTicket: Ticket | undefined;
32
- selectedTicketMessages: Message[] | undefined;
33
- isLoadingMessages: boolean;
34
-
35
- // Infinite scroll for messages
36
- isLoadingMoreMessages: boolean;
37
- hasMoreMessages: boolean;
38
- totalMessagesCount: number;
39
- loadMoreMessages: () => void;
40
-
41
- // UI state
42
- uiState: SupportUIState;
43
-
44
- // Actions
45
- selectTicket: (ticket: Ticket | null) => void;
46
- createTicket: (data: TicketFormData) => Promise<void>;
47
- sendMessage: (message: string) => Promise<void>;
48
- refreshTickets: () => Promise<void>;
49
- refreshMessages: () => Promise<void>;
50
-
51
- // Dialog actions
52
- openCreateDialog: () => void;
53
- closeCreateDialog: () => void;
54
-
55
- // Utilities
56
- getUnreadCount: () => number;
57
- }
58
-
59
- // ─────────────────────────────────────────────────────────────────────────
60
- // Context
61
- // ─────────────────────────────────────────────────────────────────────────
62
-
63
- const SupportLayoutContext = createContext<SupportLayoutContextValue | undefined>(undefined);
64
-
65
- // ─────────────────────────────────────────────────────────────────────────
66
- // Provider
67
- // ─────────────────────────────────────────────────────────────────────────
68
-
69
- interface SupportLayoutProviderProps {
70
- children: ReactNode;
71
- }
72
-
73
- export function SupportLayoutProvider({ children }: SupportLayoutProviderProps) {
74
- const support = useSupportContext();
75
- const { user } = useAuth();
76
-
77
- // UI state
78
- const [uiState, setUIState] = useState<SupportUIState>({
79
- selectedTicketUuid: null,
80
- isCreateDialogOpen: false,
81
- viewMode: 'list',
82
- });
83
-
84
- // Selected ticket
85
- const selectedTicket = support.tickets?.find(t => t.uuid === uiState.selectedTicketUuid);
86
-
87
- // Use infinite scroll hook for messages
88
- const {
89
- messages: selectedTicketMessages,
90
- isLoading: isLoadingMessages,
91
- isLoadingMore: isLoadingMoreMessages,
92
- hasMore: hasMoreMessages,
93
- totalCount: totalMessagesCount,
94
- loadMore: loadMoreMessages,
95
- refresh: refreshMessages,
96
- addMessage: addMessageOptimistically,
97
- } = useInfiniteMessages(selectedTicket?.uuid || null);
98
-
99
- // Select ticket
100
- const selectTicket = useCallback(async (ticket: Ticket | null) => {
101
- setUIState(prev => ({ ...prev, selectedTicketUuid: ticket?.uuid || null }));
102
-
103
- if (ticket?.uuid) {
104
- // The messages will be loaded automatically by the useInfiniteMessages hook
105
- // when the ticket UUID changes
106
-
107
- // Dispatch event
108
- window.dispatchEvent(
109
- new CustomEvent(SUPPORT_LAYOUT_EVENTS.TICKET_SELECTED, { detail: { ticket } })
110
- );
111
- }
112
- }, []);
113
-
114
- // Create ticket
115
- const createTicket = useCallback(async (data: TicketFormData) => {
116
- if (!user?.id) {
117
- throw new Error('User must be authenticated to create tickets');
118
- }
119
-
120
- const ticket = await support.createTicket({
121
- user: user.id,
122
- subject: data.subject,
123
- });
124
-
125
- // Send initial message if provided
126
- if (ticket.uuid && data.message) {
127
- await support.createMessage(ticket.uuid, {
128
- text: data.message,
129
- });
130
- }
131
-
132
- // Close dialog first for better UX
133
- setUIState(prev => ({ ...prev, isCreateDialogOpen: false }));
134
-
135
- // Refresh tickets list to show the new ticket
136
- await support.refreshTickets();
137
-
138
- // Dispatch event
139
- window.dispatchEvent(
140
- new CustomEvent(SUPPORT_LAYOUT_EVENTS.TICKET_CREATED, { detail: { ticket } })
141
- );
142
-
143
- // Auto-select the newly created ticket after refresh
144
- // Use the ticket UUID directly to ensure selection works even if the ticket object changes
145
- setUIState(prev => ({ ...prev, selectedTicketUuid: ticket.uuid }));
146
-
147
- // Dispatch selection event
148
- window.dispatchEvent(
149
- new CustomEvent(SUPPORT_LAYOUT_EVENTS.TICKET_SELECTED, { detail: { ticket } })
150
- );
151
- }, [support, user]);
152
-
153
- // Send message
154
- const sendMessage = useCallback(async (message: string) => {
155
- if (!selectedTicket?.uuid) return;
156
-
157
- // Create the message object
158
- const messageData: MessageCreateRequest = {
159
- text: message,
160
- };
161
-
162
- // Send message to backend
163
- const newMessage = await support.createMessage(selectedTicket.uuid, messageData);
164
-
165
- // Add message optimistically to the UI (it will be replaced when refreshing)
166
- if (newMessage) {
167
- const fullMessage: Message = {
168
- uuid: newMessage.uuid || `temp-${Date.now()}`,
169
- ticket: selectedTicket.uuid,
170
- sender: {
171
- id: user?.id || 0,
172
- display_username: user?.display_username || '',
173
- email: user?.email || '',
174
- avatar: user?.avatar || null,
175
- initials: user?.initials || '',
176
- is_staff: user?.is_staff || false,
177
- is_superuser: user?.is_superuser || false,
178
- },
179
- is_from_author: true,
180
- text: message,
181
- created_at: new Date().toISOString(),
182
- };
183
-
184
- addMessageOptimistically(fullMessage);
185
- }
186
-
187
- // Refresh messages to get the latest state
188
- await refreshMessages();
189
-
190
- // Dispatch event
191
- window.dispatchEvent(
192
- new CustomEvent(SUPPORT_LAYOUT_EVENTS.MESSAGE_SENT, { detail: { message: newMessage } })
193
- );
194
- }, [selectedTicket, support, user, addMessageOptimistically, refreshMessages]);
195
-
196
- // Dialog actions
197
- const openCreateDialog = useCallback(() => {
198
- setUIState(prev => ({ ...prev, isCreateDialogOpen: true }));
199
- }, []);
200
-
201
- const closeCreateDialog = useCallback(() => {
202
- setUIState(prev => ({ ...prev, isCreateDialogOpen: false }));
203
- }, []);
204
-
205
- // Get unread count
206
- const getUnreadCount = useCallback(() => {
207
- return support.tickets?.reduce((count, ticket) => count + (ticket.unanswered_messages_count || 0), 0) || 0;
208
- }, [support.tickets]);
209
-
210
- // Event listeners
211
- useEffect(() => {
212
- const handleOpenDialog = () => openCreateDialog();
213
- const handleCloseDialog = () => closeCreateDialog();
214
-
215
- window.addEventListener(SUPPORT_LAYOUT_EVENTS.OPEN_CREATE_DIALOG, handleOpenDialog);
216
- window.addEventListener(SUPPORT_LAYOUT_EVENTS.CLOSE_CREATE_DIALOG, handleCloseDialog);
217
-
218
- return () => {
219
- window.removeEventListener(SUPPORT_LAYOUT_EVENTS.OPEN_CREATE_DIALOG, handleOpenDialog);
220
- window.removeEventListener(SUPPORT_LAYOUT_EVENTS.CLOSE_CREATE_DIALOG, handleCloseDialog);
221
- };
222
- }, [openCreateDialog, closeCreateDialog]);
223
-
224
- const value: SupportLayoutContextValue = {
225
- tickets: support.tickets,
226
- isLoadingTickets: support.isLoadingTickets,
227
- ticketsError: support.ticketsError,
228
- selectedTicket,
229
- selectedTicketMessages,
230
- isLoadingMessages,
231
- isLoadingMoreMessages,
232
- hasMoreMessages,
233
- totalMessagesCount,
234
- loadMoreMessages,
235
- uiState,
236
- selectTicket,
237
- createTicket,
238
- sendMessage,
239
- refreshTickets: support.refreshTickets,
240
- refreshMessages,
241
- openCreateDialog,
242
- closeCreateDialog,
243
- getUnreadCount,
244
- };
245
-
246
- return (
247
- <SupportLayoutContext.Provider value={value}>
248
- {children}
249
- </SupportLayoutContext.Provider>
250
- );
251
- }
252
-
253
- // ─────────────────────────────────────────────────────────────────────────
254
- // Hook
255
- // ─────────────────────────────────────────────────────────────────────────
256
-
257
- export function useSupportLayoutContext(): SupportLayoutContextValue {
258
- const context = useContext(SupportLayoutContext);
259
- if (!context) {
260
- throw new Error('useSupportLayoutContext must be used within SupportLayoutProvider');
261
- }
262
- return context;
263
- }
@@ -1,2 +0,0 @@
1
- export * from './SupportLayoutContext';
2
-
@@ -1,33 +0,0 @@
1
- "use client"
2
-
3
- /**
4
- * Support Layout Events
5
- * Event system for SupportLayout
6
- */
7
-
8
- export const SUPPORT_LAYOUT_EVENTS = {
9
- // Dialog events
10
- OPEN_CREATE_DIALOG: 'support-layout:open-create-dialog',
11
- CLOSE_CREATE_DIALOG: 'support-layout:close-create-dialog',
12
-
13
- // Ticket events
14
- TICKET_SELECTED: 'support-layout:ticket-selected',
15
- TICKET_CREATED: 'support-layout:ticket-created',
16
-
17
- // Message events
18
- MESSAGE_SENT: 'support-layout:message-sent',
19
- } as const;
20
-
21
- // Event publishers
22
- export const openCreateTicketDialog = () => {
23
- if (typeof window !== 'undefined') {
24
- window.dispatchEvent(new CustomEvent(SUPPORT_LAYOUT_EVENTS.OPEN_CREATE_DIALOG));
25
- }
26
- };
27
-
28
- export const closeCreateTicketDialog = () => {
29
- if (typeof window !== 'undefined') {
30
- window.dispatchEvent(new CustomEvent(SUPPORT_LAYOUT_EVENTS.CLOSE_CREATE_DIALOG));
31
- }
32
- };
33
-
@@ -1,2 +0,0 @@
1
- export * from './useInfiniteTickets';
2
- export * from './useInfiniteMessages';
@@ -1,119 +0,0 @@
1
- /**
2
- * Hook for infinite scroll support messages
3
- * Uses SWR Infinite for pagination
4
- */
5
-
6
- import useSWRInfinite from 'swr/infinite';
7
- import { api, Fetchers, CfgSupportTypes } from '@djangocfg/api';
8
- import type { API } from '@djangocfg/api';
9
-
10
- type PaginatedMessageList = CfgSupportTypes.PaginatedMessageList;
11
- type Message = CfgSupportTypes.Message;
12
-
13
- const PAGE_SIZE = 20;
14
-
15
- interface UseInfiniteMessagesReturn {
16
- messages: Message[];
17
- isLoading: boolean;
18
- isLoadingMore: boolean;
19
- error: Error | undefined;
20
- hasMore: boolean;
21
- totalCount: number;
22
- loadMore: () => void;
23
- refresh: () => Promise<void>;
24
- addMessage: (message: Message) => void;
25
- }
26
-
27
- export function useInfiniteMessages(ticketUuid: string | null): UseInfiniteMessagesReturn {
28
- const getKey = (pageIndex: number, previousPageData: PaginatedMessageList | null) => {
29
- // No ticket selected
30
- if (!ticketUuid) return null;
31
-
32
- // Reached the end
33
- if (previousPageData && !previousPageData.has_next) return null;
34
-
35
- // First page, no previous data
36
- if (pageIndex === 0) return ['cfg-support-messages-infinite', ticketUuid, 1, PAGE_SIZE];
37
-
38
- // Add the page number to the SWR key
39
- return ['cfg-support-messages-infinite', ticketUuid, pageIndex + 1, PAGE_SIZE];
40
- };
41
-
42
- const fetcher = async ([, ticket_uuid, page, pageSize]: [string, string, number, number]) => {
43
- return Fetchers.getSupportTicketsMessagesList(
44
- ticket_uuid,
45
- { page, page_size: pageSize },
46
- api as unknown as API
47
- );
48
- };
49
-
50
- const {
51
- data,
52
- error,
53
- isLoading,
54
- isValidating,
55
- size,
56
- setSize,
57
- mutate,
58
- } = useSWRInfinite<PaginatedMessageList>(getKey, fetcher, {
59
- revalidateFirstPage: false,
60
- parallel: false,
61
- });
62
-
63
- // Flatten all pages into single array (reversed for chat display)
64
- const messages: Message[] = data ? data.flatMap((page) => page.results) : [];
65
-
66
- // Check if there are more pages
67
- const hasMore = data && data[data.length - 1]?.has_next;
68
-
69
- // Total count from last page
70
- const totalCount = data && data[data.length - 1]?.count;
71
-
72
- // Loading more state
73
- const isLoadingMore = !!(isValidating && data && typeof data[size - 1] !== 'undefined');
74
-
75
- // Function to load next page
76
- const loadMore = () => {
77
- if (hasMore && !isLoadingMore) {
78
- setSize(size + 1);
79
- }
80
- };
81
-
82
- // Refresh all pages
83
- const refresh = async () => {
84
- await mutate();
85
- };
86
-
87
- // Add new message optimistically
88
- const addMessage = (message: Message) => {
89
- if (!data || !data[0]) return;
90
-
91
- // Add the message to the first page
92
- const newData = [...data];
93
- const firstPage = newData[0];
94
-
95
- if (firstPage) {
96
- newData[0] = {
97
- ...firstPage,
98
- results: [message, ...firstPage.results],
99
- count: firstPage.count + 1,
100
- page: firstPage.page || 1,
101
- pages: firstPage.pages || 1,
102
- };
103
- }
104
-
105
- mutate(newData, false);
106
- };
107
-
108
- return {
109
- messages,
110
- isLoading,
111
- isLoadingMore: isLoadingMore || false,
112
- error,
113
- hasMore: hasMore || false,
114
- totalCount: totalCount || 0,
115
- loadMore,
116
- refresh,
117
- addMessage,
118
- };
119
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * Hook for infinite scroll support tickets
3
- * Uses SWR Infinite for pagination
4
- */
5
-
6
- import useSWRInfinite from 'swr/infinite';
7
- import { api, Fetchers, CfgSupportTypes } from '@djangocfg/api';
8
- import type { API } from '@djangocfg/api';
9
-
10
- type PaginatedTicketList = CfgSupportTypes.PaginatedTicketList;
11
- type Ticket = CfgSupportTypes.Ticket;
12
-
13
- const PAGE_SIZE = 20;
14
-
15
- interface UseInfiniteTicketsReturn {
16
- tickets: Ticket[];
17
- isLoading: boolean;
18
- isLoadingMore: boolean;
19
- error: any;
20
- hasMore: boolean;
21
- totalCount: number;
22
- loadMore: () => void;
23
- refresh: () => Promise<void>;
24
- }
25
-
26
- export function useInfiniteTickets(): UseInfiniteTicketsReturn {
27
- const getKey = (pageIndex: number, previousPageData: PaginatedTicketList | null) => {
28
- // Reached the end
29
- if (previousPageData && !previousPageData.has_next) return null;
30
-
31
- // First page, no previous data
32
- if (pageIndex === 0) return ['cfg-support-tickets-infinite', 1, PAGE_SIZE];
33
-
34
- // Add the page number to the SWR key
35
- return ['cfg-support-tickets-infinite', pageIndex + 1, PAGE_SIZE];
36
- };
37
-
38
- const fetcher = async ([, page, pageSize]: [string, number, number]) => {
39
- return Fetchers.getSupportTicketsList(
40
- { page, page_size: pageSize },
41
- api as unknown as API
42
- );
43
- };
44
-
45
- const {
46
- data,
47
- error,
48
- isLoading,
49
- isValidating,
50
- size,
51
- setSize,
52
- mutate,
53
- } = useSWRInfinite<PaginatedTicketList>(getKey, fetcher, {
54
- revalidateFirstPage: false,
55
- parallel: false,
56
- });
57
-
58
- // Flatten all pages into single array
59
- const tickets: Ticket[] = data ? data.flatMap((page) => page.results) : [];
60
-
61
- // Check if there are more pages
62
- const hasMore = data && data[data.length - 1]?.has_next;
63
-
64
- // Total count from last page
65
- const totalCount = data && data[data.length - 1]?.count;
66
-
67
- // Loading more state
68
- const isLoadingMore = isValidating && data && typeof data[size - 1] !== 'undefined';
69
-
70
- // Function to load next page
71
- const loadMore = () => {
72
- if (hasMore && !isLoadingMore) {
73
- setSize(size + 1);
74
- }
75
- };
76
-
77
- // Refresh all pages
78
- const refresh = async () => {
79
- await mutate();
80
- };
81
-
82
- return {
83
- tickets,
84
- isLoading,
85
- isLoadingMore: isLoadingMore || false,
86
- error,
87
- hasMore: hasMore || false,
88
- totalCount: totalCount || 0,
89
- loadMore,
90
- refresh,
91
- };
92
- }
@@ -1,8 +0,0 @@
1
- export * from './SupportLayout';
2
- export * from './context';
3
- export * from './events';
4
- // types.ts only contains UI-specific types (SupportUIState, TicketFormData)
5
- // Ticket and Message are exported from @djangocfg/layouts/contexts
6
- export * from './types';
7
- export * from './components';
8
-