@djangocfg/layouts 2.1.10 → 2.1.15

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 (107) 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/ProfileLayout.tsx +2 -2
  13. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
  14. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +2 -2
  15. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  16. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
  17. package/src/layouts/_components/UserMenu.tsx +1 -1
  18. package/src/layouts/index.ts +0 -2
  19. package/src/snippets/Analytics/useAnalytics.ts +1 -1
  20. package/src/snippets/index.ts +0 -3
  21. package/src/auth/README.md +0 -962
  22. package/src/auth/context/AccountsContext.tsx +0 -240
  23. package/src/auth/context/AuthContext.tsx +0 -604
  24. package/src/auth/context/index.ts +0 -4
  25. package/src/auth/context/types.ts +0 -68
  26. package/src/auth/hooks/index.ts +0 -17
  27. package/src/auth/hooks/useAuthForm.ts +0 -332
  28. package/src/auth/hooks/useAuthGuard.ts +0 -25
  29. package/src/auth/hooks/useAuthRedirect.ts +0 -51
  30. package/src/auth/hooks/useAutoAuth.ts +0 -49
  31. package/src/auth/hooks/useGithubAuth.ts +0 -184
  32. package/src/auth/hooks/useLocalStorage.ts +0 -214
  33. package/src/auth/hooks/useProfileCache.ts +0 -146
  34. package/src/auth/hooks/useSessionStorage.ts +0 -189
  35. package/src/auth/index.ts +0 -10
  36. package/src/auth/middlewares/index.ts +0 -1
  37. package/src/auth/middlewares/proxy.ts +0 -32
  38. package/src/auth/server.ts +0 -6
  39. package/src/auth/utils/errors.ts +0 -34
  40. package/src/auth/utils/index.ts +0 -2
  41. package/src/auth/utils/validation.ts +0 -14
  42. package/src/contexts/LeadsContext.tsx +0 -156
  43. package/src/contexts/NewsletterContext.tsx +0 -263
  44. package/src/contexts/SupportContext.tsx +0 -256
  45. package/src/contexts/index.ts +0 -59
  46. package/src/contexts/knowbase/ChatContext.tsx +0 -174
  47. package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
  48. package/src/contexts/knowbase/SessionsContext.tsx +0 -174
  49. package/src/contexts/knowbase/index.ts +0 -61
  50. package/src/contexts/payments/BalancesContext.tsx +0 -65
  51. package/src/contexts/payments/CurrenciesContext.tsx +0 -66
  52. package/src/contexts/payments/OverviewContext.tsx +0 -174
  53. package/src/contexts/payments/PaymentsContext.tsx +0 -132
  54. package/src/contexts/payments/README.md +0 -201
  55. package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
  56. package/src/contexts/payments/index.ts +0 -50
  57. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
  58. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
  59. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
  60. package/src/layouts/PaymentsLayout/components/index.ts +0 -2
  61. package/src/layouts/PaymentsLayout/events.ts +0 -47
  62. package/src/layouts/PaymentsLayout/index.ts +0 -16
  63. package/src/layouts/PaymentsLayout/types.ts +0 -6
  64. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
  65. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
  66. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  67. package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
  68. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
  69. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  70. package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
  71. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
  72. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
  73. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
  74. package/src/layouts/SupportLayout/README.md +0 -91
  75. package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
  76. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
  77. package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
  78. package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
  79. package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
  80. package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
  81. package/src/layouts/SupportLayout/components/index.ts +0 -6
  82. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
  83. package/src/layouts/SupportLayout/context/index.ts +0 -2
  84. package/src/layouts/SupportLayout/events.ts +0 -33
  85. package/src/layouts/SupportLayout/hooks/index.ts +0 -2
  86. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
  87. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
  88. package/src/layouts/SupportLayout/index.ts +0 -8
  89. package/src/layouts/SupportLayout/types.ts +0 -21
  90. package/src/snippets/Chat/ChatUIContext.tsx +0 -110
  91. package/src/snippets/Chat/ChatWidget.tsx +0 -476
  92. package/src/snippets/Chat/README.md +0 -122
  93. package/src/snippets/Chat/components/MessageInput.tsx +0 -124
  94. package/src/snippets/Chat/components/MessageList.tsx +0 -169
  95. package/src/snippets/Chat/components/SessionList.tsx +0 -192
  96. package/src/snippets/Chat/components/index.ts +0 -9
  97. package/src/snippets/Chat/hooks/index.ts +0 -6
  98. package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
  99. package/src/snippets/Chat/index.tsx +0 -45
  100. package/src/snippets/Chat/types.ts +0 -80
  101. package/src/snippets/ContactForm/ContactForm.tsx +0 -346
  102. package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
  103. package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
  104. package/src/snippets/ContactForm/ContactPage.tsx +0 -131
  105. package/src/snippets/ContactForm/dynamic.tsx +0 -55
  106. package/src/snippets/ContactForm/index.ts +0 -34
  107. 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
-