@djangocfg/ext-support 1.0.0

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 (65) hide show
  1. package/README.md +233 -0
  2. package/dist/chunk-AZ4LWZB7.js +2630 -0
  3. package/dist/hooks.cjs +2716 -0
  4. package/dist/hooks.d.cts +255 -0
  5. package/dist/hooks.d.ts +255 -0
  6. package/dist/hooks.js +1 -0
  7. package/dist/index.cjs +2693 -0
  8. package/dist/index.d.cts +1392 -0
  9. package/dist/index.d.ts +1392 -0
  10. package/dist/index.js +1 -0
  11. package/package.json +80 -0
  12. package/src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts +642 -0
  13. package/src/api/generated/ext_support/_utils/fetchers/index.ts +28 -0
  14. package/src/api/generated/ext_support/_utils/hooks/ext_support__support.ts +237 -0
  15. package/src/api/generated/ext_support/_utils/hooks/index.ts +28 -0
  16. package/src/api/generated/ext_support/_utils/schemas/Message.schema.ts +21 -0
  17. package/src/api/generated/ext_support/_utils/schemas/MessageCreate.schema.ts +15 -0
  18. package/src/api/generated/ext_support/_utils/schemas/MessageCreateRequest.schema.ts +15 -0
  19. package/src/api/generated/ext_support/_utils/schemas/MessageRequest.schema.ts +15 -0
  20. package/src/api/generated/ext_support/_utils/schemas/PaginatedMessageList.schema.ts +24 -0
  21. package/src/api/generated/ext_support/_utils/schemas/PaginatedTicketList.schema.ts +24 -0
  22. package/src/api/generated/ext_support/_utils/schemas/PatchedMessageRequest.schema.ts +15 -0
  23. package/src/api/generated/ext_support/_utils/schemas/PatchedTicketRequest.schema.ts +18 -0
  24. package/src/api/generated/ext_support/_utils/schemas/Sender.schema.ts +21 -0
  25. package/src/api/generated/ext_support/_utils/schemas/Ticket.schema.ts +21 -0
  26. package/src/api/generated/ext_support/_utils/schemas/TicketRequest.schema.ts +18 -0
  27. package/src/api/generated/ext_support/_utils/schemas/index.ts +29 -0
  28. package/src/api/generated/ext_support/api-instance.ts +131 -0
  29. package/src/api/generated/ext_support/client.ts +301 -0
  30. package/src/api/generated/ext_support/enums.ts +45 -0
  31. package/src/api/generated/ext_support/errors.ts +116 -0
  32. package/src/api/generated/ext_support/ext_support__support/client.ts +151 -0
  33. package/src/api/generated/ext_support/ext_support__support/index.ts +2 -0
  34. package/src/api/generated/ext_support/ext_support__support/models.ts +165 -0
  35. package/src/api/generated/ext_support/http.ts +103 -0
  36. package/src/api/generated/ext_support/index.ts +273 -0
  37. package/src/api/generated/ext_support/logger.ts +259 -0
  38. package/src/api/generated/ext_support/retry.ts +175 -0
  39. package/src/api/generated/ext_support/schema.json +1049 -0
  40. package/src/api/generated/ext_support/storage.ts +161 -0
  41. package/src/api/generated/ext_support/validation-events.ts +133 -0
  42. package/src/api/index.ts +9 -0
  43. package/src/config.ts +20 -0
  44. package/src/contexts/SupportContext.tsx +250 -0
  45. package/src/contexts/SupportExtensionProvider.tsx +38 -0
  46. package/src/contexts/types.ts +26 -0
  47. package/src/hooks/index.ts +33 -0
  48. package/src/index.ts +39 -0
  49. package/src/layouts/SupportLayout/README.md +91 -0
  50. package/src/layouts/SupportLayout/SupportLayout.tsx +179 -0
  51. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +155 -0
  52. package/src/layouts/SupportLayout/components/MessageInput.tsx +92 -0
  53. package/src/layouts/SupportLayout/components/MessageList.tsx +312 -0
  54. package/src/layouts/SupportLayout/components/TicketCard.tsx +96 -0
  55. package/src/layouts/SupportLayout/components/TicketList.tsx +153 -0
  56. package/src/layouts/SupportLayout/components/index.ts +6 -0
  57. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +258 -0
  58. package/src/layouts/SupportLayout/context/index.ts +2 -0
  59. package/src/layouts/SupportLayout/events.ts +33 -0
  60. package/src/layouts/SupportLayout/hooks/index.ts +2 -0
  61. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +115 -0
  62. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +88 -0
  63. package/src/layouts/SupportLayout/index.ts +6 -0
  64. package/src/layouts/SupportLayout/types.ts +21 -0
  65. package/src/utils/logger.ts +14 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Storage adapters for cross-platform token storage.
