@djangocfg/ext-payments 1.0.14 → 1.0.19
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/dist/config.cjs +5 -8
- package/dist/config.js +5 -8
- package/dist/index.cjs +1906 -1043
- package/dist/index.d.cts +644 -59
- package/dist/index.d.ts +644 -59
- package/dist/index.js +1886 -1040
- package/package.json +13 -16
- package/src/WalletPage.tsx +100 -0
- package/src/api/generated/ext_payments/CLAUDE.md +10 -4
- package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +268 -5
- package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +102 -3
- package/src/api/generated/ext_payments/_utils/schemas/Balance.schema.ts +1 -1
- package/src/api/generated/ext_payments/_utils/schemas/PaginatedWithdrawalListList.schema.ts +24 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateRequest.schema.ts +21 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateResponse.schema.ts +22 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentDetail.schema.ts +3 -3
- package/src/api/generated/ext_payments/_utils/schemas/PaymentList.schema.ts +2 -2
- package/src/api/generated/ext_payments/_utils/schemas/Transaction.schema.ts +1 -1
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCancelResponse.schema.ts +22 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateRequest.schema.ts +21 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateResponse.schema.ts +22 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema.ts +42 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalList.schema.ts +29 -0
- package/src/api/generated/ext_payments/_utils/schemas/index.ts +8 -0
- package/src/api/generated/ext_payments/client.ts +1 -1
- package/src/api/generated/ext_payments/enums.ts +36 -0
- package/src/api/generated/ext_payments/ext_payments__payments/client.ts +104 -6
- package/src/api/generated/ext_payments/ext_payments__payments/models.ts +168 -8
- package/src/api/generated/ext_payments/index.ts +1 -1
- package/src/api/generated/ext_payments/schema.json +752 -42
- package/src/components/ActivityItem.tsx +118 -0
- package/src/components/ActivityList.tsx +93 -0
- package/src/components/AddFundsSheet.tsx +342 -0
- package/src/components/BalanceHero.tsx +102 -0
- package/src/components/CurrencyCombobox.tsx +49 -0
- package/src/components/PaymentSheet.tsx +352 -0
- package/src/components/WithdrawSheet.tsx +355 -0
- package/src/components/WithdrawalSheet.tsx +332 -0
- package/src/components/index.ts +11 -0
- package/src/config.ts +1 -0
- package/src/contexts/WalletContext.tsx +356 -0
- package/src/contexts/index.ts +13 -42
- package/src/contexts/types.ts +43 -37
- package/src/hooks/index.ts +3 -20
- package/src/hooks/useCurrencyOptions.ts +79 -0
- package/src/hooks/useEstimate.ts +113 -0
- package/src/hooks/useWithdrawalEstimate.ts +117 -0
- package/src/index.ts +9 -18
- package/src/types/index.ts +78 -0
- package/src/utils/errors.ts +36 -0
- package/src/utils/format.ts +65 -0
- package/src/utils/index.ts +3 -0
- package/src/contexts/BalancesContext.tsx +0 -63
- package/src/contexts/CurrenciesContext.tsx +0 -64
- package/src/contexts/OverviewContext.tsx +0 -173
- package/src/contexts/PaymentsContext.tsx +0 -122
- package/src/contexts/PaymentsExtensionProvider.tsx +0 -56
- package/src/contexts/README.md +0 -201
- package/src/contexts/RootPaymentsContext.tsx +0 -66
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -90
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -274
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -287
- 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 -121
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -139
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -21
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -279
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -18
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -260
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -18
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* Payments Layout (v2.0 - Simplified)
|
|
4
|
-
*
|
|
5
|
-
* Simplified layout with 3 tabs: Overview, Payments, Transactions
|
|
6
|
-
* Removed: API Keys, Tariffs (deprecated in v2.0)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
'use client';
|
|
10
|
-
|
|
11
|
-
import { CreditCard, History, Wallet } from 'lucide-react';
|
|
12
|
-
import React from 'react';
|
|
13
|
-
|
|
14
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui-core';
|
|
15
|
-
|
|
16
|
-
import { OverviewProvider, PaymentsProvider, RootPaymentsProvider } from '../../contexts';
|
|
17
|
-
import { CreatePaymentDialog, PaymentDetailsDialog } from './components';
|
|
18
|
-
import { OverviewView } from './views/overview';
|
|
19
|
-
import { PaymentsView } from './views/payments';
|
|
20
|
-
import { TransactionsView } from './views/transactions';
|
|
21
|
-
|
|
22
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
23
|
-
// Payments Layout
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export interface PaymentsLayoutProps {
|
|
27
|
-
children?: React.ReactNode;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const PaymentsLayout: React.FC<PaymentsLayoutProps> = () => {
|
|
31
|
-
return (
|
|
32
|
-
<RootPaymentsProvider>
|
|
33
|
-
<div className="h-full p-6 space-y-6">
|
|
34
|
-
{/* Page Header */}
|
|
35
|
-
<div>
|
|
36
|
-
<h1 className="text-3xl font-bold tracking-tight">Payments</h1>
|
|
37
|
-
<p className="text-muted-foreground">
|
|
38
|
-
Manage your payments, balance, and transaction history
|
|
39
|
-
</p>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
{/* Main Content with Tabs */}
|
|
43
|
-
<Tabs defaultValue="overview" className="space-y-6">
|
|
44
|
-
<TabsList className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
|
|
45
|
-
<TabsTrigger value="overview" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
46
|
-
<Wallet className="h-4 w-4" />
|
|
47
|
-
<span className="hidden sm:inline">Overview</span>
|
|
48
|
-
</TabsTrigger>
|
|
49
|
-
<TabsTrigger value="payments" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
50
|
-
<CreditCard className="h-4 w-4" />
|
|
51
|
-
<span className="hidden sm:inline">Payments</span>
|
|
52
|
-
</TabsTrigger>
|
|
53
|
-
<TabsTrigger value="transactions" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
54
|
-
<History className="h-4 w-4" />
|
|
55
|
-
<span className="hidden sm:inline">Transactions</span>
|
|
56
|
-
</TabsTrigger>
|
|
57
|
-
</TabsList>
|
|
58
|
-
|
|
59
|
-
{/* Overview Tab - Balance + Recent Payments */}
|
|
60
|
-
<TabsContent value="overview" className="space-y-6">
|
|
61
|
-
<OverviewProvider>
|
|
62
|
-
<PaymentsProvider>
|
|
63
|
-
<OverviewView />
|
|
64
|
-
<CreatePaymentDialog />
|
|
65
|
-
</PaymentsProvider>
|
|
66
|
-
</OverviewProvider>
|
|
67
|
-
</TabsContent>
|
|
68
|
-
|
|
69
|
-
{/* Payments Tab - Full Payment List */}
|
|
70
|
-
<TabsContent value="payments" className="space-y-6">
|
|
71
|
-
<PaymentsProvider>
|
|
72
|
-
<PaymentsView />
|
|
73
|
-
<CreatePaymentDialog />
|
|
74
|
-
</PaymentsProvider>
|
|
75
|
-
</TabsContent>
|
|
76
|
-
|
|
77
|
-
{/* Transactions Tab - Transaction History */}
|
|
78
|
-
<TabsContent value="transactions" className="space-y-6">
|
|
79
|
-
<OverviewProvider>
|
|
80
|
-
<TransactionsView />
|
|
81
|
-
</OverviewProvider>
|
|
82
|
-
</TabsContent>
|
|
83
|
-
</Tabs>
|
|
84
|
-
|
|
85
|
-
{/* Global Payment Details Dialog */}
|
|
86
|
-
<PaymentDetailsDialog />
|
|
87
|
-
</div>
|
|
88
|
-
</RootPaymentsProvider>
|
|
89
|
-
);
|
|
90
|
-
};
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* Create Payment Dialog (v2.0 - Simplified)
|
|
4
|
-
* Dialog for creating new payments
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use client';
|
|
8
|
-
|
|
9
|
-
import { Plus, RefreshCw } from 'lucide-react';
|
|
10
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
11
|
-
import { useForm } from 'react-hook-form';
|
|
12
|
-
import { z } from 'zod';
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Form,
|
|
16
|
-
FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Input, Select,
|
|
17
|
-
SelectContent, SelectItem, SelectTrigger, SelectValue, TokenIcon
|
|
18
|
-
} from '@djangocfg/ui-core';
|
|
19
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
20
|
-
|
|
21
|
-
import { usePaymentsContext, useRootPaymentsContext } from '../../../contexts';
|
|
22
|
-
import { paymentsLogger } from '../../../utils/logger';
|
|
23
|
-
import { closePaymentsDialog, openPaymentDetailsDialog, PAYMENT_EVENTS } from '../events';
|
|
24
|
-
|
|
25
|
-
// Payment creation schema
|
|
26
|
-
const PaymentCreateSchema = z.object({
|
|
27
|
-
amount_usd: z.number().min(0.01, 'Amount must be at least $0.01'),
|
|
28
|
-
currency_code: z.string().min(1, 'Please select a currency'),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
type PaymentCreateRequest = z.infer<typeof PaymentCreateSchema>;
|
|
32
|
-
|
|
33
|
-
export const CreatePaymentDialog: React.FC = () => {
|
|
34
|
-
const [open, setOpen] = useState(false);
|
|
35
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
36
|
-
|
|
37
|
-
const { createPayment } = usePaymentsContext();
|
|
38
|
-
const { currencies, isLoadingCurrencies } = useRootPaymentsContext();
|
|
39
|
-
|
|
40
|
-
const form = useForm<PaymentCreateRequest>({
|
|
41
|
-
resolver: zodResolver(PaymentCreateSchema),
|
|
42
|
-
defaultValues: {
|
|
43
|
-
amount_usd: 10,
|
|
44
|
-
currency_code: 'USDT',
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Extract currencies list from response (handle different possible structures)
|
|
49
|
-
const currenciesList = useMemo(() => {
|
|
50
|
-
const data = currencies?.currencies || currencies?.results || currencies || [];
|
|
51
|
-
return Array.isArray(data) ? data : [];
|
|
52
|
-
}, [currencies]);
|
|
53
|
-
|
|
54
|
-
// Get currency options for select
|
|
55
|
-
const currencyOptions = useMemo(() => {
|
|
56
|
-
return currenciesList
|
|
57
|
-
.filter((curr: any) => curr.is_enabled !== false)
|
|
58
|
-
.map((curr: any) => ({
|
|
59
|
-
code: curr.code || curr.currency_code || curr.symbol,
|
|
60
|
-
name: curr.name || curr.code || curr.currency_code,
|
|
61
|
-
usd_rate: curr.usd_rate || curr.rate || 1,
|
|
62
|
-
network: curr.network || null,
|
|
63
|
-
}));
|
|
64
|
-
}, [currenciesList]);
|
|
65
|
-
|
|
66
|
-
// Calculate crypto amount from USD
|
|
67
|
-
const calculateCryptoAmount = useMemo(() => {
|
|
68
|
-
const amountUsd = form.watch('amount_usd');
|
|
69
|
-
const currencyCode = form.watch('currency_code');
|
|
70
|
-
const currency = currencyOptions.find((c: any) => c.code === currencyCode);
|
|
71
|
-
|
|
72
|
-
if (!currency || !currency.usd_rate || !amountUsd) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const cryptoAmount = amountUsd / currency.usd_rate;
|
|
77
|
-
return {
|
|
78
|
-
amount: cryptoAmount,
|
|
79
|
-
currency: currency.code,
|
|
80
|
-
rate: currency.usd_rate,
|
|
81
|
-
network: currency.network,
|
|
82
|
-
};
|
|
83
|
-
}, [form.watch('amount_usd'), form.watch('currency_code'), currencyOptions]);
|
|
84
|
-
|
|
85
|
-
// Listen for open/close events
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
const handleOpen = () => setOpen(true);
|
|
88
|
-
const handleClose = () => setOpen(false);
|
|
89
|
-
|
|
90
|
-
window.addEventListener(PAYMENT_EVENTS.OPEN_CREATE_PAYMENT_DIALOG, handleOpen);
|
|
91
|
-
window.addEventListener(PAYMENT_EVENTS.CLOSE_DIALOG, handleClose);
|
|
92
|
-
|
|
93
|
-
return () => {
|
|
94
|
-
window.removeEventListener(PAYMENT_EVENTS.OPEN_CREATE_PAYMENT_DIALOG, handleOpen);
|
|
95
|
-
window.removeEventListener(PAYMENT_EVENTS.CLOSE_DIALOG, handleClose);
|
|
96
|
-
};
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
|
-
const handleClose = () => {
|
|
100
|
-
setOpen(false);
|
|
101
|
-
form.reset();
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Initialize default currency if not set
|
|
105
|
-
useEffect(() => {
|
|
106
|
-
if (currencyOptions.length > 0 && !form.getValues('currency_code')) {
|
|
107
|
-
form.setValue('currency_code', currencyOptions[0].code);
|
|
108
|
-
}
|
|
109
|
-
}, [currencyOptions, form]);
|
|
110
|
-
|
|
111
|
-
const handleSubmit = async (data: PaymentCreateRequest) => {
|
|
112
|
-
try {
|
|
113
|
-
setIsSubmitting(true);
|
|
114
|
-
|
|
115
|
-
const result = await createPayment();
|
|
116
|
-
handleClose();
|
|
117
|
-
closePaymentsDialog();
|
|
118
|
-
|
|
119
|
-
// Extract payment ID from result
|
|
120
|
-
const paymentData = result as any;
|
|
121
|
-
const paymentId = paymentData?.payment?.id || paymentData?.id;
|
|
122
|
-
|
|
123
|
-
if (paymentId) {
|
|
124
|
-
openPaymentDetailsDialog(String(paymentId));
|
|
125
|
-
}
|
|
126
|
-
} catch (error) {
|
|
127
|
-
paymentsLogger.error('Failed to create payment:', error);
|
|
128
|
-
} finally {
|
|
129
|
-
setIsSubmitting(false);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
135
|
-
<DialogContent className="sm:max-w-md">
|
|
136
|
-
<DialogHeader>
|
|
137
|
-
<DialogTitle>Create Payment</DialogTitle>
|
|
138
|
-
<DialogDescription>
|
|
139
|
-
Create a new payment to add funds to your account.
|
|
140
|
-
</DialogDescription>
|
|
141
|
-
</DialogHeader>
|
|
142
|
-
|
|
143
|
-
<Form {...form}>
|
|
144
|
-
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
|
145
|
-
<FormField
|
|
146
|
-
control={form.control}
|
|
147
|
-
name="amount_usd"
|
|
148
|
-
render={({ field }) => (
|
|
149
|
-
<FormItem>
|
|
150
|
-
<FormLabel>Amount (USD)</FormLabel>
|
|
151
|
-
<FormControl>
|
|
152
|
-
<Input
|
|
153
|
-
type="number"
|
|
154
|
-
step="0.01"
|
|
155
|
-
min="0.01"
|
|
156
|
-
placeholder="10.00"
|
|
157
|
-
{...field}
|
|
158
|
-
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
|
159
|
-
/>
|
|
160
|
-
</FormControl>
|
|
161
|
-
<FormDescription>
|
|
162
|
-
The amount you want to pay in USD.
|
|
163
|
-
</FormDescription>
|
|
164
|
-
<FormMessage />
|
|
165
|
-
</FormItem>
|
|
166
|
-
)}
|
|
167
|
-
/>
|
|
168
|
-
|
|
169
|
-
<FormField
|
|
170
|
-
control={form.control}
|
|
171
|
-
name="currency_code"
|
|
172
|
-
render={({ field }) => (
|
|
173
|
-
<FormItem>
|
|
174
|
-
<FormLabel>Currency</FormLabel>
|
|
175
|
-
<Select
|
|
176
|
-
onValueChange={field.onChange}
|
|
177
|
-
defaultValue={field.value}
|
|
178
|
-
disabled={isLoadingCurrencies}
|
|
179
|
-
>
|
|
180
|
-
<FormControl>
|
|
181
|
-
<SelectTrigger>
|
|
182
|
-
<SelectValue placeholder="Select currency..." />
|
|
183
|
-
</SelectTrigger>
|
|
184
|
-
</FormControl>
|
|
185
|
-
<SelectContent>
|
|
186
|
-
{currencyOptions.map((curr: any) => (
|
|
187
|
-
<SelectItem key={curr.code} value={curr.code}>
|
|
188
|
-
<div className="flex items-center gap-2">
|
|
189
|
-
<TokenIcon symbol={curr.code} size={16} />
|
|
190
|
-
<span>{curr.code}</span>
|
|
191
|
-
{curr.network && (
|
|
192
|
-
<span className="text-xs text-muted-foreground">
|
|
193
|
-
({curr.network})
|
|
194
|
-
</span>
|
|
195
|
-
)}
|
|
196
|
-
</div>
|
|
197
|
-
</SelectItem>
|
|
198
|
-
))}
|
|
199
|
-
</SelectContent>
|
|
200
|
-
</Select>
|
|
201
|
-
<FormDescription>
|
|
202
|
-
The cryptocurrency to use for payment.
|
|
203
|
-
</FormDescription>
|
|
204
|
-
<FormMessage />
|
|
205
|
-
</FormItem>
|
|
206
|
-
)}
|
|
207
|
-
/>
|
|
208
|
-
|
|
209
|
-
{/* Conversion Information */}
|
|
210
|
-
{calculateCryptoAmount && (
|
|
211
|
-
<div className="rounded-sm bg-muted p-4 space-y-3">
|
|
212
|
-
{/* Amount to Send in Crypto */}
|
|
213
|
-
<div className="flex items-center justify-between">
|
|
214
|
-
<span className="text-sm text-muted-foreground">You will send</span>
|
|
215
|
-
<div className="flex items-center gap-2">
|
|
216
|
-
<TokenIcon symbol={calculateCryptoAmount.currency} size={16} />
|
|
217
|
-
<span className="font-mono font-semibold">
|
|
218
|
-
{calculateCryptoAmount.amount.toFixed(8)} {calculateCryptoAmount.currency}
|
|
219
|
-
</span>
|
|
220
|
-
</div>
|
|
221
|
-
</div>
|
|
222
|
-
|
|
223
|
-
{/* USD Amount Received */}
|
|
224
|
-
<div className="flex items-center justify-between">
|
|
225
|
-
<span className="text-sm text-muted-foreground">You will receive</span>
|
|
226
|
-
<span className="text-lg font-bold">
|
|
227
|
-
${form.watch('amount_usd')?.toFixed(2)} USD
|
|
228
|
-
</span>
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
|
-
{/* Exchange Rate */}
|
|
232
|
-
<div className="flex items-center justify-between text-xs">
|
|
233
|
-
<span className="text-muted-foreground">Rate</span>
|
|
234
|
-
<span className="font-medium">
|
|
235
|
-
1 {calculateCryptoAmount.currency} = ${calculateCryptoAmount.rate?.toFixed(2)}
|
|
236
|
-
</span>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
{/* Network Info */}
|
|
240
|
-
{calculateCryptoAmount.network && (
|
|
241
|
-
<div className="border-t pt-3">
|
|
242
|
-
<div className="flex items-center justify-between">
|
|
243
|
-
<span className="text-sm text-muted-foreground">Network</span>
|
|
244
|
-
<span className="text-sm font-medium">{calculateCryptoAmount.network}</span>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
)}
|
|
248
|
-
</div>
|
|
249
|
-
)}
|
|
250
|
-
|
|
251
|
-
<DialogFooter>
|
|
252
|
-
<Button type="button" variant="outline" onClick={handleClose} disabled={isSubmitting}>
|
|
253
|
-
Cancel
|
|
254
|
-
</Button>
|
|
255
|
-
<Button type="submit" disabled={isSubmitting || currencyOptions.length === 0}>
|
|
256
|
-
{isSubmitting ? (
|
|
257
|
-
<>
|
|
258
|
-
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
|
259
|
-
Creating...
|
|
260
|
-
</>
|
|
261
|
-
) : (
|
|
262
|
-
<>
|
|
263
|
-
<Plus className="h-4 w-4 mr-2" />
|
|
264
|
-
Create Payment
|
|
265
|
-
</>
|
|
266
|
-
)}
|
|
267
|
-
</Button>
|
|
268
|
-
</DialogFooter>
|
|
269
|
-
</form>
|
|
270
|
-
</Form>
|
|
271
|
-
</DialogContent>
|
|
272
|
-
</Dialog>
|
|
273
|
-
);
|
|
274
|
-
};
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Payment Details Dialog (v2.0 - Simplified)
|
|
3
|
-
* Shows payment details with QR code, address, and status
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import { AlertCircle, CheckCircle2, Clock, ExternalLink, RefreshCw, XCircle } from 'lucide-react';
|
|
9
|
-
import moment from 'moment';
|
|
10
|
-
import React, { useEffect, useState } from 'react';
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
Button, CopyButton, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader,
|
|
14
|
-
DialogTitle, TokenIcon
|
|
15
|
-
} from '@djangocfg/ui-core';
|
|
16
|
-
|
|
17
|
-
import { apiPayments } from '../../../api';
|
|
18
|
-
import { usePaymentsPaymentsRetrieve } from '../../../api/generated/ext_payments/_utils/hooks';
|
|
19
|
-
import { PAYMENT_EVENTS } from '../events';
|
|
20
|
-
|
|
21
|
-
export const PaymentDetailsDialog: React.FC = () => {
|
|
22
|
-
const [open, setOpen] = useState(false);
|
|
23
|
-
const [paymentId, setPaymentId] = useState<string | null>(null);
|
|
24
|
-
const [timeLeft, setTimeLeft] = useState<string>('');
|
|
25
|
-
|
|
26
|
-
// Load payment data by ID using hook
|
|
27
|
-
const shouldFetch = open && !!paymentId;
|
|
28
|
-
const { data: payment, isLoading, error, mutate } = usePaymentsPaymentsRetrieve(
|
|
29
|
-
shouldFetch ? paymentId : '',
|
|
30
|
-
apiPayments
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// Listen for open/close events
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const handleOpen = (event: Event) => {
|
|
36
|
-
const customEvent = event as CustomEvent<{ id: string }>;
|
|
37
|
-
setPaymentId(customEvent.detail.id);
|
|
38
|
-
setOpen(true);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const handleClose = () => {
|
|
42
|
-
setOpen(false);
|
|
43
|
-
setPaymentId(null);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
window.addEventListener(PAYMENT_EVENTS.OPEN_PAYMENT_DETAILS_DIALOG, handleOpen);
|
|
47
|
-
window.addEventListener(PAYMENT_EVENTS.CLOSE_DIALOG, handleClose);
|
|
48
|
-
|
|
49
|
-
return () => {
|
|
50
|
-
window.removeEventListener(PAYMENT_EVENTS.OPEN_PAYMENT_DETAILS_DIALOG, handleOpen);
|
|
51
|
-
window.removeEventListener(PAYMENT_EVENTS.CLOSE_DIALOG, handleClose);
|
|
52
|
-
};
|
|
53
|
-
}, []);
|
|
54
|
-
|
|
55
|
-
const handleClose = () => {
|
|
56
|
-
setOpen(false);
|
|
57
|
-
setPaymentId(null);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Calculate time left until expiration
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (!payment?.expires_at) return;
|
|
63
|
-
|
|
64
|
-
const updateTimeLeft = () => {
|
|
65
|
-
const now = moment();
|
|
66
|
-
const expires = moment.utc(payment.expires_at!);
|
|
67
|
-
const diff = expires.diff(now);
|
|
68
|
-
|
|
69
|
-
if (diff <= 0) {
|
|
70
|
-
setTimeLeft('Expired');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const duration = moment.duration(diff);
|
|
75
|
-
const hours = Math.floor(duration.asHours());
|
|
76
|
-
const minutes = duration.minutes();
|
|
77
|
-
const seconds = duration.seconds();
|
|
78
|
-
|
|
79
|
-
setTimeLeft(`${hours}h ${minutes}m ${seconds}s`);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
updateTimeLeft();
|
|
83
|
-
const interval = setInterval(updateTimeLeft, 1000);
|
|
84
|
-
|
|
85
|
-
return () => clearInterval(interval);
|
|
86
|
-
}, [payment?.expires_at]);
|
|
87
|
-
|
|
88
|
-
// Get status icon and color
|
|
89
|
-
const getStatusInfo = () => {
|
|
90
|
-
switch (payment?.status?.toLowerCase()) {
|
|
91
|
-
case 'pending':
|
|
92
|
-
return { icon: Clock, color: 'text-yellow-500', bg: 'bg-yellow-500/10' };
|
|
93
|
-
case 'completed':
|
|
94
|
-
case 'success':
|
|
95
|
-
return { icon: CheckCircle2, color: 'text-green-500', bg: 'bg-green-500/10' };
|
|
96
|
-
case 'failed':
|
|
97
|
-
case 'error':
|
|
98
|
-
return { icon: XCircle, color: 'text-red-500', bg: 'bg-red-500/10' };
|
|
99
|
-
case 'expired':
|
|
100
|
-
return { icon: AlertCircle, color: 'text-gray-500', bg: 'bg-gray-500/10' };
|
|
101
|
-
case 'confirming':
|
|
102
|
-
return { icon: RefreshCw, color: 'text-blue-500', bg: 'bg-blue-500/10' };
|
|
103
|
-
default:
|
|
104
|
-
return { icon: Clock, color: 'text-gray-500', bg: 'bg-gray-500/10' };
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
if (!open) return null;
|
|
109
|
-
|
|
110
|
-
// Loading state
|
|
111
|
-
if (isLoading) {
|
|
112
|
-
return (
|
|
113
|
-
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
114
|
-
<DialogContent className="sm:max-w-lg">
|
|
115
|
-
<DialogHeader>
|
|
116
|
-
<DialogTitle>Payment Details</DialogTitle>
|
|
117
|
-
<DialogDescription>Loading payment information...</DialogDescription>
|
|
118
|
-
</DialogHeader>
|
|
119
|
-
<div className="flex items-center justify-center py-12">
|
|
120
|
-
<RefreshCw className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
121
|
-
</div>
|
|
122
|
-
</DialogContent>
|
|
123
|
-
</Dialog>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Error state
|
|
128
|
-
if (shouldFetch && !isLoading && (error || !payment)) {
|
|
129
|
-
return (
|
|
130
|
-
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
131
|
-
<DialogContent className="sm:max-w-lg">
|
|
132
|
-
<DialogHeader>
|
|
133
|
-
<DialogTitle>Payment Details</DialogTitle>
|
|
134
|
-
<DialogDescription>Failed to load payment information</DialogDescription>
|
|
135
|
-
</DialogHeader>
|
|
136
|
-
<div className="flex flex-col items-center justify-center py-12 space-y-4">
|
|
137
|
-
<XCircle className="h-12 w-12 text-destructive" />
|
|
138
|
-
<p className="text-sm text-muted-foreground">
|
|
139
|
-
{error ? `Error: ${error}` : 'Payment not found'}
|
|
140
|
-
</p>
|
|
141
|
-
<Button onClick={() => mutate()}>Try Again</Button>
|
|
142
|
-
</div>
|
|
143
|
-
</DialogContent>
|
|
144
|
-
</Dialog>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const statusInfo = getStatusInfo();
|
|
149
|
-
const StatusIcon = statusInfo.icon;
|
|
150
|
-
|
|
151
|
-
// Generate QR code URL
|
|
152
|
-
const qrCodeUrl = payment.pay_address
|
|
153
|
-
? `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(payment.pay_address)}`
|
|
154
|
-
: null;
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
158
|
-
<DialogContent className="sm:max-w-lg">
|
|
159
|
-
<DialogHeader>
|
|
160
|
-
<DialogTitle>Payment Details</DialogTitle>
|
|
161
|
-
<DialogDescription>
|
|
162
|
-
Send cryptocurrency to complete your payment
|
|
163
|
-
</DialogDescription>
|
|
164
|
-
</DialogHeader>
|
|
165
|
-
|
|
166
|
-
<div className="space-y-6">
|
|
167
|
-
{/* Status Badge */}
|
|
168
|
-
<div className={`flex items-center gap-3 p-4 rounded-sm ${statusInfo.bg}`}>
|
|
169
|
-
<StatusIcon className={`h-5 w-5 ${statusInfo.color}`} />
|
|
170
|
-
<div className="flex-1">
|
|
171
|
-
<div className="font-semibold capitalize">{payment.status}</div>
|
|
172
|
-
{payment.status === 'pending' && timeLeft && (
|
|
173
|
-
<div className="text-sm text-muted-foreground">
|
|
174
|
-
Expires in {timeLeft}
|
|
175
|
-
</div>
|
|
176
|
-
)}
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
|
|
180
|
-
{/* Amount Information */}
|
|
181
|
-
<div className="space-y-3">
|
|
182
|
-
<div className="flex items-center justify-between p-4 bg-muted rounded-sm">
|
|
183
|
-
<span className="text-sm text-muted-foreground">Amount to send</span>
|
|
184
|
-
<div className="flex items-center gap-2">
|
|
185
|
-
<TokenIcon symbol={String(payment.currency_code || 'BTC')} size={20} />
|
|
186
|
-
<span className="font-mono font-bold text-lg">
|
|
187
|
-
{payment.pay_amount || '0.00000000'} {payment.currency_code}
|
|
188
|
-
</span>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
<div className="flex items-center justify-between px-4">
|
|
193
|
-
<span className="text-sm text-muted-foreground">Equivalent to</span>
|
|
194
|
-
<span className="font-semibold text-lg">
|
|
195
|
-
${parseFloat(payment.amount_usd || '0').toFixed(2)} USD
|
|
196
|
-
</span>
|
|
197
|
-
</div>
|
|
198
|
-
|
|
199
|
-
{payment.internal_payment_id && (
|
|
200
|
-
<div className="flex items-center justify-between px-4">
|
|
201
|
-
<span className="text-sm text-muted-foreground">Payment Order #</span>
|
|
202
|
-
<span className="font-mono font-medium">{payment.internal_payment_id}</span>
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
|
-
|
|
206
|
-
{payment.currency_network && (
|
|
207
|
-
<div className="flex items-center justify-between px-4">
|
|
208
|
-
<span className="text-sm text-muted-foreground">Network</span>
|
|
209
|
-
<span className="font-medium">{payment.currency_network}</span>
|
|
210
|
-
</div>
|
|
211
|
-
)}
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
{/* QR Code */}
|
|
215
|
-
{qrCodeUrl && payment.status === 'pending' && (
|
|
216
|
-
<div className="flex justify-center p-6 bg-white rounded-sm">
|
|
217
|
-
<img src={qrCodeUrl} alt="Payment QR Code" className="w-48 h-48" />
|
|
218
|
-
</div>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{/* Payment Address */}
|
|
222
|
-
{payment.pay_address && payment.status === 'pending' && (
|
|
223
|
-
<div className="space-y-2">
|
|
224
|
-
<label className="text-sm font-medium">Payment Address</label>
|
|
225
|
-
<div className="flex items-center gap-2">
|
|
226
|
-
<div className="flex-1 p-3 bg-muted rounded-sm font-mono text-sm break-all">
|
|
227
|
-
{payment.pay_address}
|
|
228
|
-
</div>
|
|
229
|
-
<CopyButton value={payment.pay_address} variant="outline" />
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
)}
|
|
233
|
-
|
|
234
|
-
{/* Transaction Hash */}
|
|
235
|
-
{payment.transaction_hash && (
|
|
236
|
-
<div className="space-y-2">
|
|
237
|
-
<label className="text-sm font-medium">Transaction Hash</label>
|
|
238
|
-
<div className="p-3 bg-muted rounded-sm font-mono text-sm break-all">
|
|
239
|
-
{payment.transaction_hash}
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
243
|
-
|
|
244
|
-
{/* Payment URL */}
|
|
245
|
-
{payment.payment_url && payment.status === 'pending' && (
|
|
246
|
-
<Button
|
|
247
|
-
variant="outline"
|
|
248
|
-
className="w-full"
|
|
249
|
-
onClick={() => window.open(payment.payment_url!, '_blank')}
|
|
250
|
-
>
|
|
251
|
-
<ExternalLink className="h-4 w-4 mr-2" />
|
|
252
|
-
Open in Payment Provider
|
|
253
|
-
</Button>
|
|
254
|
-
)}
|
|
255
|
-
|
|
256
|
-
{/* Additional Info */}
|
|
257
|
-
<div className="pt-4 border-t space-y-2 text-xs text-muted-foreground">
|
|
258
|
-
<div className="flex justify-between">
|
|
259
|
-
<span>Payment ID</span>
|
|
260
|
-
<span className="font-mono">{payment.id}</span>
|
|
261
|
-
</div>
|
|
262
|
-
<div className="flex justify-between">
|
|
263
|
-
<span>Created</span>
|
|
264
|
-
<span>{moment.utc(payment.created_at!).local().format('MMM D, YYYY HH:mm')}</span>
|
|
265
|
-
</div>
|
|
266
|
-
{payment.confirmations_count !== undefined && (
|
|
267
|
-
<div className="flex justify-between">
|
|
268
|
-
<span>Confirmations</span>
|
|
269
|
-
<span>{payment.confirmations_count}</span>
|
|
270
|
-
</div>
|
|
271
|
-
)}
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
<DialogFooter>
|
|
276
|
-
<Button variant="outline" onClick={handleClose}>
|
|
277
|
-
Close
|
|
278
|
-
</Button>
|
|
279
|
-
<Button onClick={() => mutate()} variant="ghost" size="sm">
|
|
280
|
-
<RefreshCw className="h-4 w-4 mr-2" />
|
|
281
|
-
Refresh
|
|
282
|
-
</Button>
|
|
283
|
-
</DialogFooter>
|
|
284
|
-
</DialogContent>
|
|
285
|
-
</Dialog>
|
|
286
|
-
);
|
|
287
|
-
};
|