@djangocfg/layouts 1.0.2 → 1.0.4
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/package.json +5 -5
- package/src/layouts/AppLayout/layouts/AuthLayout/AuthHelp.tsx +7 -5
- package/src/layouts/AppLayout/layouts/AuthLayout/IdentifierForm.tsx +3 -3
- package/src/layouts/AppLayout/layouts/AuthLayout/OTPForm.tsx +26 -10
- package/src/layouts/AppLayout/layouts/PrivateLayout/components/DashboardSidebar.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/DesktopUserMenu.tsx +6 -6
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Footer.tsx +1 -1
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenu.tsx +43 -133
- package/src/layouts/AppLayout/layouts/PublicLayout/components/MobileMenuUserCard.tsx +150 -0
- package/src/layouts/AppLayout/layouts/PublicLayout/components/Navigation.tsx +2 -2
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +41 -57
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +188 -57
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +323 -0
- package/src/layouts/PaymentsLayout/components/index.ts +1 -0
- package/src/layouts/PaymentsLayout/context/RootPaymentsContext.tsx +129 -0
- package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +2 -2
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +6 -6
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +2 -2
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +9 -4
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +2 -3
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +2 -3
- package/src/snippets/Chat/components/SessionList.tsx +1 -1
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +2 -2
- package/src/snippets/VideoPlayer/VideoPlayer.tsx +1 -1
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payment Details Dialog
|
|
3
|
+
* Shows payment details with QR code, address, and status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import React, { useState, useEffect } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
Dialog,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogDescription,
|
|
13
|
+
DialogFooter,
|
|
14
|
+
DialogHeader,
|
|
15
|
+
DialogTitle,
|
|
16
|
+
Button,
|
|
17
|
+
TokenIcon,
|
|
18
|
+
} from '@djangocfg/ui';
|
|
19
|
+
import { Copy, ExternalLink, CheckCircle2, Clock, XCircle, AlertCircle, RefreshCw } from 'lucide-react';
|
|
20
|
+
import { Hooks, api } from '@djangocfg/api';
|
|
21
|
+
import type { API } from '@djangocfg/api';
|
|
22
|
+
|
|
23
|
+
export const PAYMENT_DETAILS_EVENTS = {
|
|
24
|
+
OPEN_PAYMENT_DETAILS: 'open-payment-details',
|
|
25
|
+
CLOSE_PAYMENT_DETAILS: 'close-payment-details',
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export const PaymentDetailsDialog: React.FC = () => {
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
const [paymentId, setPaymentId] = useState<string | null>(null);
|
|
31
|
+
const [copied, setCopied] = useState(false);
|
|
32
|
+
const [timeLeft, setTimeLeft] = useState<string>('');
|
|
33
|
+
|
|
34
|
+
// Load payment data by ID using hook
|
|
35
|
+
// Only fetch when dialog is open and paymentId is set
|
|
36
|
+
const shouldFetch = open && !!paymentId;
|
|
37
|
+
const { data: payment, isLoading, error, mutate } = Hooks.usePaymentsPaymentRetrieve(
|
|
38
|
+
shouldFetch ? paymentId : '',
|
|
39
|
+
api as unknown as API
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Debug logging
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (error) {
|
|
45
|
+
console.error('Payment loading error:', error);
|
|
46
|
+
}
|
|
47
|
+
}, [error]);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const handleOpen = (event: Event) => {
|
|
51
|
+
const customEvent = event as CustomEvent<{ paymentId: string }>;
|
|
52
|
+
console.log('Opening payment details for ID:', customEvent.detail.paymentId);
|
|
53
|
+
setPaymentId(customEvent.detail.paymentId);
|
|
54
|
+
setOpen(true);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleClose = () => {
|
|
58
|
+
setOpen(false);
|
|
59
|
+
setPaymentId(null);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
window.addEventListener(PAYMENT_DETAILS_EVENTS.OPEN_PAYMENT_DETAILS, handleOpen);
|
|
63
|
+
window.addEventListener(PAYMENT_DETAILS_EVENTS.CLOSE_PAYMENT_DETAILS, handleClose);
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
window.removeEventListener(PAYMENT_DETAILS_EVENTS.OPEN_PAYMENT_DETAILS, handleOpen);
|
|
67
|
+
window.removeEventListener(PAYMENT_DETAILS_EVENTS.CLOSE_PAYMENT_DETAILS, handleClose);
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const handleClose = () => {
|
|
72
|
+
setOpen(false);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleCopyAddress = async () => {
|
|
76
|
+
if (payment?.pay_address) {
|
|
77
|
+
await navigator.clipboard.writeText(payment.pay_address);
|
|
78
|
+
setCopied(true);
|
|
79
|
+
setTimeout(() => setCopied(false), 2000);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Calculate time left until expiration
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!payment?.expires_at) return;
|
|
86
|
+
|
|
87
|
+
const updateTimeLeft = () => {
|
|
88
|
+
const now = new Date().getTime();
|
|
89
|
+
const expires = new Date(payment.expires_at!).getTime();
|
|
90
|
+
const diff = expires - now;
|
|
91
|
+
|
|
92
|
+
if (diff <= 0) {
|
|
93
|
+
setTimeLeft('Expired');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
98
|
+
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
99
|
+
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
|
100
|
+
|
|
101
|
+
setTimeLeft(`${hours}h ${minutes}m ${seconds}s`);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
updateTimeLeft();
|
|
105
|
+
const interval = setInterval(updateTimeLeft, 1000);
|
|
106
|
+
|
|
107
|
+
return () => clearInterval(interval);
|
|
108
|
+
}, [payment?.expires_at]);
|
|
109
|
+
|
|
110
|
+
// Get status icon and color
|
|
111
|
+
const getStatusInfo = () => {
|
|
112
|
+
switch (payment?.status) {
|
|
113
|
+
case 'pending':
|
|
114
|
+
return { icon: Clock, color: 'text-yellow-500', bg: 'bg-yellow-500/10' };
|
|
115
|
+
case 'completed':
|
|
116
|
+
return { icon: CheckCircle2, color: 'text-green-500', bg: 'bg-green-500/10' };
|
|
117
|
+
case 'failed':
|
|
118
|
+
return { icon: XCircle, color: 'text-red-500', bg: 'bg-red-500/10' };
|
|
119
|
+
case 'expired':
|
|
120
|
+
return { icon: AlertCircle, color: 'text-gray-500', bg: 'bg-gray-500/10' };
|
|
121
|
+
default:
|
|
122
|
+
return { icon: Clock, color: 'text-gray-500', bg: 'bg-gray-500/10' };
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (!open) return null;
|
|
127
|
+
|
|
128
|
+
// Loading state
|
|
129
|
+
if (isLoading) {
|
|
130
|
+
return (
|
|
131
|
+
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
132
|
+
<DialogContent className="sm:max-w-lg">
|
|
133
|
+
<DialogHeader>
|
|
134
|
+
<DialogTitle>Payment Details</DialogTitle>
|
|
135
|
+
<DialogDescription>Loading payment information...</DialogDescription>
|
|
136
|
+
</DialogHeader>
|
|
137
|
+
<div className="flex items-center justify-center py-12">
|
|
138
|
+
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
139
|
+
</div>
|
|
140
|
+
</DialogContent>
|
|
141
|
+
</Dialog>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Error state - only show error if we actually tried to fetch and failed
|
|
146
|
+
if (shouldFetch && !isLoading && (error || !payment)) {
|
|
147
|
+
return (
|
|
148
|
+
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
149
|
+
<DialogContent className="sm:max-w-lg">
|
|
150
|
+
<DialogHeader>
|
|
151
|
+
<DialogTitle>Payment Details</DialogTitle>
|
|
152
|
+
<DialogDescription>Failed to load payment information</DialogDescription>
|
|
153
|
+
</DialogHeader>
|
|
154
|
+
<div className="flex flex-col items-center justify-center py-12 space-y-4">
|
|
155
|
+
<XCircle className="h-12 w-12 text-destructive" />
|
|
156
|
+
<p className="text-sm text-muted-foreground">
|
|
157
|
+
{error ? `Error: ${error}` : 'Payment not found'}
|
|
158
|
+
</p>
|
|
159
|
+
<Button onClick={() => mutate()}>Try Again</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</DialogContent>
|
|
162
|
+
</Dialog>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const statusInfo = getStatusInfo();
|
|
167
|
+
const StatusIcon = statusInfo.icon;
|
|
168
|
+
|
|
169
|
+
// Generate QR code URL (using simple data URL)
|
|
170
|
+
const qrCodeUrl = payment.pay_address
|
|
171
|
+
? `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(payment.pay_address)}`
|
|
172
|
+
: null;
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
176
|
+
<DialogContent className="sm:max-w-lg">
|
|
177
|
+
<DialogHeader>
|
|
178
|
+
<DialogTitle>Payment Details</DialogTitle>
|
|
179
|
+
<DialogDescription>
|
|
180
|
+
Send cryptocurrency to complete your payment
|
|
181
|
+
</DialogDescription>
|
|
182
|
+
</DialogHeader>
|
|
183
|
+
|
|
184
|
+
<div className="space-y-6">
|
|
185
|
+
{/* Status Badge */}
|
|
186
|
+
<div className={`flex items-center gap-3 p-4 rounded-sm ${statusInfo.bg}`}>
|
|
187
|
+
<StatusIcon className={`h-5 w-5 ${statusInfo.color}`} />
|
|
188
|
+
<div className="flex-1">
|
|
189
|
+
<div className="font-semibold capitalize">{payment.status}</div>
|
|
190
|
+
{payment.status === 'pending' && timeLeft && (
|
|
191
|
+
<div className="text-sm text-muted-foreground">
|
|
192
|
+
Expires in {timeLeft}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Amount Information */}
|
|
199
|
+
<div className="space-y-3">
|
|
200
|
+
<div className="flex items-center justify-between p-4 bg-muted rounded-sm">
|
|
201
|
+
<span className="text-sm text-muted-foreground">Amount to send</span>
|
|
202
|
+
<div className="flex items-center gap-2">
|
|
203
|
+
<TokenIcon symbol={String(payment.currency || 'BTC')} size={20} />
|
|
204
|
+
<span className="font-mono font-bold text-lg">
|
|
205
|
+
{payment.amount_crypto?.toFixed(8)} {payment.currency}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div className="flex items-center justify-between px-4">
|
|
211
|
+
<span className="text-sm text-muted-foreground">Equivalent to</span>
|
|
212
|
+
<span className="font-semibold text-lg">${payment.amount_usd?.toFixed(2)} USD</span>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
{payment.provider_payment_id && (
|
|
216
|
+
<div className="flex items-center justify-between px-4">
|
|
217
|
+
<span className="text-sm text-muted-foreground">Payment Order #</span>
|
|
218
|
+
<span className="font-mono font-medium">{payment.provider_payment_id}</span>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
|
|
222
|
+
{payment.network && (
|
|
223
|
+
<div className="flex items-center justify-between px-4">
|
|
224
|
+
<span className="text-sm text-muted-foreground">Network</span>
|
|
225
|
+
<span className="font-medium">{payment.network}</span>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
{/* QR Code */}
|
|
231
|
+
{qrCodeUrl && payment.status === 'pending' && (
|
|
232
|
+
<div className="flex justify-center p-6 bg-white rounded-sm">
|
|
233
|
+
<img src={qrCodeUrl} alt="Payment QR Code" className="w-48 h-48" />
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
|
|
237
|
+
{/* Payment Address */}
|
|
238
|
+
{payment.pay_address && payment.status === 'pending' && (
|
|
239
|
+
<div className="space-y-2">
|
|
240
|
+
<label className="text-sm font-medium">Payment Address</label>
|
|
241
|
+
<div className="flex items-center gap-2">
|
|
242
|
+
<div className="flex-1 p-3 bg-muted rounded-sm font-mono text-sm break-all">
|
|
243
|
+
{payment.pay_address}
|
|
244
|
+
</div>
|
|
245
|
+
<Button
|
|
246
|
+
variant="outline"
|
|
247
|
+
size="icon"
|
|
248
|
+
onClick={handleCopyAddress}
|
|
249
|
+
className="shrink-0"
|
|
250
|
+
>
|
|
251
|
+
{copied ? (
|
|
252
|
+
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
|
253
|
+
) : (
|
|
254
|
+
<Copy className="h-4 w-4" />
|
|
255
|
+
)}
|
|
256
|
+
</Button>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
{/* Transaction Hash */}
|
|
262
|
+
{payment.transaction_hash && (
|
|
263
|
+
<div className="space-y-2">
|
|
264
|
+
<label className="text-sm font-medium">Transaction Hash</label>
|
|
265
|
+
<div className="p-3 bg-muted rounded-sm font-mono text-sm break-all">
|
|
266
|
+
{payment.transaction_hash}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
)}
|
|
270
|
+
|
|
271
|
+
{/* Payment URL */}
|
|
272
|
+
{payment.payment_url && payment.status === 'pending' && (
|
|
273
|
+
<Button
|
|
274
|
+
variant="outline"
|
|
275
|
+
className="w-full"
|
|
276
|
+
onClick={() => window.open(payment.payment_url!, '_blank')}
|
|
277
|
+
>
|
|
278
|
+
<ExternalLink className="h-4 w-4 mr-2" />
|
|
279
|
+
Open in Payment Provider
|
|
280
|
+
</Button>
|
|
281
|
+
)}
|
|
282
|
+
|
|
283
|
+
{/* Additional Info */}
|
|
284
|
+
<div className="pt-4 border-t space-y-2 text-xs text-muted-foreground">
|
|
285
|
+
<div className="flex justify-between">
|
|
286
|
+
<span>Payment ID</span>
|
|
287
|
+
<span className="font-mono">{payment.id}</span>
|
|
288
|
+
</div>
|
|
289
|
+
<div className="flex justify-between">
|
|
290
|
+
<span>Created</span>
|
|
291
|
+
<span>{new Date(payment.created_at!).toLocaleString()}</span>
|
|
292
|
+
</div>
|
|
293
|
+
{payment.confirmations_count !== undefined && (
|
|
294
|
+
<div className="flex justify-between">
|
|
295
|
+
<span>Confirmations</span>
|
|
296
|
+
<span>{payment.confirmations_count}</span>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<DialogFooter>
|
|
303
|
+
<Button variant="outline" onClick={handleClose}>
|
|
304
|
+
Close
|
|
305
|
+
</Button>
|
|
306
|
+
</DialogFooter>
|
|
307
|
+
</DialogContent>
|
|
308
|
+
</Dialog>
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Helper function to open payment details dialog
|
|
313
|
+
export const openPaymentDetails = (paymentId: string) => {
|
|
314
|
+
window.dispatchEvent(
|
|
315
|
+
new CustomEvent(PAYMENT_DETAILS_EVENTS.OPEN_PAYMENT_DETAILS, {
|
|
316
|
+
detail: { paymentId },
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export const closePaymentDetails = () => {
|
|
322
|
+
window.dispatchEvent(new Event(PAYMENT_DETAILS_EVENTS.CLOSE_PAYMENT_DETAILS));
|
|
323
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { CreateApiKeyDialog } from './CreateApiKeyDialog';
|
|
2
2
|
export { DeleteApiKeyDialog } from './DeleteApiKeyDialog';
|
|
3
3
|
export { CreatePaymentDialog } from './CreatePaymentDialog';
|
|
4
|
+
export { PaymentDetailsDialog, openPaymentDetails, closePaymentDetails } from './PaymentDetailsDialog';
|
|
4
5
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, useEffect, type ReactNode } from 'react';
|
|
4
|
+
import { api, Hooks } from '@djangocfg/api';
|
|
5
|
+
import type { API } from '@djangocfg/api';
|
|
6
|
+
import type {
|
|
7
|
+
Currency,
|
|
8
|
+
PaginatedCurrencyListList,
|
|
9
|
+
ProviderCurrency,
|
|
10
|
+
PaginatedProviderCurrencyList,
|
|
11
|
+
Network,
|
|
12
|
+
PaginatedNetworkList,
|
|
13
|
+
} from '@djangocfg/api';
|
|
14
|
+
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
16
|
+
// Context Type
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export interface RootPaymentsContextValue {
|
|
20
|
+
// Currencies
|
|
21
|
+
currencies: PaginatedCurrencyListList | undefined;
|
|
22
|
+
isLoadingCurrencies: boolean;
|
|
23
|
+
currenciesError: Error | undefined;
|
|
24
|
+
refreshCurrencies: () => Promise<void>;
|
|
25
|
+
|
|
26
|
+
// Provider Currencies
|
|
27
|
+
providerCurrencies: PaginatedProviderCurrencyList | undefined;
|
|
28
|
+
isLoadingProviderCurrencies: boolean;
|
|
29
|
+
providerCurrenciesError: Error | undefined;
|
|
30
|
+
refreshProviderCurrencies: () => Promise<void>;
|
|
31
|
+
|
|
32
|
+
// Networks
|
|
33
|
+
networks: PaginatedNetworkList | undefined;
|
|
34
|
+
isLoadingNetworks: boolean;
|
|
35
|
+
networksError: Error | undefined;
|
|
36
|
+
refreshNetworks: () => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
40
|
+
// Context
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const RootPaymentsContext = createContext<RootPaymentsContextValue | undefined>(undefined);
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// Provider
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export function RootPaymentsProvider({ children }: { children: ReactNode }) {
|
|
50
|
+
// List all currencies
|
|
51
|
+
const {
|
|
52
|
+
data: currencies,
|
|
53
|
+
error: currenciesError,
|
|
54
|
+
isLoading: isLoadingCurrencies,
|
|
55
|
+
mutate: mutateCurrencies,
|
|
56
|
+
} = Hooks.usePaymentsCurrenciesList({}, api as unknown as API);
|
|
57
|
+
|
|
58
|
+
// List all provider currencies
|
|
59
|
+
const {
|
|
60
|
+
data: providerCurrencies,
|
|
61
|
+
error: providerCurrenciesError,
|
|
62
|
+
isLoading: isLoadingProviderCurrencies,
|
|
63
|
+
mutate: mutateProviderCurrencies,
|
|
64
|
+
} = Hooks.usePaymentsProviderCurrenciesList({}, api as unknown as API);
|
|
65
|
+
|
|
66
|
+
// List all networks
|
|
67
|
+
const {
|
|
68
|
+
data: networks,
|
|
69
|
+
error: networksError,
|
|
70
|
+
isLoading: isLoadingNetworks,
|
|
71
|
+
mutate: mutateNetworks,
|
|
72
|
+
} = Hooks.usePaymentsNetworksList({}, api as unknown as API);
|
|
73
|
+
|
|
74
|
+
const refreshCurrencies = async () => {
|
|
75
|
+
await mutateCurrencies();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const refreshProviderCurrencies = async () => {
|
|
79
|
+
await mutateProviderCurrencies();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const refreshNetworks = async () => {
|
|
83
|
+
await mutateNetworks();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const value: RootPaymentsContextValue = {
|
|
87
|
+
currencies,
|
|
88
|
+
isLoadingCurrencies,
|
|
89
|
+
currenciesError,
|
|
90
|
+
refreshCurrencies,
|
|
91
|
+
providerCurrencies,
|
|
92
|
+
isLoadingProviderCurrencies,
|
|
93
|
+
providerCurrenciesError,
|
|
94
|
+
refreshProviderCurrencies,
|
|
95
|
+
networks,
|
|
96
|
+
isLoadingNetworks,
|
|
97
|
+
networksError,
|
|
98
|
+
refreshNetworks,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<RootPaymentsContext.Provider value={value}>{children}</RootPaymentsContext.Provider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
107
|
+
// Hook
|
|
108
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export function useRootPaymentsContext(): RootPaymentsContextValue {
|
|
111
|
+
const context = useContext(RootPaymentsContext);
|
|
112
|
+
if (!context) {
|
|
113
|
+
throw new Error('useRootPaymentsContext must be used within RootPaymentsProvider');
|
|
114
|
+
}
|
|
115
|
+
return context;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
119
|
+
// Re-export types
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
export type {
|
|
123
|
+
Currency,
|
|
124
|
+
PaginatedCurrencyListList,
|
|
125
|
+
ProviderCurrency,
|
|
126
|
+
PaginatedProviderCurrencyList,
|
|
127
|
+
Network,
|
|
128
|
+
PaginatedNetworkList,
|
|
129
|
+
};
|
|
@@ -88,7 +88,7 @@ export const ApiKeysList: React.FC = () => {
|
|
|
88
88
|
<CardContent>
|
|
89
89
|
<div className="space-y-4">
|
|
90
90
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
91
|
-
<div key={i} className="flex items-center justify-between p-4 border rounded-
|
|
91
|
+
<div key={i} className="flex items-center justify-between p-4 border rounded-sm">
|
|
92
92
|
<div className="space-y-2 flex-1">
|
|
93
93
|
<Skeleton className="h-4 w-32" />
|
|
94
94
|
<Skeleton className="h-3 w-48" />
|
|
@@ -131,7 +131,7 @@ export const ApiKeysList: React.FC = () => {
|
|
|
131
131
|
{keysList.map((key) => (
|
|
132
132
|
<div
|
|
133
133
|
key={key.id}
|
|
134
|
-
className="flex items-center justify-between p-4 border rounded-
|
|
134
|
+
className="flex items-center justify-between p-4 border rounded-sm hover:bg-accent/50 transition-colors"
|
|
135
135
|
>
|
|
136
136
|
<div className="flex-1 min-w-0">
|
|
137
137
|
<div className="flex items-center gap-3 mb-2">
|
|
@@ -16,15 +16,15 @@ import {
|
|
|
16
16
|
Skeleton,
|
|
17
17
|
} from '@djangocfg/ui';
|
|
18
18
|
import { Wallet, RefreshCw, Plus } from 'lucide-react';
|
|
19
|
-
import {
|
|
19
|
+
import { useOverviewContext } from '@djangocfg/api/cfg/contexts';
|
|
20
20
|
import { openCreatePaymentDialog } from '../../../events';
|
|
21
21
|
|
|
22
22
|
export const BalanceCard: React.FC = () => {
|
|
23
|
-
const {
|
|
24
|
-
balanceSummary,
|
|
25
|
-
isLoadingSummary,
|
|
26
|
-
refreshSummary
|
|
27
|
-
} =
|
|
23
|
+
const {
|
|
24
|
+
balanceSummary,
|
|
25
|
+
isLoadingSummary,
|
|
26
|
+
refreshSummary
|
|
27
|
+
} = useOverviewContext();
|
|
28
28
|
|
|
29
29
|
const formatCurrency = (amount?: number | null) => {
|
|
30
30
|
if (amount === null || amount === undefined) return '$0.00';
|
|
@@ -72,7 +72,7 @@ export const RecentPayments: React.FC = () => {
|
|
|
72
72
|
</CardHeader>
|
|
73
73
|
<CardContent className="space-y-3">
|
|
74
74
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
75
|
-
<div key={i} className="flex items-center justify-between p-3 border rounded-
|
|
75
|
+
<div key={i} className="flex items-center justify-between p-3 border rounded-sm">
|
|
76
76
|
<div className="space-y-2">
|
|
77
77
|
<Skeleton className="h-4 w-32" />
|
|
78
78
|
<Skeleton className="h-3 w-24" />
|
|
@@ -112,7 +112,7 @@ export const RecentPayments: React.FC = () => {
|
|
|
112
112
|
{payments.map((payment) => (
|
|
113
113
|
<div
|
|
114
114
|
key={payment.id}
|
|
115
|
-
className="flex items-center justify-between p-3 border rounded-
|
|
115
|
+
className="flex items-center justify-between p-3 border rounded-sm hover:bg-accent cursor-pointer transition-colors"
|
|
116
116
|
onClick={() => openPaymentDetailsDialog(payment.id)}
|
|
117
117
|
>
|
|
118
118
|
<div className="flex-1">
|
|
@@ -29,7 +29,8 @@ 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
|
|
32
|
+
import { openCreatePaymentDialog } from '../../../events';
|
|
33
|
+
import { openPaymentDetails } from '../../../components/PaymentDetailsDialog';
|
|
33
34
|
|
|
34
35
|
export const PaymentsList: React.FC = () => {
|
|
35
36
|
const {
|
|
@@ -155,7 +156,7 @@ export const PaymentsList: React.FC = () => {
|
|
|
155
156
|
{isLoadingPayments ? (
|
|
156
157
|
<div className="space-y-3">
|
|
157
158
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
158
|
-
<div key={i} className="flex items-center justify-between p-4 border rounded-
|
|
159
|
+
<div key={i} className="flex items-center justify-between p-4 border rounded-sm">
|
|
159
160
|
<div className="space-y-2">
|
|
160
161
|
<Skeleton className="h-4 w-32" />
|
|
161
162
|
<Skeleton className="h-3 w-24" />
|
|
@@ -197,7 +198,11 @@ export const PaymentsList: React.FC = () => {
|
|
|
197
198
|
</TableHeader>
|
|
198
199
|
<TableBody>
|
|
199
200
|
{paymentsList.map((payment) => (
|
|
200
|
-
<TableRow
|
|
201
|
+
<TableRow
|
|
202
|
+
key={payment.id}
|
|
203
|
+
className="cursor-pointer hover:bg-accent"
|
|
204
|
+
onClick={() => openPaymentDetails(payment.id)}
|
|
205
|
+
>
|
|
201
206
|
<TableCell>
|
|
202
207
|
<div>
|
|
203
208
|
<div className="font-medium">
|
|
@@ -229,7 +234,7 @@ export const PaymentsList: React.FC = () => {
|
|
|
229
234
|
size="sm"
|
|
230
235
|
onClick={(e) => {
|
|
231
236
|
e.stopPropagation();
|
|
232
|
-
|
|
237
|
+
openPaymentDetails(payment.id);
|
|
233
238
|
}}
|
|
234
239
|
>
|
|
235
240
|
<ExternalLink className="h-4 w-4" />
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api,
|
|
7
|
+
import { api, Fetchers, CfgSupportTypes } from '@djangocfg/api';
|
|
8
8
|
import type { API } from '@djangocfg/api';
|
|
9
|
-
import { CfgSupportTypes } from '@djangocfg/api';
|
|
10
9
|
|
|
11
10
|
type PaginatedMessageList = CfgSupportTypes.PaginatedMessageList;
|
|
12
11
|
type Message = CfgSupportTypes.Message;
|
|
@@ -41,7 +40,7 @@ export function useInfiniteMessages(ticketUuid: string | null): UseInfiniteMessa
|
|
|
41
40
|
};
|
|
42
41
|
|
|
43
42
|
const fetcher = async ([, ticket_uuid, page, pageSize]: [string, string, number, number]) => {
|
|
44
|
-
return getSupportTicketsMessagesList(
|
|
43
|
+
return Fetchers.getSupportTicketsMessagesList(
|
|
45
44
|
ticket_uuid,
|
|
46
45
|
{ page, page_size: pageSize },
|
|
47
46
|
api as unknown as API
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api,
|
|
7
|
+
import { api, Fetchers, CfgSupportTypes } from '@djangocfg/api';
|
|
8
8
|
import type { API } from '@djangocfg/api';
|
|
9
|
-
import { CfgSupportTypes } from '@djangocfg/api';
|
|
10
9
|
|
|
11
10
|
type PaginatedTicketList = CfgSupportTypes.PaginatedTicketList;
|
|
12
11
|
type Ticket = CfgSupportTypes.Ticket;
|
|
@@ -37,7 +36,7 @@ export function useInfiniteTickets(): UseInfiniteTicketsReturn {
|
|
|
37
36
|
};
|
|
38
37
|
|
|
39
38
|
const fetcher = async ([, page, pageSize]: [string, number, number]) => {
|
|
40
|
-
return getSupportTicketsList(
|
|
39
|
+
return Fetchers.getSupportTicketsList(
|
|
41
40
|
{ page, page_size: pageSize },
|
|
42
41
|
api as unknown as API
|
|
43
42
|
);
|
|
@@ -102,7 +102,7 @@ export const SessionList: React.FC<SessionListProps> = ({
|
|
|
102
102
|
{sessions.map((session, index) => (
|
|
103
103
|
<div
|
|
104
104
|
key={session.id}
|
|
105
|
-
className="group relative flex items-start gap-3 p-4 border rounded-
|
|
105
|
+
className="group relative flex items-start gap-3 p-4 border rounded-sm
|
|
106
106
|
hover:bg-muted/50 hover:border-primary/50 hover:shadow-md
|
|
107
107
|
transition-all duration-200 cursor-pointer
|
|
108
108
|
animate-in fade-in slide-in-from-left-2"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import useSWRInfinite from 'swr/infinite';
|
|
7
|
-
import { api,
|
|
7
|
+
import { api, Fetchers, CfgKnowbaseTypes } from '@djangocfg/api';
|
|
8
8
|
import type { API } from '@djangocfg/api';
|
|
9
9
|
|
|
10
10
|
type PaginatedChatSessionList = CfgKnowbaseTypes.PaginatedChatSessionList;
|
|
@@ -25,7 +25,7 @@ export function useInfiniteSessions() {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const fetcher = async ([, page, pageSize]: [string, number, number]) => {
|
|
28
|
-
return getKnowbaseAdminSessionsList(
|
|
28
|
+
return Fetchers.getKnowbaseAdminSessionsList(
|
|
29
29
|
{ page, page_size: pageSize },
|
|
30
30
|
api as unknown as API
|
|
31
31
|
);
|
|
@@ -71,7 +71,7 @@ export const VideoPlayer = forwardRef<VideoPlayerRef, VideoPlayerProps>(({
|
|
|
71
71
|
{/* Video Player */}
|
|
72
72
|
<div
|
|
73
73
|
className={cn(
|
|
74
|
-
"relative w-full overflow-hidden rounded-
|
|
74
|
+
"relative w-full overflow-hidden rounded-sm bg-black",
|
|
75
75
|
theme === 'minimal' && "rounded-none",
|
|
76
76
|
theme === 'modern' && "rounded-xl shadow-2xl"
|
|
77
77
|
)}
|