3
+ *
4
+ * Supports:
5
+ * - LocalStorage (browser)
6
+ * - Cookies (SSR/browser)
7
+ * - Memory (Node.js/Electron/testing)
8
+ */
9
+
10
+ import type { APILogger } from './logger';
11
+
12
+ /**
13
+ * Storage adapter interface for cross-platform token storage.
14
+ */
15
+ export interface StorageAdapter {
16
+ getItem(key: string): string | null;
17
+ setItem(key: string, value: string): void;
18
+ removeItem(key: string): void;
19
+ }
20
+
21
+ /**
22
+ * LocalStorage adapter with safe try-catch for browser environments.
23
+ * Works in modern browsers with localStorage support.
24
+ *
25
+ * Note: This adapter uses window.localStorage and should only be used in browser/client environments.
26
+ * For server-side usage, use MemoryStorageAdapter or CookieStorageAdapter instead.
27
+ */
28
+ export class LocalStorageAdapter implements StorageAdapter {
29
+ private logger?: APILogger;
30
+
31
+ constructor(logger?: APILogger) {
32
+ this.logger = logger;
33
+ }
34
+
35
+ getItem(key: string): string | null {
36
+ try {
37
+ if (typeof window !== 'undefined' && window.localStorage) {
38
+ const value = localStorage.getItem(key);
39
+ this.logger?.debug(`LocalStorage.getItem("${key}"): ${value ? 'found' : 'not found'}`);
40
+ return value;
41
+ }
42
+ this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
43
+ } catch (error) {
44
+ this.logger?.error('LocalStorage.getItem failed:', error);
45
+ }
46
+ return null;
47
+ }
48
+
49
+ setItem(key: string, value: string): void {
50
+ try {
51
+ if (typeof window !== 'undefined' && window.localStorage) {
52
+ localStorage.setItem(key, value);
53
+ this.logger?.debug(`LocalStorage.setItem("${key}"): success`);
54
+ } else {
55
+ this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
56
+ }
57
+ } catch (error) {
58
+ this.logger?.error('LocalStorage.setItem failed:', error);
59
+ }
60
+ }
61
+
62
+ removeItem(key: string): void {
63
+ try {
64
+ if (typeof window !== 'undefined' && window.localStorage) {
65
+ localStorage.removeItem(key);
66
+ this.logger?.debug(`LocalStorage.removeItem("${key}"): success`);
67
+ } else {
68
+ this.logger?.warn('LocalStorage not available: window.localStorage is undefined');
69
+ }
70
+ } catch (error) {
71
+ this.logger?.error('LocalStorage.removeItem failed:', error);
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Cookie-based storage adapter for SSR and browser environments.
78
+ * Useful for Next.js, Nuxt.js, and other SSR frameworks.
79
+ */
80
+ export class CookieStorageAdapter implements StorageAdapter {
81
+ private logger?: APILogger;
82
+
83
+ constructor(logger?: APILogger) {
84
+ this.logger = logger;
85
+ }
86
+
87
+ getItem(key: string): string | null {
88
+ try {
89
+ if (typeof document === 'undefined') {
90
+ this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
91
+ return null;
92
+ }
93
+ const value = `; ${document.cookie}`;
94
+ const parts = value.split(`; ${key}=`);
95
+ if (parts.length === 2) {
96
+ const result = parts.pop()?.split(';').shift() || null;
97
+ this.logger?.debug(`CookieStorage.getItem("${key}"): ${result ? 'found' : 'not found'}`);
98
+ return result;
99
+ }
100
+ this.logger?.debug(`CookieStorage.getItem("${key}"): not found`);
101
+ } catch (error) {
102
+ this.logger?.error('CookieStorage.getItem failed:', error);
103
+ }
104
+ return null;
105
+ }
106
+
107
+ setItem(key: string, value: string): void {
108
+ try {
109
+ if (typeof document !== 'undefined') {
110
+ document.cookie = `${key}=${value}; path=/; max-age=31536000`;
111
+ this.logger?.debug(`CookieStorage.setItem("${key}"): success`);
112
+ } else {
113
+ this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
114
+ }
115
+ } catch (error) {
116
+ this.logger?.error('CookieStorage.setItem failed:', error);
117
+ }
118
+ }
119
+
120
+ removeItem(key: string): void {
121
+ try {
122
+ if (typeof document !== 'undefined') {
123
+ document.cookie = `${key}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
124
+ this.logger?.debug(`CookieStorage.removeItem("${key}"): success`);
125
+ } else {
126
+ this.logger?.warn('Cookies not available: document is undefined (SSR context?)');
127
+ }
128
+ } catch (error) {
129
+ this.logger?.error('CookieStorage.removeItem failed:', error);
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * In-memory storage adapter for Node.js, Electron, and testing environments.
136
+ * Data is stored in RAM and cleared when process exits.
137
+ */
138
+ export class MemoryStorageAdapter implements StorageAdapter {
139
+ private storage: Map<string, string> = new Map();
140
+ private logger?: APILogger;
141
+
142
+ constructor(logger?: APILogger) {
143
+ this.logger = logger;
144
+ }
145
+
146
+ getItem(key: string): string | null {
147
+ const value = this.storage.get(key) || null;
148
+ this.logger?.debug(`MemoryStorage.getItem("${key}"): ${value ? 'found' : 'not found'}`);
149
+ return value;
150
+ }
151
+
152
+ setItem(key: string, value: string): void {
153
+ this.storage.set(key, value);
154
+ this.logger?.debug(`MemoryStorage.setItem("${key}"): success`);
155
+ }
156
+
157
+ removeItem(key: string): void {
158
+ this.storage.delete(key);
159
+ this.logger?.debug(`MemoryStorage.removeItem("${key}"): success`);
160
+ }
161
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Zod Validation Events - Browser CustomEvent integration
3
+ *
4
+ * Dispatches browser CustomEvents when Zod validation fails, allowing
5
+ * React/frontend apps to listen and handle validation errors globally.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In your React app
10
+ * window.addEventListener('zod-validation-error', (event) => {
11
+ * const { operation, path, method, error, response } = event.detail;
12
+ * console.error(`Validation failed for ${method} ${path}`, error);
13
+ * // Show toast notification, log to Sentry, etc.
14
+ * });
15
+ * ```
16
+ */
17
+
18
+ import type { ZodError } from 'zod'
19
+
20
+ /**
21
+ * Validation error event detail
22
+ */
23
+ export interface ValidationErrorDetail {
24
+ /** Operation/function name that failed validation */
25
+ operation: string
26
+ /** API endpoint path */
27
+ path: string
28
+ /** HTTP method */
29
+ method: string
30
+ /** Zod validation error */
31
+ error: ZodError
32
+ /** Raw response data that failed validation */
33
+ response: any
34
+ /** Timestamp of the error */
35
+ timestamp: Date
36
+ }
37
+
38
+ /**
39
+ * Custom event type for Zod validation errors
40
+ */
41
+ export type ValidationErrorEvent = CustomEvent<ValidationErrorDetail>
42
+
43
+ /**
44
+ * Dispatch a Zod validation error event.
45
+ *
46
+ * Only dispatches in browser environment (when window is defined).
47
+ * Safe to call in Node.js/SSR - will be a no-op.
48
+ *
49
+ * @param detail - Validation error details
50
+ */
51
+ export function dispatchValidationError(detail: ValidationErrorDetail): void {
52
+ // Check if running in browser
53
+ if (typeof window === 'undefined') {
54
+ return
55
+ }
56
+
57
+ try {
58
+ const event = new CustomEvent<ValidationErrorDetail>('zod-validation-error', {
59
+ detail,
60
+ bubbles: true,
61
+ cancelable: false,
62
+ })
63
+
64
+ window.dispatchEvent(event)
65
+ } catch (error) {
66
+ // Silently fail - validation event dispatch should never crash the app
67
+ console.warn('Failed to dispatch validation error event:', error)
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Add a global listener for Zod validation errors.
73
+ *
74
+ * @param callback - Function to call when validation error occurs
75
+ * @returns Cleanup function to remove the listener
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const cleanup = onValidationError(({ operation, error }) => {
80
+ * toast.error(`Validation failed in ${operation}`);
81
+ * logToSentry(error);
82
+ * });
83
+ *
84
+ * // Later, remove listener
85
+ * cleanup();
86
+ * ```
87
+ */
88
+ export function onValidationError(
89
+ callback: (detail: ValidationErrorDetail) => void
90
+ ): () => void {
91
+ if (typeof window === 'undefined') {
92
+ // Return no-op cleanup function for SSR
93
+ return () => {}
94
+ }
95
+
96
+ const handler = (event: Event) => {
97
+ if (event instanceof CustomEvent) {
98
+ callback(event.detail)
99
+ }
100
+ }
101
+
102
+ window.addEventListener('zod-validation-error', handler)
103
+
104
+ // Return cleanup function
105
+ return () => {
106
+ window.removeEventListener('zod-validation-error', handler)
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Format Zod error for logging/display.
112
+ *
113
+ * @param error - Zod validation error
114
+ * @returns Formatted error message
115
+ */
116
+ export function formatZodError(error: ZodError): string {
117
+ const issues = error.issues.map((issue, index) => {
118
+ const path = issue.path.join('.') || 'root'
119
+ const parts = [`${index + 1}. ${path}: ${issue.message}`]
120
+
121
+ if ('expected' in issue && issue.expected) {
122
+ parts.push(` Expected: ${issue.expected}`)
123
+ }
124
+
125
+ if ('received' in issue && issue.received) {
126
+ parts.push(` Received: ${issue.received}`)
127
+ }
128
+
129
+ return parts.join('\n')
130
+ })
131
+
132
+ return issues.join('\n')
133
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Support Extension API
3
+ *
4
+ * Pre-configured API instance with shared authentication
5
+ */
6
+ import { API } from './generated/ext_support';
7
+ import { createExtensionAPI } from '@djangocfg/ext-base/api';
8
+
9
+ export const apiSupport = createExtensionAPI(API);
package/src/config.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Support extension configuration
3
+ */
4
+
5
+ import type { ExtensionMetadata } from '@djangocfg/ext-base';
6
+
7
+ export const extensionConfig: ExtensionMetadata = {
8
+ name: 'support',
9
+ version: '1.0.0',
10
+ author: 'DjangoCFG',
11
+ displayName: 'Support',
12
+ description: 'Customer support and ticketing system',
13
+ icon: '🎫',
14
+ license: 'MIT',
15
+ githubUrl: 'https://github.com/markolofsen/django-cfg',
16
+ homepage: 'https://djangocfg.com',
17
+ keywords: ['support', 'tickets', 'helpdesk', 'customer-service'],
18
+ dependencies: [],
19
+ minVersion: '2.0.0',
20
+ } as const;
@@ -0,0 +1,250 @@
1
+ 'use client';
2
+
3
+ import React, { createContext, useContext, type ReactNode } from 'react';
4
+ import {
5
+ useSupportTicketsList,
6
+ useSupportTicketsRetrieve,
7
+ useCreateSupportTicketsCreate,
8
+ useUpdateSupportTicketsUpdate,
9
+ usePartialUpdateSupportTicketsPartialUpdate,
10
+ useDeleteSupportTicketsDestroy,
11
+ useSupportTicketsMessagesList,
12
+ useSupportTicketsMessagesRetrieve,
13
+ useCreateSupportTicketsMessagesCreate,
14
+ useUpdateSupportTicketsMessagesUpdate,
15
+ usePartialUpdateSupportTicketsMessagesPartialUpdate,
16
+ useDeleteSupportTicketsMessagesDestroy,
17
+ } from '../api/generated/ext_support/_utils/hooks';
18
+ import type {
19
+ Ticket,
20
+ TicketRequest,
21
+ PatchedTicketRequest,
22
+ Message,
23
+ MessageRequest,
24
+ MessageCreateRequest,
25
+ PatchedMessageRequest,
26
+ } from './types';
27
+
28
+ // ─────────────────────────────────────────────────────────────────────────
29
+ // Context Type
30
+ // ─────────────────────────────────────────────────────────────────────────
31
+
32
+ export interface SupportContextValue {
33
+ // Tickets
34
+ tickets: Ticket[] | undefined;
35
+ isLoadingTickets: boolean;
36
+ ticketsError: Error | undefined;
37
+ refreshTickets: () => Promise<void>;
38
+
39
+ // Ticket operations
40
+ getTicket: (uuid: string) => Promise<Ticket | undefined>;
41
+ createTicket: (data: TicketRequest) => Promise<Ticket>;
42
+ updateTicket: (uuid: string, data: TicketRequest) => Promise<Ticket>;
43
+ partialUpdateTicket: (uuid: string, data: PatchedTicketRequest) => Promise<Ticket>;
44
+ deleteTicket: (uuid: string) => Promise<void>;
45
+
46
+ // Messages
47
+ getMessages: (ticketUuid: string) => Promise<Message[] | undefined>;
48
+ getMessage: (ticketUuid: string, messageUuid: string) => Promise<Message | undefined>;
49
+ createMessage: (ticketUuid: string, data: MessageCreateRequest) => Promise<Message>;
50
+ updateMessage: (ticketUuid: string, messageUuid: string, data: MessageRequest) => Promise<Message>;
51
+ partialUpdateMessage: (
52
+ ticketUuid: string,
53
+ messageUuid: string,
54
+ data: PatchedMessageRequest
55
+ ) => Promise<Message>;
56
+ deleteMessage: (ticketUuid: string, messageUuid: string) => Promise<void>;
57
+ refreshMessages: (ticketUuid: string) => Promise<void>;
58
+ }
59
+
60
+ // ─────────────────────────────────────────────────────────────────────────
61
+ // Context
62
+ // ─────────────────────────────────────────────────────────────────────────
63
+
64
+ const SupportContext = createContext<SupportContextValue | undefined>(undefined);
65
+
66
+ // ─────────────────────────────────────────────────────────────────────────
67
+ // Provider
68
+ // ─────────────────────────────────────────────────────────────────────────
69
+
70
+ export function SupportProvider({ children }: { children: ReactNode }) {
71
+ // List tickets (first page only for count)
72
+ const {
73
+ data: ticketsData,
74
+ error: ticketsError,
75
+ isLoading: isLoadingTickets,
76
+ mutate: mutateTickets,
77
+ } = useSupportTicketsList({ page: 1, page_size: 1 });
78
+
79
+ const refreshTickets = async () => {
80
+ await mutateTickets();
81
+ };
82
+
83
+ // Ticket mutations
84
+ const createTicketMutation = useCreateSupportTicketsCreate();
85
+ const updateTicketMutation = useUpdateSupportTicketsUpdate();
86
+ const partialUpdateTicketMutation = usePartialUpdateSupportTicketsPartialUpdate();
87
+ const deleteTicketMutation = useDeleteSupportTicketsDestroy();
88
+
89
+ // Message mutations
90
+ const createMessageMutation = useCreateSupportTicketsMessagesCreate();
91
+ const updateMessageMutation = useUpdateSupportTicketsMessagesUpdate();
92
+ const partialUpdateMessageMutation = usePartialUpdateSupportTicketsMessagesPartialUpdate();
93
+ const deleteMessageMutation = useDeleteSupportTicketsMessagesDestroy();
94
+
95
+ // Get single ticket
96
+ const getTicket = async (uuid: string): Promise<Ticket | undefined> => {
97
+ const { data } = useSupportTicketsRetrieve(uuid);
98
+ return data;
99
+ };
100
+
101
+ // Create ticket
102
+ const createTicket = async (data: TicketRequest): Promise<Ticket> => {
103
+ const result = await createTicketMutation(data);
104
+ await refreshTickets();
105
+ return result as Ticket;
106
+ };
107
+
108
+ // Update ticket
109
+ const updateTicket = async (uuid: string, data: TicketRequest): Promise<Ticket> => {
110
+ const result = await updateTicketMutation(uuid, data);
111
+ await refreshTickets();
112
+ return result as Ticket;
113
+ };
114
+
115
+ // Partial update ticket (currently not supported by generated API)
116
+ const partialUpdateTicket = async (
117
+ uuid: string,
118
+ data: PatchedTicketRequest
119
+ ): Promise<Ticket> => {
120
+ // TODO: Fix generator to include data parameter for PATCH requests
121
+ // const result = await partialUpdateTicketMutation(uuid, data);
122
+ // For now, fallback to full update
123
+ const result = await updateTicketMutation(uuid, data as unknown as TicketRequest);
124
+ await refreshTickets();
125
+ return result as Ticket;
126
+ };
127
+
128
+ // Delete ticket
129
+ const deleteTicket = async (uuid: string): Promise<void> => {
130
+ await deleteTicketMutation(uuid);
131
+ await refreshTickets();
132
+ };
133
+
134
+ // Get messages for ticket
135
+ const getMessages = async (ticketUuid: string): Promise<Message[] | undefined> => {
136
+ const { data } = useSupportTicketsMessagesList(ticketUuid, { page: 1, page_size: 100 });
137
+ return data?.results;
138
+ };
139
+
140
+ // Get single message
141
+ const getMessage = async (
142
+ ticketUuid: string,
143
+ messageUuid: string
144
+ ): Promise<Message | undefined> => {
145
+ const { data } = useSupportTicketsMessagesRetrieve(
146
+ ticketUuid,
147
+ messageUuid
148
+ );
149
+ return data;
150
+ };
151
+
152
+ // Create message
153
+ const createMessage = async (
154
+ ticketUuid: string,
155
+ data: MessageCreateRequest
156
+ ): Promise<Message> => {
157
+ const result = await createMessageMutation(ticketUuid, data);
158
+ return result as Message;
159
+ };
160
+
161
+ // Update message
162
+ const updateMessage = async (
163
+ ticketUuid: string,
164
+ messageUuid: string,
165
+ data: MessageRequest
166
+ ): Promise<Message> => {
167
+ const result = await updateMessageMutation(
168
+ ticketUuid,
169
+ messageUuid,
170
+ data
171
+ );
172
+ return result as Message;
173
+ };
174
+
175
+ // Partial update message (currently not supported by generated API)
176
+ const partialUpdateMessage = async (
177
+ ticketUuid: string,
178
+ messageUuid: string,
179
+ data: PatchedMessageRequest
180
+ ): Promise<Message> => {
181
+ // TODO: Fix generator to include data parameter for PATCH requests
182
+ // const result = await partialUpdateMessageMutation(ticketUuid, messageUuid, data);
183
+ // For now, fallback to full update
184
+ const result = await updateMessageMutation(
185
+ ticketUuid,
186
+ messageUuid,
187
+ data as MessageRequest
188
+ );
189
+ return result as Message;
190
+ };
191
+
192
+ // Delete message
193
+ const deleteMessage = async (ticketUuid: string, messageUuid: string): Promise<void> => {
194
+ await deleteMessageMutation(ticketUuid, messageUuid);
195
+ };
196
+
197
+ // Refresh messages for specific ticket
198
+ const refreshMessages = async (ticketUuid: string): Promise<void> => {
199
+ // We'll use mutate from the hook, but we need to get it dynamically
200
+ // For now, we can just refresh tickets which will update everything
201
+ await refreshTickets();
202
+ };
203
+
204
+ const value: SupportContextValue = {
205
+ tickets: ticketsData?.results,
206
+ isLoadingTickets,
207
+ ticketsError,
208
+ refreshTickets,
209
+ getTicket,
210
+ createTicket,
211
+ updateTicket,
212
+ partialUpdateTicket,
213
+ deleteTicket,
214
+ getMessages,
215
+ getMessage,
216
+ createMessage,
217
+ updateMessage,
218
+ partialUpdateMessage,
219
+ deleteMessage,
220
+ refreshMessages,
221
+ };
222
+
223
+ return <SupportContext.Provider value={value}>{children}</SupportContext.Provider>;
224
+ }
225
+
226
+ // ─────────────────────────────────────────────────────────────────────────
227
+ // Hook
228
+ // ─────────────────────────────────────────────────────────────────────────
229
+
230
+ export function useSupportContext(): SupportContextValue {
231
+ const context = useContext(SupportContext);
232
+ if (!context) {
233
+ throw new Error('useSupportContext must be used within SupportProvider');
234
+ }
235
+ return context;
236
+ }
237
+
238
+ // ─────────────────────────────────────────────────────────────────────────
239
+ // Re-export types for external use
240
+ // ─────────────────────────────────────────────────────────────────────────
241
+
242
+ export type {
243
+ Ticket,
244
+ TicketRequest,
245
+ PatchedTicketRequest,
246
+ Message,
247
+ MessageRequest,
248
+ MessageCreateRequest,
249
+ PatchedMessageRequest,
250
+ } from './types';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Main Support Extension Provider
3
+ *
4
+ * Wraps support context with ExtensionProvider for proper registration
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import type { ReactNode } from 'react';
10
+ import { ExtensionProvider } from '@djangocfg/ext-base/hooks';
11
+ import { extensionConfig } from '../config';
12
+ import { SupportProvider } from './SupportContext';
13
+
14
+ interface SupportExtensionProviderProps {
15
+ children: ReactNode;
16
+ }
17
+
18
+ /**
19
+ * Main provider for Support extension
20
+ *
21
+ * Provides support context for ticket management
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <SupportExtensionProvider>
26
+ * <YourApp />
27
+ * </SupportExtensionProvider>
28
+ * ```
29
+ */
30
+ export function SupportExtensionProvider({ children }: SupportExtensionProviderProps) {
31
+ return (
32
+ <ExtensionProvider metadata={extensionConfig}>
33
+ <SupportProvider>
34
+ {children}
35
+ </SupportProvider>
36
+ </ExtensionProvider>
37
+ );
38
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Support Types
3
+ * Centralized type definitions for support context
4
+ */
5
+
6
+ import type {
7
+ useSupportTicketsRetrieve,
8
+ useUpdateSupportTicketsUpdate,
9
+ usePartialUpdateSupportTicketsPartialUpdate,
10
+ useSupportTicketsMessagesRetrieve,
11
+ useUpdateSupportTicketsMessagesUpdate,
12
+ useCreateSupportTicketsMessagesCreate,
13
+ usePartialUpdateSupportTicketsMessagesPartialUpdate,
14
+ } from '../api/generated/ext_support/_utils/hooks';
15
+
16
+ // ─────────────────────────────────────────────────────────────────────────
17
+ // Support Types
18
+ // ─────────────────────────────────────────────────────────────────────────
19
+
20
+ export type Ticket = NonNullable<Awaited<ReturnType<typeof useSupportTicketsRetrieve>>['data']>;
21
+ export type TicketRequest = Parameters<ReturnType<typeof useUpdateSupportTicketsUpdate>>[1];
22
+ export type PatchedTicketRequest = Parameters<ReturnType<typeof usePartialUpdateSupportTicketsPartialUpdate>>[1];
23
+ export type Message = NonNullable<Awaited<ReturnType<typeof useSupportTicketsMessagesRetrieve>>['data']>;
24
+ export type MessageRequest = Parameters<ReturnType<typeof useUpdateSupportTicketsMessagesUpdate>>[2];
25
+ export type MessageCreateRequest = Parameters<ReturnType<typeof useCreateSupportTicketsMessagesCreate>>[1];
26
+ export type PatchedMessageRequest = Parameters<ReturnType<typeof usePartialUpdateSupportTicketsMessagesPartialUpdate>>[2];
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * @djangocfg/ext-support/hooks
5
+ *
6
+ * React hooks for support extension.
7
+ * Use this entry point in client components.
8
+ */
9
+
10
+ // Re-export everything from main entry (server-safe code)
11
+ export * from '../index';
12
+
13
+ // SWR hooks from generated API
14
+ export * from '../api/generated/ext_support/_utils/hooks';
15
+
16
+ // Context and providers
17
+ export {
18
+ SupportProvider,
19
+ useSupportContext,
20
+ } from '../contexts/SupportContext';
21
+
22
+ // Layout context hooks
23
+ export {
24
+ SupportLayoutProvider,
25
+ useSupportLayoutContext,
26
+ } from '../layouts/SupportLayout/context';
27
+
28
+ // Layout components (they use hooks internally)
29
+ export * from '../layouts/SupportLayout/components';
30
+
31
+ // Custom hooks
32
+ export { useInfiniteMessages } from '../layouts/SupportLayout/hooks/useInfiniteMessages';
33
+ export { useInfiniteTickets } from '../layouts/SupportLayout/hooks/useInfiniteTickets';