@djangocfg/layouts 1.0.1

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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/package.json +86 -0
  4. package/src/auth/README.md +962 -0
  5. package/src/auth/context/AuthContext.tsx +458 -0
  6. package/src/auth/context/index.ts +2 -0
  7. package/src/auth/context/types.ts +63 -0
  8. package/src/auth/hooks/index.ts +6 -0
  9. package/src/auth/hooks/useAuthForm.ts +329 -0
  10. package/src/auth/hooks/useAuthGuard.ts +23 -0
  11. package/src/auth/hooks/useAuthRedirect.ts +51 -0
  12. package/src/auth/hooks/useAutoAuth.ts +42 -0
  13. package/src/auth/hooks/useLocalStorage.ts +211 -0
  14. package/src/auth/hooks/useSessionStorage.ts +186 -0
  15. package/src/auth/index.ts +10 -0
  16. package/src/auth/middlewares/index.ts +1 -0
  17. package/src/auth/middlewares/proxy.ts +24 -0
  18. package/src/auth/server.ts +6 -0
  19. package/src/auth/utils/errors.ts +34 -0
  20. package/src/auth/utils/index.ts +2 -0
  21. package/src/auth/utils/validation.ts +14 -0
  22. package/src/index.ts +15 -0
  23. package/src/layouts/AppLayout/AppLayout.tsx +123 -0
  24. package/src/layouts/AppLayout/README.md +204 -0
  25. package/src/layouts/AppLayout/SUMMARY.md +240 -0
  26. package/src/layouts/AppLayout/USAGE.md +312 -0
  27. package/src/layouts/AppLayout/components/PageProgress.tsx +104 -0
  28. package/src/layouts/AppLayout/components/Seo.tsx +87 -0
  29. package/src/layouts/AppLayout/components/index.ts +6 -0
  30. package/src/layouts/AppLayout/context/AppContext.tsx +146 -0
  31. package/src/layouts/AppLayout/context/index.ts +5 -0
  32. package/src/layouts/AppLayout/hooks/index.ts +6 -0
  33. package/src/layouts/AppLayout/hooks/useLayoutMode.ts +26 -0
  34. package/src/layouts/AppLayout/hooks/useNavigation.ts +49 -0
  35. package/src/layouts/AppLayout/index.ts +31 -0
  36. package/src/layouts/AppLayout/layouts/AuthLayout/AuthContext.tsx +51 -0
  37. package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +111 -0
  38. package/src/layouts/AppLayout/layouts/AuthLayout/AuthLayout.tsx +40 -0
  39. package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +330 -0
  40. package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +158 -0
  41. package/src/layouts/AppLayout/layouts/AuthLayout/index.ts +13 -0
  42. package/src/layouts/AppLayout/layouts/AuthLayout/types.ts +61 -0
  43. package/src/layouts/AppLayout/layouts/PrivateLayout/PrivateLayout.tsx +92 -0
  44. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardContent.tsx +60 -0
  45. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardHeader.tsx +170 -0
  46. package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +164 -0
  47. package/src/layouts/AppLayout/layouts/PrivateLayout/components/index.ts +7 -0
  48. package/src/layouts/AppLayout/layouts/PrivateLayout/index.ts +5 -0
  49. package/src/layouts/AppLayout/layouts/PublicLayout/PublicLayout.tsx +44 -0
  50. package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +136 -0
  51. package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +262 -0
  52. package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +289 -0
  53. package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +159 -0
  54. package/src/layouts/AppLayout/layouts/PublicLayout/index.ts +5 -0
  55. package/src/layouts/AppLayout/layouts/index.ts +7 -0
  56. package/src/layouts/AppLayout/providers/CoreProviders.tsx +47 -0
  57. package/src/layouts/AppLayout/providers/index.ts +5 -0
  58. package/src/layouts/AppLayout/types/config.ts +40 -0
  59. package/src/layouts/AppLayout/types/index.ts +10 -0
  60. package/src/layouts/AppLayout/types/layout.ts +47 -0
  61. package/src/layouts/AppLayout/types/navigation.ts +41 -0
  62. package/src/layouts/AppLayout/types/routes.ts +45 -0
  63. package/src/layouts/AppLayout/utils/index.ts +5 -0
  64. package/src/layouts/AppLayout/utils/routeDetection.ts +31 -0
  65. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +125 -0
  66. package/src/layouts/PaymentsLayout/README.md +133 -0
  67. package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +172 -0
  68. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +203 -0
  69. package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +100 -0
  70. package/src/layouts/PaymentsLayout/components/index.ts +4 -0
  71. package/src/layouts/PaymentsLayout/events.ts +106 -0
  72. package/src/layouts/PaymentsLayout/index.ts +20 -0
  73. package/src/layouts/PaymentsLayout/types.ts +19 -0
  74. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +109 -0
  75. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +194 -0
  76. package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +3 -0
  77. package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +19 -0
  78. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +99 -0
  79. package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +103 -0
  80. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +138 -0
  81. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +4 -0
  82. package/src/layouts/PaymentsLayout/views/overview/index.tsx +23 -0
  83. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +282 -0
  84. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +2 -0
  85. package/src/layouts/PaymentsLayout/views/payments/index.tsx +18 -0
  86. package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +29 -0
  87. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +29 -0
  88. package/src/layouts/ProfileLayout/ProfileLayout.tsx +110 -0
  89. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +146 -0
  90. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +208 -0
  91. package/src/layouts/ProfileLayout/components/index.ts +3 -0
  92. package/src/layouts/ProfileLayout/index.ts +3 -0
  93. package/src/layouts/SupportLayout/README.md +91 -0
  94. package/src/layouts/SupportLayout/SupportLayout.tsx +178 -0
  95. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +154 -0
  96. package/src/layouts/SupportLayout/components/MessageInput.tsx +92 -0
  97. package/src/layouts/SupportLayout/components/MessageList.tsx +312 -0
  98. package/src/layouts/SupportLayout/components/TicketCard.tsx +96 -0
  99. package/src/layouts/SupportLayout/components/TicketList.tsx +152 -0
  100. package/src/layouts/SupportLayout/components/index.ts +6 -0
  101. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +260 -0
  102. package/src/layouts/SupportLayout/context/index.ts +2 -0
  103. package/src/layouts/SupportLayout/events.ts +31 -0
  104. package/src/layouts/SupportLayout/hooks/index.ts +2 -0
  105. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +118 -0
  106. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +91 -0
  107. package/src/layouts/SupportLayout/index.ts +6 -0
  108. package/src/layouts/SupportLayout/types.ts +23 -0
  109. package/src/layouts/index.ts +9 -0
  110. package/src/snippets/AuthDialog/AuthDialog.tsx +88 -0
  111. package/src/snippets/AuthDialog/events.ts +21 -0
  112. package/src/snippets/AuthDialog/index.ts +3 -0
  113. package/src/snippets/AuthDialog/useAuthDialog.ts +27 -0
  114. package/src/snippets/Breadcrumbs.tsx +80 -0
  115. package/src/snippets/Chat/ChatUIContext.tsx +110 -0
  116. package/src/snippets/Chat/ChatWidget.tsx +476 -0
  117. package/src/snippets/Chat/README.md +122 -0
  118. package/src/snippets/Chat/components/MessageInput.tsx +124 -0
  119. package/src/snippets/Chat/components/MessageList.tsx +168 -0
  120. package/src/snippets/Chat/components/SessionList.tsx +192 -0
  121. package/src/snippets/Chat/components/index.ts +9 -0
  122. package/src/snippets/Chat/hooks/index.ts +6 -0
  123. package/src/snippets/Chat/hooks/useInfiniteSessions.ts +83 -0
  124. package/src/snippets/Chat/index.tsx +44 -0
  125. package/src/snippets/Chat/types.ts +79 -0
  126. package/src/snippets/VideoPlayer/README.md +203 -0
  127. package/src/snippets/VideoPlayer/VideoControls.tsx +133 -0
  128. package/src/snippets/VideoPlayer/VideoPlayer.tsx +114 -0
  129. package/src/snippets/VideoPlayer/index.ts +8 -0
  130. package/src/snippets/VideoPlayer/types.ts +61 -0
  131. package/src/snippets/index.ts +10 -0
  132. package/src/styles/dashboard.css +41 -0
  133. package/src/styles/index.css +20 -0
  134. package/src/styles/sources.css +6 -0
  135. package/src/types/index.ts +1 -0
  136. package/src/types/pageConfig.ts +103 -0
  137. package/src/utils/index.ts +6 -0
  138. package/src/utils/logger.ts +57 -0
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Recent Payments Component
3
+ * Display recent payment transactions
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import {
10
+ Card,
11
+ CardContent,
12
+ CardHeader,
13
+ CardTitle,
14
+ Button,
15
+ Badge,
16
+ Skeleton,
17
+ } from '@djangocfg/ui';
18
+ import { History, ExternalLink } from 'lucide-react';
19
+ import { useOverviewContext } from '@djangocfg/api/cfg/contexts';
20
+ import { openPaymentDetailsDialog } from '../../../events';
21
+
22
+ export const RecentPayments: React.FC = () => {
23
+ const { recentPayments, isLoadingOverview } = useOverviewContext();
24
+
25
+ const formatCurrency = (amount?: number | null) => {
26
+ if (amount === null || amount === undefined) return '$0.00';
27
+ return new Intl.NumberFormat('en-US', {
28
+ style: 'currency',
29
+ currency: 'USD',
30
+ minimumFractionDigits: 2,
31
+ }).format(amount);
32
+ };
33
+
34
+ const getRelativeTime = (date: string | null | undefined): string => {
35
+ if (!date) return 'N/A';
36
+
37
+ const now = new Date();
38
+ const target = new Date(date);
39
+ const diffInSeconds = Math.floor((now.getTime() - target.getTime()) / 1000);
40
+
41
+ if (diffInSeconds < 60) return 'Just now';
42
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
43
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
44
+ return `${Math.floor(diffInSeconds / 86400)}d ago`;
45
+ };
46
+
47
+ const getStatusVariant = (
48
+ status: string | null | undefined
49
+ ): 'default' | 'destructive' | 'outline' | 'secondary' => {
50
+ switch (status?.toLowerCase()) {
51
+ case 'completed':
52
+ case 'success':
53
+ return 'default';
54
+ case 'pending':
55
+ return 'secondary';
56
+ case 'failed':
57
+ case 'error':
58
+ return 'destructive';
59
+ default:
60
+ return 'outline';
61
+ }
62
+ };
63
+
64
+ if (isLoadingOverview) {
65
+ return (
66
+ <Card>
67
+ <CardHeader>
68
+ <CardTitle className="flex items-center gap-2">
69
+ <History className="h-5 w-5" />
70
+ Recent Payments
71
+ </CardTitle>
72
+ </CardHeader>
73
+ <CardContent className="space-y-3">
74
+ {Array.from({ length: 5 }).map((_, i) => (
75
+ <div key={i} className="flex items-center justify-between p-3 border rounded-lg">
76
+ <div className="space-y-2">
77
+ <Skeleton className="h-4 w-32" />
78
+ <Skeleton className="h-3 w-24" />
79
+ </div>
80
+ <Skeleton className="h-6 w-16" />
81
+ </div>
82
+ ))}
83
+ </CardContent>
84
+ </Card>
85
+ );
86
+ }
87
+
88
+ const payments = recentPayments?.results || [];
89
+
90
+ return (
91
+ <Card>
92
+ <CardHeader>
93
+ <CardTitle className="flex items-center justify-between">
94
+ <div className="flex items-center gap-2">
95
+ <History className="h-5 w-5" />
96
+ Recent Payments
97
+ </div>
98
+ <Button variant="ghost" size="sm">
99
+ View All
100
+ <ExternalLink className="h-4 w-4 ml-2" />
101
+ </Button>
102
+ </CardTitle>
103
+ </CardHeader>
104
+ <CardContent>
105
+ {payments.length === 0 ? (
106
+ <div className="text-center py-8 text-muted-foreground">
107
+ <History className="h-12 w-12 mx-auto mb-4 opacity-50" />
108
+ <p>No recent payments</p>
109
+ </div>
110
+ ) : (
111
+ <div className="space-y-3">
112
+ {payments.map((payment) => (
113
+ <div
114
+ key={payment.id}
115
+ className="flex items-center justify-between p-3 border rounded-lg hover:bg-accent cursor-pointer transition-colors"
116
+ onClick={() => openPaymentDetailsDialog(payment.id)}
117
+ >
118
+ <div className="flex-1">
119
+ <div className="flex items-center gap-2">
120
+ <span className="font-medium">{formatCurrency(payment.amount_usd)}</span>
121
+ <Badge variant={getStatusVariant(payment.status)} className="text-xs">
122
+ {payment.status}
123
+ </Badge>
124
+ </div>
125
+ <p className="text-sm text-muted-foreground">
126
+ {getRelativeTime(payment.created_at)} • {payment.provider || 'Unknown provider'}
127
+ </p>
128
+ </div>
129
+ <ExternalLink className="h-4 w-4 text-muted-foreground" />
130
+ </div>
131
+ ))}
132
+ </div>
133
+ )}
134
+ </CardContent>
135
+ </Card>
136
+ );
137
+ };
138
+
@@ -0,0 +1,4 @@
1
+ export { MetricsCards } from './MetricsCards';
2
+ export { BalanceCard } from './BalanceCard';
3
+ export { RecentPayments } from './RecentPayments';
4
+
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Overview View
3
+ * Dashboard with metrics, balance, and recent activity
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import { MetricsCards, BalanceCard, RecentPayments } from './components';
10
+
11
+ export const OverviewView: React.FC = () => {
12
+ return (
13
+ <div className="space-y-6">
14
+ <MetricsCards />
15
+
16
+ <div className="grid gap-6 lg:grid-cols-2">
17
+ <BalanceCard />
18
+ <RecentPayments />
19
+ </div>
20
+ </div>
21
+ );
22
+ };
23
+
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Payments List Component
3
+ * Display paginated list of payments with filters
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 { Plus, Search, Filter, ChevronLeft, ChevronRight, RefreshCw, ExternalLink } from 'lucide-react';
31
+ import { usePaymentsContext } from '@djangocfg/api/cfg/contexts';
32
+ import { openCreatePaymentDialog, openPaymentDetailsDialog } from '../../../events';
33
+
34
+ export const PaymentsList: React.FC = () => {
35
+ const {
36
+ payments,
37
+ isLoadingPayments,
38
+ refreshPayments,
39
+ } = usePaymentsContext();
40
+
41
+ const paymentsList = payments?.results || [];
42
+ const paymentsPage = payments?.page || 1;
43
+ const paymentsPageSize = payments?.page_size || 20;
44
+ const paymentsTotalCount = payments?.count || 0;
45
+
46
+ const [searchTerm, setSearchTerm] = useState('');
47
+ const [statusFilter, setStatusFilter] = useState<string>('all');
48
+
49
+ const formatCurrency = (amount?: number | null) => {
50
+ if (amount === null || amount === undefined) return '$0.00';
51
+ return new Intl.NumberFormat('en-US', {
52
+ style: 'currency',
53
+ currency: 'USD',
54
+ minimumFractionDigits: 2,
55
+ }).format(amount);
56
+ };
57
+
58
+ const getRelativeTime = (date: string | null | undefined): string => {
59
+ if (!date) return 'N/A';
60
+
61
+ const now = new Date();
62
+ const target = new Date(date);
63
+ const diffInSeconds = Math.floor((now.getTime() - target.getTime()) / 1000);
64
+
65
+ if (diffInSeconds < 60) return 'Just now';
66
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
67
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
68
+ return `${Math.floor(diffInSeconds / 86400)}d ago`;
69
+ };
70
+
71
+ const getStatusVariant = (
72
+ status: string | null | undefined
73
+ ): 'default' | 'destructive' | 'outline' | 'secondary' => {
74
+ switch (status?.toLowerCase()) {
75
+ case 'completed':
76
+ case 'success':
77
+ return 'default';
78
+ case 'pending':
79
+ return 'secondary';
80
+ case 'failed':
81
+ case 'error':
82
+ return 'destructive';
83
+ default:
84
+ return 'outline';
85
+ }
86
+ };
87
+
88
+ const handleSearch = async (value: string) => {
89
+ setSearchTerm(value);
90
+ // TODO: Implement search/filter in PaymentsContext
91
+ await refreshPayments();
92
+ };
93
+
94
+ const handleStatusFilter = async (status: string) => {
95
+ setStatusFilter(status);
96
+ // TODO: Implement search/filter in PaymentsContext
97
+ await refreshPayments();
98
+ };
99
+
100
+ const handlePageChange = async (page: number) => {
101
+ // TODO: Implement pagination in PaymentsContext
102
+ await refreshPayments();
103
+ };
104
+
105
+ const totalPages = Math.ceil((paymentsTotalCount || 0) / (paymentsPageSize || 20));
106
+ const showingFrom = ((paymentsPage || 1) - 1) * (paymentsPageSize || 20) + 1;
107
+ const showingTo = Math.min((paymentsPage || 1) * (paymentsPageSize || 20), paymentsTotalCount || 0);
108
+
109
+ return (
110
+ <Card>
111
+ <CardHeader>
112
+ <CardTitle className="flex items-center justify-between">
113
+ <span>Payment History</span>
114
+ <div className="flex items-center gap-2">
115
+ <Button variant="outline" size="sm" onClick={refreshPayments} disabled={isLoadingPayments}>
116
+ <RefreshCw className={`h-4 w-4 mr-2 ${isLoadingPayments ? 'animate-spin' : ''}`} />
117
+ Refresh
118
+ </Button>
119
+ <Button size="sm" onClick={() => openCreatePaymentDialog()}>
120
+ <Plus className="h-4 w-4 mr-2" />
121
+ New Payment
122
+ </Button>
123
+ </div>
124
+ </CardTitle>
125
+ </CardHeader>
126
+
127
+ <CardContent className="space-y-4">
128
+ {/* Filters */}
129
+ <div className="flex flex-col sm:flex-row gap-4">
130
+ <div className="relative flex-1">
131
+ <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
132
+ <Input
133
+ placeholder="Search payments..."
134
+ value={searchTerm}
135
+ onChange={(e) => handleSearch(e.target.value)}
136
+ className="pl-10"
137
+ />
138
+ </div>
139
+
140
+ <Select value={statusFilter} onValueChange={handleStatusFilter}>
141
+ <SelectTrigger className="w-full sm:w-48">
142
+ <Filter className="h-4 w-4 mr-2" />
143
+ <SelectValue placeholder="Filter by status" />
144
+ </SelectTrigger>
145
+ <SelectContent>
146
+ <SelectItem value="all">All Statuses</SelectItem>
147
+ <SelectItem value="completed">Completed</SelectItem>
148
+ <SelectItem value="pending">Pending</SelectItem>
149
+ <SelectItem value="failed">Failed</SelectItem>
150
+ </SelectContent>
151
+ </Select>
152
+ </div>
153
+
154
+ {/* Payments Table */}
155
+ {isLoadingPayments ? (
156
+ <div className="space-y-3">
157
+ {Array.from({ length: 5 }).map((_, i) => (
158
+ <div key={i} className="flex items-center justify-between p-4 border rounded-lg">
159
+ <div className="space-y-2">
160
+ <Skeleton className="h-4 w-32" />
161
+ <Skeleton className="h-3 w-24" />
162
+ </div>
163
+ <Skeleton className="h-6 w-16" />
164
+ </div>
165
+ ))}
166
+ </div>
167
+ ) : !paymentsList || paymentsList.length === 0 ? (
168
+ <div className="text-center py-12">
169
+ <div className="w-16 h-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center">
170
+ <Search className="w-8 h-8 text-muted-foreground" />
171
+ </div>
172
+ <h3 className="text-lg font-semibold mb-2">No Payments Found</h3>
173
+ <p className="text-muted-foreground mb-4">
174
+ {searchTerm || statusFilter !== 'all'
175
+ ? 'No payments match your current filters'
176
+ : "You haven't made any payments yet"}
177
+ </p>
178
+ <Button onClick={() => openCreatePaymentDialog()}>
179
+ <Plus className="h-4 w-4 mr-2" />
180
+ Create Payment
181
+ </Button>
182
+ </div>
183
+ ) : (
184
+ <>
185
+ <div className="rounded-md border">
186
+ <Table>
187
+ <TableHeader>
188
+ <TableRow>
189
+ <TableHead>Date</TableHead>
190
+ <TableHead>Amount</TableHead>
191
+ <TableHead>Currency</TableHead>
192
+ <TableHead>Status</TableHead>
193
+ <TableHead>Provider</TableHead>
194
+ <TableHead>ID</TableHead>
195
+ <TableHead className="text-right">Actions</TableHead>
196
+ </TableRow>
197
+ </TableHeader>
198
+ <TableBody>
199
+ {paymentsList.map((payment) => (
200
+ <TableRow key={payment.id} className="cursor-pointer hover:bg-accent">
201
+ <TableCell>
202
+ <div>
203
+ <div className="font-medium">
204
+ {payment.created_at
205
+ ? new Date(payment.created_at).toLocaleDateString()
206
+ : 'N/A'}
207
+ </div>
208
+ <div className="text-sm text-muted-foreground">
209
+ {getRelativeTime(payment.created_at)}
210
+ </div>
211
+ </div>
212
+ </TableCell>
213
+ <TableCell className="font-mono font-semibold">
214
+ {formatCurrency(payment.amount_usd)}
215
+ </TableCell>
216
+ <TableCell>
217
+ <Badge variant="outline">Currency #{payment.currency}</Badge>
218
+ </TableCell>
219
+ <TableCell>
220
+ <Badge variant={getStatusVariant(payment.status)}>{payment.status}</Badge>
221
+ </TableCell>
222
+ <TableCell>{payment.provider || 'N/A'}</TableCell>
223
+ <TableCell className="font-mono text-sm text-muted-foreground">
224
+ {payment.id.slice(0, 8)}...
225
+ </TableCell>
226
+ <TableCell className="text-right">
227
+ <Button
228
+ variant="ghost"
229
+ size="sm"
230
+ onClick={(e) => {
231
+ e.stopPropagation();
232
+ openPaymentDetailsDialog(payment.id);
233
+ }}
234
+ >
235
+ <ExternalLink className="h-4 w-4" />
236
+ </Button>
237
+ </TableCell>
238
+ </TableRow>
239
+ ))}
240
+ </TableBody>
241
+ </Table>
242
+ </div>
243
+
244
+ {/* Pagination */}
245
+ {totalPages > 1 && (
246
+ <div className="flex items-center justify-between">
247
+ <div className="text-sm text-muted-foreground">
248
+ Showing {showingFrom} to {showingTo} of {paymentsTotalCount} payments
249
+ </div>
250
+
251
+ <div className="flex items-center gap-2">
252
+ <Button
253
+ variant="outline"
254
+ size="sm"
255
+ onClick={() => handlePageChange((paymentsPage || 1) - 1)}
256
+ disabled={!paymentsPage || paymentsPage <= 1}
257
+ >
258
+ <ChevronLeft className="h-4 w-4" />
259
+ </Button>
260
+
261
+ <span className="text-sm">
262
+ Page {paymentsPage || 1} of {totalPages}
263
+ </span>
264
+
265
+ <Button
266
+ variant="outline"
267
+ size="sm"
268
+ onClick={() => handlePageChange((paymentsPage || 1) + 1)}
269
+ disabled={!paymentsPage || paymentsPage >= totalPages}
270
+ >
271
+ <ChevronRight className="h-4 w-4" />
272
+ </Button>
273
+ </div>
274
+ </div>
275
+ )}
276
+ </>
277
+ )}
278
+ </CardContent>
279
+ </Card>
280
+ );
281
+ };
282
+
@@ -0,0 +1,2 @@
1
+ export { PaymentsList } from './PaymentsList';
2
+
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Payments View
3
+ * List and manage payment transactions
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import { PaymentsList } from './components';
10
+
11
+ export const PaymentsView: React.FC = () => {
12
+ return (
13
+ <div className="space-y-6">
14
+ <PaymentsList />
15
+ </div>
16
+ );
17
+ };
18
+
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Tariffs View
3
+ * View and manage tariff plans
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import { Card, CardContent } from '@djangocfg/ui';
10
+ import { Crown } from 'lucide-react';
11
+
12
+ export const TariffsView: React.FC = () => {
13
+ 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
+ <Crown className="w-8 h-8 text-muted-foreground" />
19
+ </div>
20
+ <h3 className="text-lg font-semibold mb-2">Tariff Plans Coming Soon</h3>
21
+ <p className="text-muted-foreground">
22
+ Subscription tiers and pricing plans will be available here
23
+ </p>
24
+ </div>
25
+ </CardContent>
26
+ </Card>
27
+ );
28
+ };
29
+
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Transactions View
3
+ * View transaction history and balance changes
4
+ */
5
+
6
+ 'use client';
7
+
8
+ import React from 'react';
9
+ import { Card, CardContent } from '@djangocfg/ui';
10
+ import { History } from 'lucide-react';
11
+
12
+ export const TransactionsView: React.FC = () => {
13
+ 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>
27
+ );
28
+ };
29
+
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from '@djangocfg/ui/components';
12
+ import { AccountsProvider } from '@djangocfg/api/cfg/contexts';
13
+ import { useAuth } from '../../auth';
14
+
15
+ import { AvatarSection, ProfileForm } from './components';
16
+
17
+ interface ProfileLayoutProps {
18
+ // Callbacks
19
+ onUnauthenticated?: () => void;
20
+
21
+ // Optional customization
22
+ title?: string;
23
+ description?: string;
24
+ showMemberSince?: boolean;
25
+ }
26
+
27
+ const ProfileContent = ({
28
+ onUnauthenticated,
29
+ title = 'Profile Settings',
30
+ description = 'Manage your account information and preferences',
31
+ showMemberSince = true
32
+ }: ProfileLayoutProps) => {
33
+ const { user, isLoading } = useAuth();
34
+
35
+ const formatDate = (dateString: string) => {
36
+ return new Date(dateString).toLocaleDateString('en-US', {
37
+ year: 'numeric',
38
+ month: 'long',
39
+ day: 'numeric',
40
+ });
41
+ };
42
+
43
+ // Show auth check if no user
44
+ if (!user && !isLoading) {
45
+ React.useEffect(() => {
46
+ if (onUnauthenticated) {
47
+ onUnauthenticated();
48
+ }
49
+ }, [onUnauthenticated]);
50
+
51
+ return (
52
+ <div className="flex items-center justify-center min-h-screen">
53
+ <div className="text-center">
54
+ <h1 className="text-2xl font-bold text-foreground mb-4">Not Authenticated</h1>
55
+ <p className="text-muted-foreground mb-4">Please log in to view your profile.</p>
56
+ </div>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ if (isLoading) {
62
+ return (
63
+ <div className="flex items-center justify-center min-h-screen">
64
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ return (
70
+ <div className="container mx-auto px-4 py-8">
71
+ <div className="max-w-2xl mx-auto space-y-6">
72
+ {/* Header */}
73
+ <div className="text-center space-y-2">
74
+ <h1 className="text-3xl font-bold text-foreground">{title}</h1>
75
+ <p className="text-muted-foreground">{description}</p>
76
+ </div>
77
+
78
+ {/* Main Profile Card */}
79
+ <Card className="bg-card/50 backdrop-blur-sm border-border/50">
80
+ <CardHeader className="text-center pb-6">
81
+ <AvatarSection />
82
+
83
+ <CardTitle className="text-xl">
84
+ {user?.display_username || user?.email}
85
+ </CardTitle>
86
+ {showMemberSince && user?.date_joined && (
87
+ <CardDescription className="text-muted-foreground">
88
+ Member since {formatDate(user.date_joined)}
89
+ </CardDescription>
90
+ )}
91
+ </CardHeader>
92
+
93
+ <CardContent className="space-y-6">
94
+ {/* Profile Form */}
95
+ <ProfileForm />
96
+ </CardContent>
97
+ </Card>
98
+ </div>
99
+ </div>
100
+ );
101
+ };
102
+
103
+ export const ProfileLayout: React.FC<ProfileLayoutProps> = (props) => {
104
+ return (
105
+ <AccountsProvider>
106
+ <ProfileContent {...props} />
107
+ </AccountsProvider>
108
+ );
109
+ };
110
+