@djangocfg/layouts 1.0.4 → 1.0.5

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 (28) hide show
  1. package/package.json +5 -5
  2. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +47 -65
  3. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +118 -163
  4. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +31 -45
  5. package/src/layouts/PaymentsLayout/components/index.ts +1 -4
  6. package/src/layouts/PaymentsLayout/events.ts +23 -84
  7. package/src/layouts/PaymentsLayout/index.ts +7 -11
  8. package/src/layouts/PaymentsLayout/types.ts +3 -16
  9. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +45 -16
  10. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +16 -12
  11. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  12. package/src/layouts/PaymentsLayout/views/overview/index.tsx +3 -6
  13. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +50 -30
  14. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  15. package/src/layouts/PaymentsLayout/views/payments/index.tsx +1 -2
  16. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +273 -0
  17. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +1 -0
  18. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +5 -17
  19. package/src/layouts/PaymentsLayout/README.md +0 -133
  20. package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +0 -172
  21. package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +0 -100
  22. package/src/layouts/PaymentsLayout/context/RootPaymentsContext.tsx +0 -129
  23. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +0 -109
  24. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +0 -194
  25. package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +0 -3
  26. package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +0 -19
  27. package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +0 -103
  28. package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +0 -29
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Payments List Component
2
+ * Payments List Component (v2.0 - Simplified)
3
3
  * Display paginated list of payments with filters
4
4
  */
5
5
 
@@ -29,8 +29,7 @@ import {
29
29
  } from '@djangocfg/ui';
30
30
  import { Plus, Search, Filter, ChevronLeft, ChevronRight, RefreshCw, ExternalLink } from 'lucide-react';
31
31
  import { usePaymentsContext } from '@djangocfg/api/cfg/contexts';
32
- import { openCreatePaymentDialog } from '../../../events';
33
- import { openPaymentDetails } from '../../../components/PaymentDetailsDialog';
32
+ import { openCreatePaymentDialog, openPaymentDetailsDialog } from '../../../events';
34
33
 
