@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
package/src/index.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @djangocfg/ext-support
3
+ *
4
+ * Support ticket system extension.
5
+ * This entry point is server-safe (no SWR/React hooks).
6
+ *
7
+ * For React hooks, import from '@djangocfg/ext-support/hooks'
8
+ */
9
+
10
+ // API client (server-safe)
11
+ export * from './api/generated/ext_support';
12
+ export { API } from './api/generated/ext_support';
13
+ export { apiSupport } from './api';
14
+
15
+ // Layout components
16
+ export { SupportLayout } from './layouts/SupportLayout';
17
+ export type { SupportLayoutProps } from './layouts/SupportLayout/SupportLayout';
18
+
19
+ // Events
20
+ export * from './layouts/SupportLayout/events';
21
+
22
+ // Types
23
+ export type { SupportUIState, TicketFormData } from './layouts/SupportLayout/types';
24
+
25
+ // Re-export API types for convenience
26
+ export type {
27
+ Ticket,
28
+ TicketRequest,
29
+ PatchedTicketRequest,
30
+ Message,
31
+ MessageRequest,
32
+ MessageCreateRequest,
33
+ PatchedMessageRequest,
34
+ PaginatedTicketList,
35
+ PaginatedMessageList,
36
+ } from './api/generated/ext_support/_utils/schemas';
37
+
38
+ // Note: Hooks are NOT exported here to keep this bundle server-safe
39
+ // Use: import { useSupportTicketsList } from '@djangocfg/ext-support/hooks'
@@ -0,0 +1,91 @@
1
+ # Support Layout
2
+
3
+ Modern support ticket system layout with resizable panels and mobile-optimized interface.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Desktop**: Resizable split-panel view (ticket list | conversation)
8
+ - ✅ **Mobile**: Single-column navigation with back/forward flow
9
+ - ✅ **Real-time**: Auto-refresh messages after sending
10
+ - ✅ **Event-driven**: Dialog management via custom events
11
+ - ✅ **Type-safe**: Full TypeScript support with generated API types
12
+ - ✅ **Smart UI**: Unread counters, status badges, relative timestamps
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ SupportLayout
18
+ ├── SupportLayoutProvider (UI state + events wrapper)
19
+ │ └── SupportProvider (API context from @djangocfg/api)
20
+ │ └── AccountsProvider (for user.id in ticket creation)
21
+ └── Components
22
+ ├── TicketList (scrollable ticket cards)
23
+ ├── MessageList (conversation bubbles)
24
+ ├── MessageInput (with keyboard shortcuts)
25
+ └── CreateTicketDialog (event-driven)
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```tsx
31
+ import { SupportLayout } from '@djangocfg/layouts';
32
+
33
+ export default function SupportPage() {
34
+ return <SupportLayout />;
35
+ }
36
+ ```
37
+
38
+ ## Event-based Dialog Opening
39
+
40
+ ```tsx
41
+ import { openCreateTicketDialog } from '@djangocfg/layouts';
42
+
43
+ function MyComponent() {
44
+ return (
45
+ <Button onClick={openCreateTicketDialog}>
46
+ Create Ticket
47
+ </Button>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ## API Integration
53
+
54
+ Uses generated SWR hooks from `@djangocfg/api/cfg/contexts`:
55
+ - `useSupportContext()` - Tickets CRUD, messages CRUD
56
+ - Automatic cache revalidation after mutations
57
+ - Type-safe request/response handling
58
+
59
+ ## Mobile Optimization
60
+
61
+ - Auto-detects screen width (≤768px)
62
+ - Single-column navigation when mobile
63
+ - Back button to return to ticket list
64
+ - Optimized touch targets
65
+
66
+ ## Key Components
67
+
68
+ ### TicketCard
69
+ - Status badges with color-coding
70
+ - Unread message counters
71
+ - Relative timestamps
72
+ - Click to select
73
+
74
+ ### MessageList
75
+ - Auto-scroll to latest message
76
+ - User vs. Admin message styling
77
+ - Avatar placeholders
78
+ - Timestamp formatting
79
+
80
+ ### MessageInput
81
+ - Multi-line support (Shift+Enter)
82
+ - Submit on Enter
83
+ - Disabled when ticket closed
84
+ - Loading states
85
+
86
+ ### CreateTicketDialog
87
+ - Subject + initial message
88
+ - Zod validation
89
+ - Auto-selects created ticket
90
+ - Toast notifications
91
+
@@ -0,0 +1,179 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Support Layout
4
+ * Modern support layout with resizable panels for desktop and mobile-optimized view
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import { SupportProvider } from '../../contexts/SupportContext';
11
+ import { SupportLayoutProvider, useSupportLayoutContext } from './context';
12
+ import {
13
+ TicketList,
14
+ MessageList,
15
+ MessageInput,
16
+ CreateTicketDialog,
17
+ } from './components';
18
+ import {
19
+ Button,
20
+ ResizablePanelGroup,
21
+ ResizablePanel,
22
+ ResizableHandle,
23
+ } from '@djangocfg/ui-nextjs';
24
+ import { Plus, LifeBuoy, ArrowLeft } from 'lucide-react';
25
+
26
+ // ─────────────────────────────────────────────────────────────────────────
27
+ // Support Layout Content (with context)
28
+ // ─────────────────────────────────────────────────────────────────────────
29
+
30
+ const SupportLayoutContent: React.FC = () => {
31
+ const { selectedTicket, selectTicket, openCreateDialog, getUnreadCount } =
32
+ useSupportLayoutContext();
33
+ const [isMobile, setIsMobile] = React.useState(false);
34
+
35
+ React.useEffect(() => {
36
+ const checkMobile = () => setIsMobile(window.innerWidth <= 768);
37
+ checkMobile();
38
+ window.addEventListener('resize', checkMobile);
39
+ return () => window.removeEventListener('resize', checkMobile);
40
+ }, []);
41
+
42
+ const unreadCount = getUnreadCount();
43
+
44
+ if (isMobile) {
45
+ // Mobile layout - single column with navigation
46
+ return (
47
+ <div className="h-screen flex flex-col overflow-hidden">
48
+ {/* Mobile Header */}
49
+ <div className="flex items-center justify-between p-4 border-b bg-background flex-shrink-0">
50
+ <div className="flex items-center gap-2">
51
+ {selectedTicket ? (
52
+ <Button
53
+ variant="ghost"
54
+ size="sm"
55
+ onClick={() => selectTicket(null)}
56
+ className="p-1"
57
+ >
58
+ <ArrowLeft className="h-5 w-5" />
59
+ </Button>
60
+ ) : (
61
+ <LifeBuoy className="h-6 w-6 text-primary" />
62
+ )}
63
+ <h1 className="text-xl font-semibold">
64
+ {selectedTicket ? selectedTicket.subject : 'Support'}
65
+ </h1>
66
+ {unreadCount > 0 && !selectedTicket && (
67
+ <div className="h-5 w-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
68
+ {unreadCount}
69
+ </div>
70
+ )}
71
+ </div>
72
+ {!selectedTicket && (
73
+ <Button onClick={openCreateDialog} size="sm">
74
+ <Plus className="h-4 w-4 mr-2" />
75
+ New Ticket
76
+ </Button>
77
+ )}
78
+ </div>
79
+
80
+ {/* Mobile Content */}
81
+ <div className="flex-1 min-h-0 overflow-hidden">
82
+ {selectedTicket ? (
83
+ // Show messages when ticket is selected
84
+ <div className="h-full flex flex-col">
85
+ <div className="flex-1 min-h-0 overflow-hidden">
86
+ <MessageList />
87
+ </div>
88
+ <div className="flex-shrink-0">
89
+ <MessageInput />
90
+ </div>
91
+ </div>
92
+ ) : (
93
+ // Show ticket list when no ticket is selected
94
+ <TicketList />
95
+ )}
96
+ </div>
97
+
98
+ {/* Dialog */}
99
+ <CreateTicketDialog />
100
+ </div>
101
+ );
102
+ }
103
+
104
+ // Desktop layout - resizable panels
105
+ return (
106
+ <div className="h-screen flex flex-col overflow-hidden">
107
+ {/* Desktop Header */}
108
+ <div className="flex items-center justify-between p-6 border-b bg-background flex-shrink-0">
109
+ <div className="flex items-center gap-3">
110
+ <LifeBuoy className="h-7 w-7 text-primary" />
111
+ <div>
112
+ <h1 className="text-2xl font-bold">Support Center</h1>
113
+ <p className="text-sm text-muted-foreground">Get help from our support team</p>
114
+ </div>
115
+ {unreadCount > 0 && (
116
+ <div className="h-6 w-6 bg-red-500 text-white text-sm rounded-full flex items-center justify-center">
117
+ {unreadCount}
118
+ </div>
119
+ )}
120
+ </div>
121
+
122
+ <Button onClick={openCreateDialog}>
123
+ <Plus className="h-4 w-4 mr-2" />
124
+ New Ticket
125
+ </Button>
126
+ </div>
127
+
128
+ {/* Desktop Content */}
129
+ <div className="flex-1 min-h-0 overflow-hidden">
130
+ <ResizablePanelGroup direction="horizontal" className="h-full">
131
+ {/* Ticket List Panel */}
132
+ <ResizablePanel defaultSize={35} minSize={25} maxSize={50}>
133
+ <div className="h-full border-r overflow-hidden">
134
+ <TicketList />
135
+ </div>
136
+ </ResizablePanel>
137
+
138
+ <ResizableHandle withHandle className="hover:bg-accent transition-colors" />
139
+
140
+ {/* Messages Panel */}
141
+ <ResizablePanel defaultSize={65} minSize={50}>
142
+ <div className="h-full flex flex-col overflow-hidden">
143
+ <div className="flex-1 min-h-0 overflow-hidden">
144
+ <MessageList />
145
+ </div>
146
+ <div className="flex-shrink-0">
147
+ <MessageInput />
148
+ </div>
149
+ </div>
150
+ </ResizablePanel>
151
+ </ResizablePanelGroup>
152
+ </div>
153
+
154
+ {/* Dialog */}
155
+ <CreateTicketDialog />
156
+ </div>
157
+ );
158
+ };
159
+
160
+ // ─────────────────────────────────────────────────────────────────────────
161
+ // Support Layout (with providers)
162
+ // ─────────────────────────────────────────────────────────────────────────
163
+
164
+ export interface SupportLayoutProps {
165
+ children?: React.ReactNode;
166
+ }
167
+
168
+ export const SupportLayout: React.FC<SupportLayoutProps> = () => {
169
+ return (
170
+ <div className="h-screen w-full overflow-hidden">
171
+ <SupportProvider>
172
+ <SupportLayoutProvider>
173
+ <SupportLayoutContent />
174
+ </SupportLayoutProvider>
175
+ </SupportProvider>
176
+ </div>
177
+ );
178
+ };
179
+
@@ -0,0 +1,155 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Create Ticket Dialog
4
+ * Dialog for creating new support tickets
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ DialogDescription,
16
+ Button,
17
+ Form,
18
+ FormField,
19
+ FormItem,
20
+ FormLabel,
21
+ FormControl,
22
+ FormMessage,
23
+ Input,
24
+ Textarea,
25
+ } from '@djangocfg/ui-nextjs';
26
+ import { useForm } from 'react-hook-form';
27
+ import { zodResolver } from '@hookform/resolvers/zod';
28
+ import { z } from 'zod';
29
+ import { Plus, Loader2 } from 'lucide-react';
30
+ import { supportLogger } from '../../../utils/logger';
31
+ import { useSupportLayoutContext } from '../context';
32
+ import { useToast } from '@djangocfg/ui-nextjs';
33
+ import type { TicketFormData } from '../types';
34
+
35
+ const createTicketSchema = z.object({
36
+ subject: z.string().min(1, 'Subject is required').max(200, 'Subject too long'),
37
+ message: z.string().min(1, 'Message is required').max(5000, 'Message too long'),
38
+ });
39
+
40
+ export const CreateTicketDialog: React.FC = () => {
41
+ const { uiState, createTicket, closeCreateDialog } = useSupportLayoutContext();
42
+ const { toast } = useToast();
43
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
44
+
45
+ const form = useForm<TicketFormData>({
46
+ resolver: zodResolver(createTicketSchema),
47
+ defaultValues: {
48
+ subject: '',
49
+ message: '',
50
+ },
51
+ });
52
+
53
+ const onSubmit = async (data: TicketFormData) => {
54
+ setIsSubmitting(true);
55
+ try {
56
+ await createTicket(data);
57
+ form.reset();
58
+ toast({
59
+ title: 'Success',
60
+ description: 'Support ticket created successfully',
61
+ });
62
+ } catch (error) {
63
+ supportLogger.error('Failed to create ticket:', error);
64
+ toast({
65
+ title: 'Error',
66
+ description: 'Failed to create ticket. Please try again.',
67
+ variant: 'destructive',
68
+ });
69
+ } finally {
70
+ setIsSubmitting(false);
71
+ }
72
+ };
73
+
74
+ const handleClose = () => {
75
+ form.reset();
76
+ closeCreateDialog();
77
+ };
78
+
79
+ return (
80
+ <Dialog open={uiState.isCreateDialogOpen} onOpenChange={(open) => !open && handleClose()}>
81
+ <DialogContent className="sm:max-w-[600px] animate-in fade-in slide-in-from-bottom-4 duration-300">
82
+ <DialogHeader>
83
+ <DialogTitle className="flex items-center gap-2">
84
+ <Plus className="h-5 w-5" />
85
+ Create Support Ticket
86
+ </DialogTitle>
87
+ <DialogDescription>
88
+ Describe your issue and we'll help you resolve it as quickly as possible.
89
+ </DialogDescription>
90
+ </DialogHeader>
91
+
92
+ <Form {...form}>
93
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
94
+ <FormField
95
+ control={form.control}
96
+ name="subject"
97
+ render={({ field }) => (
98
+ <FormItem>
99
+ <FormLabel>Subject</FormLabel>
100
+ <FormControl>
101
+ <Input placeholder="Brief description of your issue..." {...field} />
102
+ </FormControl>
103
+ <FormMessage />
104
+ </FormItem>
105
+ )}
106
+ />
107
+
108
+ <FormField
109
+ control={form.control}
110
+ name="message"
111
+ render={({ field }) => (
112
+ <FormItem>
113
+ <FormLabel>Message</FormLabel>
114
+ <FormControl>
115
+ <Textarea
116
+ placeholder="Describe your issue in detail. Include any error messages, steps to reproduce, or relevant information..."
117
+ className="min-h-[120px]"
118
+ {...field}
119
+ />
120
+ </FormControl>
121
+ <FormMessage />
122
+ </FormItem>
123
+ )}
124
+ />
125
+
126
+ <div className="flex justify-end gap-3 pt-4">
127
+ <Button
128
+ type="button"
129
+ variant="outline"
130
+ onClick={handleClose}
131
+ disabled={isSubmitting}
132
+ >
133
+ Cancel
134
+ </Button>
135
+ <Button type="submit" disabled={isSubmitting}>
136
+ {isSubmitting ? (
137
+ <>
138
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
139
+ Creating...
140
+ </>
141
+ ) : (
142
+ <>
143
+ <Plus className="h-4 w-4 mr-2" />
144
+ Create Ticket
145
+ </>
146
+ )}
147
+ </Button>
148
+ </div>
149
+ </form>
150
+ </Form>
151
+ </DialogContent>
152
+ </Dialog>
153
+ );
154
+ };
155
+
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Message Input Component
3
+ * Input field for sending messages
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React, { useState } from 'react';
9
+ import { Button, Textarea, useToast } from '@djangocfg/ui-nextjs';
10
+ import { Send } from 'lucide-react';
11
+ import { supportLogger } from '../../../utils/logger';
12
+ import { useSupportLayoutContext } from '../context';
13
+
14
+ export const MessageInput: React.FC = () => {
15
+ const { selectedTicket, sendMessage } = useSupportLayoutContext();
16
+ const { toast } = useToast();
17
+ const [message, setMessage] = useState('');
18
+ const [isSending, setIsSending] = useState(false);
19
+
20
+ const handleSubmit = async (e: React.FormEvent) => {
21
+ e.preventDefault();
22
+
23
+ if (!message.trim() || !selectedTicket) return;
24
+
25
+ setIsSending(true);
26
+ try {
27
+ await sendMessage(message.trim());
28
+ setMessage('');
29
+ toast({
30
+ title: 'Success',
31
+ description: 'Message sent successfully',
32
+ });
33
+ } catch (error) {
34
+ supportLogger.error('Failed to send message:', error);
35
+ toast({
36
+ title: 'Error',
37
+ description: 'Failed to send message',
38
+ variant: 'destructive',
39
+ });
40
+ } finally {
41
+ setIsSending(false);
42
+ }
43
+ };
44
+
45
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
46
+ if (e.key === 'Enter' && !e.shiftKey) {
47
+ e.preventDefault();
48
+ handleSubmit(e);
49
+ }
50
+ };
51
+
52
+ if (!selectedTicket) {
53
+ return null;
54
+ }
55
+
56
+ const canSendMessage = selectedTicket.status !== 'closed';
57
+
58
+ return (
59
+ <form onSubmit={handleSubmit} className="p-4 border-t bg-background/50 backdrop-blur-sm flex-shrink-0">
60
+ <div className="flex gap-2">
61
+ <Textarea
62
+ value={message}
63
+ onChange={(e) => setMessage(e.target.value)}
64
+ onKeyDown={handleKeyDown}
65
+ placeholder={
66
+ canSendMessage
67
+ ? 'Type your message... (Shift+Enter for new line)'
68
+ : 'This ticket is closed'
69
+ }
70
+ className="min-h-[60px] max-h-[200px] transition-all duration-200
71
+ focus:ring-2 focus:ring-primary/20"
72
+ disabled={!canSendMessage || isSending}
73
+ />
74
+ <Button
75
+ type="submit"
76
+ size="icon"
77
+ disabled={!message.trim() || !canSendMessage || isSending}
78
+ className="shrink-0 transition-all duration-200
79
+ hover:scale-110 active:scale-95 disabled:scale-100"
80
+ >
81
+ <Send className="h-4 w-4" />
82
+ </Button>
83
+ </div>
84
+ {!canSendMessage && (
85
+ <p className="text-xs text-muted-foreground mt-2 animate-in fade-in slide-in-from-top-1 duration-200">
86
+ This ticket is closed. You cannot send new messages.
87
+ </p>
88
+ )}
89
+ </form>
90
+ );
91
+ };
92
+