@djangocfg/layouts 2.1.10 → 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.
- package/README.md +53 -161
- package/package.json +6 -6
- package/src/components/RedirectPage/RedirectPage.tsx +1 -1
- package/src/index.ts +0 -6
- package/src/layouts/AppLayout/AppLayout.tsx +1 -1
- package/src/layouts/AppLayout/BaseApp.tsx +1 -1
- package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
- package/src/layouts/_components/UserMenu.tsx +1 -1
- package/src/layouts/index.ts +0 -2
- package/src/snippets/Analytics/useAnalytics.ts +1 -1
- package/src/snippets/index.ts +0 -3
- package/src/auth/README.md +0 -962
- package/src/auth/context/AccountsContext.tsx +0 -240
- package/src/auth/context/AuthContext.tsx +0 -604
- package/src/auth/context/index.ts +0 -4
- package/src/auth/context/types.ts +0 -68
- package/src/auth/hooks/index.ts +0 -17
- package/src/auth/hooks/useAuthForm.ts +0 -332
- package/src/auth/hooks/useAuthGuard.ts +0 -25
- package/src/auth/hooks/useAuthRedirect.ts +0 -51
- package/src/auth/hooks/useAutoAuth.ts +0 -49
- package/src/auth/hooks/useGithubAuth.ts +0 -184
- package/src/auth/hooks/useLocalStorage.ts +0 -214
- package/src/auth/hooks/useProfileCache.ts +0 -146
- package/src/auth/hooks/useSessionStorage.ts +0 -189
- package/src/auth/index.ts +0 -10
- package/src/auth/middlewares/index.ts +0 -1
- package/src/auth/middlewares/proxy.ts +0 -32
- package/src/auth/server.ts +0 -6
- package/src/auth/utils/errors.ts +0 -34
- package/src/auth/utils/index.ts +0 -2
- package/src/auth/utils/validation.ts +0 -14
- package/src/contexts/LeadsContext.tsx +0 -156
- package/src/contexts/NewsletterContext.tsx +0 -263
- package/src/contexts/SupportContext.tsx +0 -256
- package/src/contexts/index.ts +0 -59
- package/src/contexts/knowbase/ChatContext.tsx +0 -174
- package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
- package/src/contexts/knowbase/SessionsContext.tsx +0 -174
- package/src/contexts/knowbase/index.ts +0 -61
- package/src/contexts/payments/BalancesContext.tsx +0 -65
- package/src/contexts/payments/CurrenciesContext.tsx +0 -66
- package/src/contexts/payments/OverviewContext.tsx +0 -174
- package/src/contexts/payments/PaymentsContext.tsx +0 -132
- package/src/contexts/payments/README.md +0 -201
- package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
- package/src/contexts/payments/index.ts +0 -50
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
- package/src/layouts/PaymentsLayout/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/events.ts +0 -47
- package/src/layouts/PaymentsLayout/index.ts +0 -16
- package/src/layouts/PaymentsLayout/types.ts +0 -6
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
- package/src/layouts/SupportLayout/README.md +0 -91
- package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
- package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
- package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
- package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
- package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
- package/src/layouts/SupportLayout/components/index.ts +0 -6
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
- package/src/layouts/SupportLayout/context/index.ts +0 -2
- package/src/layouts/SupportLayout/events.ts +0 -33
- package/src/layouts/SupportLayout/hooks/index.ts +0 -2
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
- package/src/layouts/SupportLayout/index.ts +0 -8
- package/src/layouts/SupportLayout/types.ts +0 -21
- package/src/snippets/Chat/ChatUIContext.tsx +0 -110
- package/src/snippets/Chat/ChatWidget.tsx +0 -476
- package/src/snippets/Chat/README.md +0 -122
- package/src/snippets/Chat/components/MessageInput.tsx +0 -124
- package/src/snippets/Chat/components/MessageList.tsx +0 -169
- package/src/snippets/Chat/components/SessionList.tsx +0 -192
- package/src/snippets/Chat/components/index.ts +0 -9
- package/src/snippets/Chat/hooks/index.ts +0 -6
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
- package/src/snippets/Chat/index.tsx +0 -45
- package/src/snippets/Chat/types.ts +0 -80
- package/src/snippets/ContactForm/ContactForm.tsx +0 -346
- package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
- package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
- package/src/snippets/ContactForm/ContactPage.tsx +0 -131
- package/src/snippets/ContactForm/dynamic.tsx +0 -55
- package/src/snippets/ContactForm/index.ts +0 -34
- package/src/snippets/ContactForm/types.ts +0 -110
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transactions List Component (v2.0 - Simplified)
|
|
3
|
-
* Display transaction history with balance changes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useState } from 'react';
|
|
9
|
-
import {
|
|
10
|
-
Card,
|
|
11
|
-
CardContent,
|
|
12
|
-
CardHeader,
|
|
13
|
-
CardTitle,
|
|
14
|
-
Table,
|
|
15
|
-
TableBody,
|
|
16
|
-
TableCell,
|
|
17
|
-
TableHead,
|
|
18
|
-
TableHeader,
|
|
19
|
-
TableRow,
|
|
20
|
-
Button,
|
|
21
|
-
Badge,
|
|
22
|
-
Input,
|
|
23
|
-
Select,
|
|
24
|
-
SelectContent,
|
|
25
|
-
SelectItem,
|
|
26
|
-
SelectTrigger,
|
|
27
|
-
SelectValue,
|
|
28
|
-
Skeleton,
|
|
29
|
-
} from '@djangocfg/ui-nextjs';
|
|
30
|
-
import { History, Search, Filter, RefreshCw, ArrowUpRight, ArrowDownLeft } from 'lucide-react';
|
|
31
|
-
import { useOverviewContext } from '@djangocfg/layouts/contexts';
|
|
32
|
-
|
|
33
|
-
export const TransactionsList: React.FC = () => {
|
|
34
|
-
const {
|
|
35
|
-
transactions,
|
|
36
|
-
isLoadingTransactions,
|
|
37
|
-
refreshTransactions,
|
|
38
|
-
} = useOverviewContext();
|
|
39
|
-
|
|
40
|
-
const [searchTerm, setSearchTerm] = useState('');
|
|
41
|
-
const [typeFilter, setTypeFilter] = useState<string>('all');
|
|
42
|
-
|
|
43
|
-
// Extract transactions array from response (handle different possible structures)
|
|
44
|
-
const transactionsList = transactions?.results || transactions?.transactions || [];
|
|
45
|
-
|
|
46
|
-
const formatCurrency = (amount?: number | null) => {
|
|
47
|
-
if (amount === null || amount === undefined) return '$0.00';
|
|
48
|
-
return new Intl.NumberFormat('en-US', {
|
|
49
|
-
style: 'currency',
|
|
50
|
-
currency: 'USD',
|
|
51
|
-
minimumFractionDigits: 2,
|
|
52
|
-
}).format(amount);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const formatDate = (date: string | null | undefined): string => {
|
|
56
|
-
if (!date) return 'N/A';
|
|
57
|
-
try {
|
|
58
|
-
return new Date(date).toLocaleString('en-US', {
|
|
59
|
-
year: 'numeric',
|
|
60
|
-
month: 'short',
|
|
61
|
-
day: 'numeric',
|
|
62
|
-
hour: '2-digit',
|
|
63
|
-
minute: '2-digit',
|
|
64
|
-
});
|
|
65
|
-
} catch {
|
|
66
|
-
return 'Invalid date';
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const getRelativeTime = (date: string | null | undefined): string => {
|
|
71
|
-
if (!date) return 'N/A';
|
|
72
|
-
|
|
73
|
-
const now = new Date();
|
|
74
|
-
const target = new Date(date);
|
|
75
|
-
const diffInSeconds = Math.floor((now.getTime() - target.getTime()) / 1000);
|
|
76
|
-
|
|
77
|
-
if (diffInSeconds < 60) return 'Just now';
|
|
78
|
-
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
|
|
79
|
-
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
|
|
80
|
-
return `${Math.floor(diffInSeconds / 86400)}d ago`;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const getTypeVariant = (
|
|
84
|
-
type: string | null | undefined
|
|
85
|
-
): 'default' | 'destructive' | 'outline' | 'secondary' => {
|
|
86
|
-
switch (type?.toLowerCase()) {
|
|
87
|
-
case 'deposit':
|
|
88
|
-
case 'credit':
|
|
89
|
-
return 'default';
|
|
90
|
-
case 'withdrawal':
|
|
91
|
-
case 'debit':
|
|
92
|
-
return 'destructive';
|
|
93
|
-
default:
|
|
94
|
-
return 'outline';
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const getTypeIcon = (type: string | null | undefined) => {
|
|
99
|
-
const isDeposit = type?.toLowerCase() === 'deposit' || type?.toLowerCase() === 'credit';
|
|
100
|
-
return isDeposit ? (
|
|
101
|
-
<ArrowDownLeft className="h-4 w-4 text-green-600" />
|
|
102
|
-
) : (
|
|
103
|
-
<ArrowUpRight className="h-4 w-4 text-red-600" />
|
|
104
|
-
);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const handleSearch = async (value: string) => {
|
|
108
|
-
setSearchTerm(value);
|
|
109
|
-
await refreshTransactions();
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const handleTypeFilter = async (type: string) => {
|
|
113
|
-
setTypeFilter(type);
|
|
114
|
-
await refreshTransactions();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Filter transactions client-side
|
|
118
|
-
const filteredTransactions = transactionsList.filter((transaction: any) => {
|
|
119
|
-
const matchesSearch = searchTerm
|
|
120
|
-
? transaction.id?.toString().toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
121
|
-
transaction.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
122
|
-
transaction.type?.toLowerCase().includes(searchTerm.toLowerCase())
|
|
123
|
-
: true;
|
|
124
|
-
|
|
125
|
-
const matchesType = typeFilter !== 'all'
|
|
126
|
-
? transaction.type?.toLowerCase() === typeFilter.toLowerCase()
|
|
127
|
-
: true;
|
|
128
|
-
|
|
129
|
-
return matchesSearch && matchesType;
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
if (isLoadingTransactions) {
|
|
133
|
-
return (
|
|
134
|
-
<Card>
|
|
135
|
-
<CardHeader>
|
|
136
|
-
<CardTitle className="flex items-center gap-2">
|
|
137
|
-
<History className="h-5 w-5" />
|
|
138
|
-
Transaction History
|
|
139
|
-
</CardTitle>
|
|
140
|
-
</CardHeader>
|
|
141
|
-
<CardContent className="space-y-3">
|
|
142
|
-
{Array.from({ length: 5 }).map((_, i) => (
|
|
143
|
-
<div key={i} className="flex items-center justify-between p-4 border rounded-sm">
|
|
144
|
-
<div className="space-y-2">
|
|
145
|
-
<Skeleton className="h-4 w-32" />
|
|
146
|
-
<Skeleton className="h-3 w-24" />
|
|
147
|
-
</div>
|
|
148
|
-
<Skeleton className="h-6 w-16" />
|
|
149
|
-
</div>
|
|
150
|
-
))}
|
|
151
|
-
</CardContent>
|
|
152
|
-
</Card>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<Card>
|
|
158
|
-
<CardHeader>
|
|
159
|
-
<CardTitle className="flex items-center justify-between">
|
|
160
|
-
<div className="flex items-center gap-2">
|
|
161
|
-
<History className="h-5 w-5" />
|
|
162
|
-
Transaction History
|
|
163
|
-
</div>
|
|
164
|
-
<Button variant="outline" size="sm" onClick={refreshTransactions} disabled={isLoadingTransactions}>
|
|
165
|
-
<RefreshCw className={`h-4 w-4 mr-2 ${isLoadingTransactions ? 'animate-spin' : ''}`} />
|
|
166
|
-
Refresh
|
|
167
|
-
</Button>
|
|
168
|
-
</CardTitle>
|
|
169
|
-
</CardHeader>
|
|
170
|
-
|
|
171
|
-
<CardContent className="space-y-4">
|
|
172
|
-
{/* Filters */}
|
|
173
|
-
<div className="flex flex-col sm:flex-row gap-4">
|
|
174
|
-
<div className="relative flex-1">
|
|
175
|
-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
176
|
-
<Input
|
|
177
|
-
placeholder="Search by ID, description, or type..."
|
|
178
|
-
value={searchTerm}
|
|
179
|
-
onChange={(e) => handleSearch(e.target.value)}
|
|
180
|
-
className="pl-10"
|
|
181
|
-
/>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
<Select value={typeFilter} onValueChange={handleTypeFilter}>
|
|
185
|
-
<SelectTrigger className="w-full sm:w-48">
|
|
186
|
-
<Filter className="h-4 w-4 mr-2" />
|
|
187
|
-
<SelectValue placeholder="Filter by type" />
|
|
188
|
-
</SelectTrigger>
|
|
189
|
-
<SelectContent>
|
|
190
|
-
<SelectItem value="all">All Types</SelectItem>
|
|
191
|
-
<SelectItem value="deposit">Deposits</SelectItem>
|
|
192
|
-
<SelectItem value="withdrawal">Withdrawals</SelectItem>
|
|
193
|
-
</SelectContent>
|
|
194
|
-
</Select>
|
|
195
|
-
</div>
|
|
196
|
-
|
|
197
|
-
{/* Transactions Table */}
|
|
198
|
-
{filteredTransactions.length === 0 ? (
|
|
199
|
-
<div className="text-center py-12">
|
|
200
|
-
<div className="w-16 h-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center">
|
|
201
|
-
<History className="w-8 h-8 text-muted-foreground" />
|
|
202
|
-
</div>
|
|
203
|
-
<h3 className="text-lg font-semibold mb-2">No Transactions Found</h3>
|
|
204
|
-
<p className="text-muted-foreground">
|
|
205
|
-
{searchTerm || typeFilter !== 'all'
|
|
206
|
-
? 'No transactions match your current filters'
|
|
207
|
-
: "You don't have any transactions yet"}
|
|
208
|
-
</p>
|
|
209
|
-
</div>
|
|
210
|
-
) : (
|
|
211
|
-
<div className="rounded-md border">
|
|
212
|
-
<Table>
|
|
213
|
-
<TableHeader>
|
|
214
|
-
<TableRow>
|
|
215
|
-
<TableHead>Date & Time</TableHead>
|
|
216
|
-
<TableHead>Type</TableHead>
|
|
217
|
-
<TableHead>Amount</TableHead>
|
|
218
|
-
<TableHead>Balance After</TableHead>
|
|
219
|
-
<TableHead>Description</TableHead>
|
|
220
|
-
<TableHead>Reference</TableHead>
|
|
221
|
-
</TableRow>
|
|
222
|
-
</TableHeader>
|
|
223
|
-
<TableBody>
|
|
224
|
-
{filteredTransactions.map((transaction: any, index: number) => {
|
|
225
|
-
const isDeposit = transaction.type?.toLowerCase() === 'deposit' || transaction.type?.toLowerCase() === 'credit';
|
|
226
|
-
return (
|
|
227
|
-
<TableRow key={transaction.id || index}>
|
|
228
|
-
<TableCell>
|
|
229
|
-
<div>
|
|
230
|
-
<div className="font-medium">
|
|
231
|
-
{formatDate(transaction.created_at || transaction.timestamp)}
|
|
232
|
-
</div>
|
|
233
|
-
<div className="text-sm text-muted-foreground">
|
|
234
|
-
{getRelativeTime(transaction.created_at || transaction.timestamp)}
|
|
235
|
-
</div>
|
|
236
|
-
</div>
|
|
237
|
-
</TableCell>
|
|
238
|
-
<TableCell>
|
|
239
|
-
<div className="flex items-center gap-2">
|
|
240
|
-
{getTypeIcon(transaction.type)}
|
|
241
|
-
<Badge variant={getTypeVariant(transaction.type)}>
|
|
242
|
-
{transaction.type || 'Unknown'}
|
|
243
|
-
</Badge>
|
|
244
|
-
</div>
|
|
245
|
-
</TableCell>
|
|
246
|
-
<TableCell className="font-mono font-semibold">
|
|
247
|
-
<span className={isDeposit ? 'text-green-600' : 'text-red-600'}>
|
|
248
|
-
{isDeposit ? '+' : '-'}
|
|
249
|
-
{formatCurrency(Math.abs(transaction.amount || transaction.amount_usd || 0))}
|
|
250
|
-
</span>
|
|
251
|
-
</TableCell>
|
|
252
|
-
<TableCell className="font-mono">
|
|
253
|
-
{formatCurrency(transaction.balance_after || 0)}
|
|
254
|
-
</TableCell>
|
|
255
|
-
<TableCell className="text-sm">
|
|
256
|
-
{transaction.description || transaction.note || 'No description'}
|
|
257
|
-
</TableCell>
|
|
258
|
-
<TableCell className="font-mono text-sm text-muted-foreground">
|
|
259
|
-
{transaction.reference || transaction.payment_id
|
|
260
|
-
? `${(transaction.reference || transaction.payment_id).toString().slice(0, 8)}...`
|
|
261
|
-
: 'N/A'}
|
|
262
|
-
</TableCell>
|
|
263
|
-
</TableRow>
|
|
264
|
-
);
|
|
265
|
-
})}
|
|
266
|
-
</TableBody>
|
|
267
|
-
</Table>
|
|
268
|
-
</div>
|
|
269
|
-
)}
|
|
270
|
-
</CardContent>
|
|
271
|
-
</Card>
|
|
272
|
-
);
|
|
273
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { TransactionsList } from './TransactionsList';
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transactions View (v2.0 - Simplified)
|
|
3
|
-
* View transaction history and balance changes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { TransactionsList } from './components';
|
|
10
|
-
|
|
11
|
-
export const TransactionsView: React.FC = () => {
|
|
12
|
-
return (
|
|
13
|
-
<div className="space-y-6">
|
|
14
|
-
<TransactionsList />
|
|
15
|
-
</div>
|
|
16
|
-
);
|
|
17
|
-
};
|
|
@@ -1,91 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,179 +0,0 @@
|
|
|
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 '@djangocfg/layouts/contexts';
|
|
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
|
-
|