35
34
  export const PaymentsList: React.FC = () => {
36
35
  const {
@@ -38,22 +37,23 @@ export const PaymentsList: React.FC = () => {
38
37
  isLoadingPayments,
39
38
  refreshPayments,
40
39
  } = usePaymentsContext();
41
-
40
+
42
41
  const paymentsList = payments?.results || [];
43
- const paymentsPage = payments?.page || 1;
44
- const paymentsPageSize = payments?.page_size || 20;
45
- const paymentsTotalCount = payments?.count || 0;
42
+ const currentPage = payments?.page || 1;
43
+ const pageSize = payments?.page_size || 20;
44
+ const totalCount = payments?.count || 0;
46
45
 
47
46
  const [searchTerm, setSearchTerm] = useState('');
48
47
  const [statusFilter, setStatusFilter] = useState<string>('all');
49
48
 
50
- const formatCurrency = (amount?: number | null) => {
49
+ const formatCurrency = (amount?: number | string | null) => {
51
50
  if (amount === null || amount === undefined) return '$0.00';
51
+ const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
52
52
  return new Intl.NumberFormat('en-US', {
53
53
  style: 'currency',
54
54
  currency: 'USD',
55
55
  minimumFractionDigits: 2,
56
- }).format(amount);
56
+ }).format(numAmount);
57
57
  };
58
58
 
59
59
  const getRelativeTime = (date: string | null | undefined): string => {
@@ -77,9 +77,11 @@ export const PaymentsList: React.FC = () => {
77
77
  case 'success':
78
78
  return 'default';
79
79
  case 'pending':
80
+ case 'confirming':
80
81
  return 'secondary';
81
82
  case 'failed':
82
83
  case 'error':
84
+ case 'expired':
83
85
  return 'destructive';
84
86
  default:
85
87
  return 'outline';
@@ -88,13 +90,13 @@ export const PaymentsList: React.FC = () => {
88
90
 
89
91
  const handleSearch = async (value: string) => {
90
92
  setSearchTerm(value);
91
- // TODO: Implement search/filter in PaymentsContext
93
+ // TODO: Implement search/filter in PaymentsContext when API supports it
92
94
  await refreshPayments();
93
95
  };
94
96
 
95
97
  const handleStatusFilter = async (status: string) => {
96
98
  setStatusFilter(status);
97
- // TODO: Implement search/filter in PaymentsContext
99
+ // TODO: Implement status filter in PaymentsContext when API supports it
98
100
  await refreshPayments();
99
101
  };
100
102
 
@@ -103,9 +105,24 @@ export const PaymentsList: React.FC = () => {
103
105
  await refreshPayments();
104
106
  };
105
107
 
106
- const totalPages = Math.ceil((paymentsTotalCount || 0) / (paymentsPageSize || 20));
107
- const showingFrom = ((paymentsPage || 1) - 1) * (paymentsPageSize || 20) + 1;
108
- const showingTo = Math.min((paymentsPage || 1) * (paymentsPageSize || 20), paymentsTotalCount || 0);
108
+ // Filter payments client-side for now (until API supports filtering)
109
+ const filteredPayments = paymentsList.filter((payment) => {
110
+ const matchesSearch = searchTerm
111
+ ? payment.id?.toLowerCase().includes(searchTerm.toLowerCase()) ||
112
+ payment.status?.toLowerCase().includes(searchTerm.toLowerCase()) ||
113
+ payment.currency_code?.toLowerCase().includes(searchTerm.toLowerCase())
114
+ : true;
115
+
116
+ const matchesStatus = statusFilter !== 'all'
117
+ ? payment.status?.toLowerCase() === statusFilter.toLowerCase()
118
+ : true;
119
+
120
+ return matchesSearch && matchesStatus;
121
+ });
122
+
123
+ const totalPages = Math.ceil((totalCount || 0) / (pageSize || 20));
124
+ const showingFrom = ((currentPage || 1) - 1) * (pageSize || 20) + 1;
125
+ const showingTo = Math.min((currentPage || 1) * (pageSize || 20), totalCount || 0);
109
126
 
110
127
  return (
111
128
  <Card>
@@ -131,7 +148,7 @@ export const PaymentsList: React.FC = () => {
131
148
  <div className="relative flex-1">
132
149
  <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
133
150
  <Input
134
- placeholder="Search payments..."
151
+ placeholder="Search by ID, status, or currency..."
135
152
  value={searchTerm}
136
153
  onChange={(e) => handleSearch(e.target.value)}
137
154
  className="pl-10"
@@ -147,7 +164,9 @@ export const PaymentsList: React.FC = () => {
147
164
  <SelectItem value="all">All Statuses</SelectItem>
148
165
  <SelectItem value="completed">Completed</SelectItem>
149
166
  <SelectItem value="pending">Pending</SelectItem>
167
+ <SelectItem value="confirming">Confirming</SelectItem>
150
168
  <SelectItem value="failed">Failed</SelectItem>
169
+ <SelectItem value="expired">Expired</SelectItem>
151
170
  </SelectContent>
152
171
  </Select>
153
172
  </div>
@@ -165,7 +184,7 @@ export const PaymentsList: React.FC = () => {
165
184
  </div>
166
185
  ))}
167
186
  </div>
168
- ) : !paymentsList || paymentsList.length === 0 ? (
187
+ ) : filteredPayments.length === 0 ? (
169
188
  <div className="text-center py-12">
170
189
  <div className="w-16 h-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center">
171
190
  <Search className="w-8 h-8 text-muted-foreground" />
@@ -192,16 +211,16 @@ export const PaymentsList: React.FC = () => {
192
211
  <TableHead>Currency</TableHead>
193
212
  <TableHead>Status</TableHead>
194
213
  <TableHead>Provider</TableHead>
195
- <TableHead>ID</TableHead>
214
+ <TableHead>Payment ID</TableHead>
196
215
  <TableHead className="text-right">Actions</TableHead>
197
216
  </TableRow>
198
217
  </TableHeader>
199
218
  <TableBody>
200
- {paymentsList.map((payment) => (
219
+ {filteredPayments.map((payment) => (
201
220
  <TableRow
202
221
  key={payment.id}
203
222
  className="cursor-pointer hover:bg-accent"
204
- onClick={() => openPaymentDetails(payment.id)}
223
+ onClick={() => openPaymentDetailsDialog(String(payment.id))}
205
224
  >
206
225
  <TableCell>
207
226
  <div>
@@ -219,14 +238,16 @@ export const PaymentsList: React.FC = () => {
219
238
  {formatCurrency(payment.amount_usd)}
220
239
  </TableCell>
221
240
  <TableCell>
222
- <Badge variant="outline">Currency #{payment.currency}</Badge>
241
+ <Badge variant="outline">{payment.currency_code || 'USD'}</Badge>
223
242
  </TableCell>
224
243
  <TableCell>
225
244
  <Badge variant={getStatusVariant(payment.status)}>{payment.status}</Badge>
226
245
  </TableCell>
227
- <TableCell>{payment.provider || 'N/A'}</TableCell>
246
+ <TableCell className="text-sm text-muted-foreground">
247
+ NowPayments
248
+ </TableCell>
228
249
  <TableCell className="font-mono text-sm text-muted-foreground">
229
- {payment.id.slice(0, 8)}...
250
+ {payment.id ? `${payment.id.toString().slice(0, 8)}...` : 'N/A'}
230
251
  </TableCell>
231
252
  <TableCell className="text-right">
232
253
  <Button
@@ -234,7 +255,7 @@ export const PaymentsList: React.FC = () => {
234
255
  size="sm"
235
256
  onClick={(e) => {
236
257
  e.stopPropagation();
237
- openPaymentDetails(payment.id);
258
+ openPaymentDetailsDialog(String(payment.id));
238
259
  }}
239
260
  >
240
261
  <ExternalLink className="h-4 w-4" />
@@ -250,28 +271,28 @@ export const PaymentsList: React.FC = () => {
250
271
  {totalPages > 1 && (
251
272
  <div className="flex items-center justify-between">
252
273
  <div className="text-sm text-muted-foreground">
253
- Showing {showingFrom} to {showingTo} of {paymentsTotalCount} payments
274
+ Showing {showingFrom} to {showingTo} of {totalCount} payments
254
275
  </div>
255
276
 
256
277
  <div className="flex items-center gap-2">
257
278
  <Button
258
279
  variant="outline"
259
280
  size="sm"
260
- onClick={() => handlePageChange((paymentsPage || 1) - 1)}
261
- disabled={!paymentsPage || paymentsPage <= 1}
281
+ onClick={() => handlePageChange((currentPage || 1) - 1)}
282
+ disabled={!currentPage || currentPage <= 1}
262
283
  >
263
284
  <ChevronLeft className="h-4 w-4" />
264
285
  </Button>
265
286
 
266
287
  <span className="text-sm">
267
- Page {paymentsPage || 1} of {totalPages}
288
+ Page {currentPage || 1} of {totalPages}
268
289
  </span>
269
290
 
270
291
  <Button
271
292
  variant="outline"
272
293
  size="sm"
273
- onClick={() => handlePageChange((paymentsPage || 1) + 1)}
274
- disabled={!paymentsPage || paymentsPage >= totalPages}
294
+ onClick={() => handlePageChange((currentPage || 1) + 1)}
295
+ disabled={!currentPage || currentPage >= totalPages}
275
296
  >
276
297
  <ChevronRight className="h-4 w-4" />
277
298
  </Button>
@@ -284,4 +305,3 @@ export const PaymentsList: React.FC = () => {
284
305
  </Card>
285
306
  );
286
307
  };
287
-
@@ -1,2 +1 @@
1
1
  export { PaymentsList } from './PaymentsList';
2
-
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Payments View
2
+ * Payments View (v2.0 - Simplified)
3
3
  * List and manage payment transactions
4
4
  */
5
5
 
@@ -15,4 +15,3 @@ export const PaymentsView: React.FC = () => {
15
15
  </div>
16
16
  );
17
17
  };
18
-
@@ -0,0 +1,273 @@
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';
30
+ import { History, Search, Filter, RefreshCw, ArrowUpRight, ArrowDownLeft } from 'lucide-react';
31
+ import { useOverviewContext } from '@djangocfg/api/cfg/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
+ };
@@ -0,0 +1 @@
1
+ export { TransactionsList } from './TransactionsList';
@@ -1,29 +1,17 @@
1
1
  /**
2
- * Transactions View
2
+ * Transactions View (v2.0 - Simplified)
3
3
  * View transaction history and balance changes
4
4
  */
5
5
 
6
6
  'use client';
7
7
 
8
8
  import React from 'react';
9
- import { Card, CardContent } from '@djangocfg/ui';
10
- import { History } from 'lucide-react';
9
+ import { TransactionsList } from './components';
11
10
 
12
11
  export const TransactionsView: React.FC = () => {
13
12
  return (
14
- <Card>
15
- <CardContent className="py-12">
16
- <div className="text-center">
17
- <div className="w-16 h-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center">
18
- <History className="w-8 h-8 text-muted-foreground" />
19
- </div>
20
- <h3 className="text-lg font-semibold mb-2">Transactions Coming Soon</h3>
21
- <p className="text-muted-foreground">
22
- Transaction history and balance changes will be available here
23
- </p>
24
- </div>
25
- </CardContent>
26
- </Card>
13
+ <div className="space-y-6">
14
+ <TransactionsList />
15
+ </div>
27
16
  );
28
17
  };
29
-
@@ -1,133 +0,0 @@
1
- # Payments Layout
2
-
3
- Modern payments layout with tabbed interface for managing payments, balances, API keys, and subscriptions.
4
-
5
- ## Structure
6
-
7
- ```
8
- PaymentsLayout/
9
- ├── PaymentsLayout.tsx # Main layout with tabs and providers
10
- ├── events.ts # Event-based dialog communication
11
- ├── types.ts # TypeScript types
12
- ├── index.ts # Public exports
13
- └── views/ # Tab views
14
- ├── overview/ # Dashboard with metrics and balance
15
- │ ├── components/
16
- │ │ ├── MetricsCards.tsx
17
- │ │ ├── BalanceCard.tsx
18
- │ │ ├── RecentPayments.tsx
19
- │ │ └── index.ts
20
- │ └── index.tsx
21
- ├── payments/ # Payment transactions list
22
- │ ├── components/
23
- │ │ ├── PaymentsList.tsx
24
- │ │ └── index.ts
25
- │ └── index.tsx
26
- ├── transactions/ # Transaction history (placeholder)
27
- │ └── index.tsx
28
- ├── apikeys/ # API keys management
29
- │ ├── components/
30
- │ │ ├── ApiKeysList.tsx
31
- │ │ ├── ApiKeyMetrics.tsx
32
- │ │ └── index.ts
33
- │ └── index.tsx
34
- └── tariffs/ # Tariff plans (placeholder)
35
- └── index.tsx
36
- ```
37
-
38
- ## Usage
39
-
40
- ```tsx
41
- import { PaymentsLayout } from '@djangocfg/layouts';
42
-
43
- function PaymentsPage() {
44
- return <PaymentsLayout />;
45
- }
46
- ```
47
-
48
- ## Features
49
-
50
- ### Tabs
51
- - **Overview** - Dashboard with key metrics, balance card, and recent payments
52
- - **Payments** - Full payment history with search and filters
53
- - **Transactions** - Balance transaction history (coming soon)
54
- - **API Keys** - Create, manage, and monitor API keys
55
- - **Tariffs** - Subscription plans and pricing (coming soon)
56
-
57
- ### Contexts
58
- The layout automatically provides all necessary contexts:
59
- - `OverviewProvider` - Dashboard metrics and overview data
60
- - `PaymentsProvider` - Payment CRUD operations
61
- - `BalancesProvider` - User balance management
62
- - `ApiKeysProvider` - API key management
63
-
64
- ### Event System
65
- Uses event-based communication for dialogs:
66
- - `openCreateApiKeyDialog()` - Open create API key dialog
67
- - `openEditApiKeyDialog(id)` - Open edit API key dialog
68
- - `openDeleteApiKeyDialog(id)` - Open delete confirmation dialog
69
- - `openCreatePaymentDialog()` - Open create payment dialog
70
- - `openPaymentDetailsDialog(id)` - Open payment details dialog
71
- - `openCancelPaymentDialog(id)` - Open cancel payment dialog
72
- - `closePaymentsDialog()` - Close any active dialog
73
-
74
- ## Components
75
-
76
- ### Overview View
77
- - **MetricsCards** - Display key metrics (balance, payments, API keys)
78
- - **BalanceCard** - Show current balance with quick actions
79
- - **RecentPayments** - List of recent payment transactions
80
-
81
- ### Payments View
82
- - **PaymentsList** - Paginated payment list with filters and search
83
-
84
- ### API Keys View
85
- - **ApiKeyMetrics** - API key usage statistics
86
- - **ApiKeysList** - List of API keys with management actions
87
-
88
- ## Responsive Design
89
- - Mobile-first responsive layout
90
- - Adaptive tabs (icons only on mobile, text on desktop)
91
- - Responsive grid layouts for cards and metrics
92
-
93
- ## Architecture
94
-
95
- ### Decomposed Contexts
96
- Uses specialized contexts from `@djangocfg/api/cfg/contexts`:
97
- - `PaymentsContext` - Payment operations
98
- - `BalancesContext` - Balance queries
99
- - `ApiKeysContext` - API key CRUD
100
- - `OverviewContext` - Dashboard data
101
-
102
- ### View-based Structure
103
- Each tab is a separate view with:
104
- - Dedicated components folder
105
- - Dedicated hooks folder (for future custom hooks)
106
- - Clean separation of concerns
107
-
108
- ### Event-driven Dialogs
109
- Dialogs are controlled via events, not props:
110
- - Loose coupling between components
111
- - Easy to trigger from anywhere
112
- - Centralized dialog management
113
-
114
- ## Migration from Old Layout
115
-
116
- The old `PaymentsLayout_old` has been replaced with this new structure. Key improvements:
117
-
118
- 1. **Better Organization** - Views are logically separated
119
- 2. **Decomposed Contexts** - Each context has single responsibility
120
- 3. **Event System** - Better dialog management
121
- 4. **Scalability** - Easy to add new tabs and features
122
- 5. **Type Safety** - Full TypeScript support
123
-
124
- ## Future Enhancements
125
-
126
- - [ ] Complete Transactions view with filtering
127
- - [ ] Complete Tariffs view with plan comparison
128
- - [ ] Add payment creation dialogs
129
- - [ ] Add API key creation/edit dialogs
130
- - [ ] Add real-time payment status updates
131
- - [ ] Add charts and analytics
132
- - [ ] Add export functionality
133
-