@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
|
@@ -5,46 +5,44 @@
|
|
|
5
5
|
|
|
6
6
|
'use client';
|
|
7
7
|
|
|
8
|
-
import React
|
|
8
|
+
import React from 'react';
|
|
9
9
|
import {
|
|
10
10
|
PaymentsProvider,
|
|
11
|
-
BalancesProvider,
|
|
12
11
|
ApiKeysProvider,
|
|
13
12
|
OverviewProvider,
|
|
13
|
+
RootPaymentsProvider,
|
|
14
14
|
} from '@djangocfg/api/cfg/contexts';
|
|
15
15
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui';
|
|
16
16
|
import { Wallet, CreditCard, History, Key, Crown } from 'lucide-react';
|
|
17
|
-
import type { PaymentTab } from './types';
|
|
18
17
|
import { OverviewView } from './views/overview';
|
|
19
18
|
import { PaymentsView } from './views/payments';
|
|
20
19
|
import { TransactionsView } from './views/transactions';
|
|
21
20
|
import { ApiKeysView } from './views/apikeys';
|
|
22
21
|
import { TariffsView } from './views/tariffs';
|
|
23
|
-
import { CreateApiKeyDialog, DeleteApiKeyDialog } from './components';
|
|
22
|
+
import { CreateApiKeyDialog, DeleteApiKeyDialog, CreatePaymentDialog, PaymentDetailsDialog } from './components';
|
|
24
23
|
|
|
25
24
|
// ─────────────────────────────────────────────────────────────────────────
|
|
26
|
-
// Payments Layout
|
|
25
|
+
// Payments Layout
|
|
27
26
|
// ─────────────────────────────────────────────────────────────────────────
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export interface PaymentsLayoutProps {
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
}
|
|
31
31
|
|
|
32
|
+
export const PaymentsLayout: React.FC<PaymentsLayoutProps> = () => {
|
|
32
33
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
<RootPaymentsProvider>
|
|
35
|
+
<div className="h-full p-6 space-y-6">
|
|
36
|
+
{/* Page Header */}
|
|
37
|
+
<div>
|
|
38
|
+
<h1 className="text-3xl font-bold tracking-tight">Payments & Billing</h1>
|
|
39
|
+
<p className="text-muted-foreground">
|
|
40
|
+
Manage your payments, subscriptions, API keys, and account balance
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
41
43
|
|
|
42
44
|
{/* Main Content with Tabs */}
|
|
43
|
-
<Tabs
|
|
44
|
-
value={activeTab}
|
|
45
|
-
onValueChange={(value) => setActiveTab(value as PaymentTab)}
|
|
46
|
-
className="space-y-6"
|
|
47
|
-
>
|
|
45
|
+
<Tabs defaultValue="overview" className="space-y-6">
|
|
48
46
|
<TabsList className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
|
|
49
47
|
<TabsTrigger value="overview" className="inline-flex items-center gap-2 px-3 py-1.5">
|
|
50
48
|
<Wallet className="h-4 w-4" />
|
|
@@ -68,58 +66,44 @@ const PaymentsLayoutContent: React.FC = () => {
|
|
|
68
66
|
</TabsTrigger>
|
|
69
67
|
</TabsList>
|
|
70
68
|
|
|
71
|
-
{/*
|
|
69
|
+
{/* Each tab wrapped in its own provider - loads only when tab is active */}
|
|
72
70
|
<TabsContent value="overview" className="space-y-6">
|
|
73
|
-
<
|
|
71
|
+
<OverviewProvider>
|
|
72
|
+
<PaymentsProvider>
|
|
73
|
+
<OverviewView />
|
|
74
|
+
<CreatePaymentDialog />
|
|
75
|
+
</PaymentsProvider>
|
|
76
|
+
</OverviewProvider>
|
|
74
77
|
</TabsContent>
|
|
75
78
|
|
|
76
|
-
{/* Payments Tab */}
|
|
77
79
|
<TabsContent value="payments" className="space-y-6">
|
|
78
|
-
<
|
|
80
|
+
<PaymentsProvider>
|
|
81
|
+
<PaymentsView />
|
|
82
|
+
<CreatePaymentDialog />
|
|
83
|
+
</PaymentsProvider>
|
|
79
84
|
</TabsContent>
|
|
80
85
|
|
|
81
|
-
{/* Transactions Tab */}
|
|
82
86
|
<TabsContent value="transactions" className="space-y-6">
|
|
83
87
|
<TransactionsView />
|
|
84
88
|
</TabsContent>
|
|
85
89
|
|
|
86
|
-
{/* API Keys Tab */}
|
|
87
90
|
<TabsContent value="apikeys" className="space-y-6">
|
|
88
|
-
<
|
|
91
|
+
<ApiKeysProvider>
|
|
92
|
+
<ApiKeysView />
|
|
93
|
+
<CreateApiKeyDialog />
|
|
94
|
+
<DeleteApiKeyDialog />
|
|
95
|
+
</ApiKeysProvider>
|
|
89
96
|
</TabsContent>
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</Tabs>
|
|
98
|
+
<TabsContent value="tariffs" className="space-y-6">
|
|
99
|
+
<TariffsView />
|
|
100
|
+
</TabsContent>
|
|
101
|
+
</Tabs>
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<DeleteApiKeyDialog />
|
|
103
|
+
{/* Global Payment Details Dialog */}
|
|
104
|
+
<PaymentDetailsDialog />
|
|
100
105
|
</div>
|
|
101
|
-
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
105
|
-
// Payments Layout (with providers)
|
|
106
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
export interface PaymentsLayoutProps {
|
|
109
|
-
children?: React.ReactNode;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export const PaymentsLayout: React.FC<PaymentsLayoutProps> = () => {
|
|
113
|
-
return (
|
|
114
|
-
<OverviewProvider>
|
|
115
|
-
<PaymentsProvider>
|
|
116
|
-
<BalancesProvider>
|
|
117
|
-
<ApiKeysProvider>
|
|
118
|
-
<PaymentsLayoutContent />
|
|
119
|
-
</ApiKeysProvider>
|
|
120
|
-
</BalancesProvider>
|
|
121
|
-
</PaymentsProvider>
|
|
122
|
-
</OverviewProvider>
|
|
106
|
+
</RootPaymentsProvider>
|
|
123
107
|
);
|
|
124
108
|
};
|
|
125
109
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
'use client';
|
|
7
7
|
|
|
8
|
-
import React, { useState } from 'react';
|
|
8
|
+
import React, { useState, useEffect, useMemo } from 'react';
|
|
9
9
|
import {
|
|
10
10
|
Dialog,
|
|
11
11
|
DialogContent,
|
|
@@ -21,40 +21,108 @@ import {
|
|
|
21
21
|
FormLabel,
|
|
22
22
|
FormMessage,
|
|
23
23
|
Input,
|
|
24
|
-
|
|
25
|
-
SelectContent,
|
|
26
|
-
SelectItem,
|
|
27
|
-
SelectTrigger,
|
|
28
|
-
SelectValue,
|
|
24
|
+
Combobox,
|
|
29
25
|
Button,
|
|
26
|
+
TokenIcon,
|
|
30
27
|
useEventListener,
|
|
31
28
|
} from '@djangocfg/ui';
|
|
32
29
|
import { Plus, RefreshCw } from 'lucide-react';
|
|
33
30
|
import { useForm } from 'react-hook-form';
|
|
34
31
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
35
|
-
import { usePaymentsContext } from '@djangocfg/api/cfg/contexts';
|
|
36
|
-
import { Schemas
|
|
32
|
+
import { usePaymentsContext, useRootPaymentsContext } from '@djangocfg/api/cfg/contexts';
|
|
33
|
+
import { Schemas } from '@djangocfg/api/cfg/generated';
|
|
37
34
|
import { paymentsLogger } from '../../../utils/logger';
|
|
38
35
|
import { PAYMENTS_DIALOG_EVENTS, closePaymentsDialog } from '../events';
|
|
36
|
+
import { openPaymentDetails } from './PaymentDetailsDialog';
|
|
37
|
+
import type { ProviderCurrency } from '@djangocfg/api/cfg/contexts';
|
|
38
|
+
import type { ComboboxOption } from '@djangocfg/ui';
|
|
39
39
|
|
|
40
40
|
const { PaymentCreateRequestSchema } = Schemas;
|
|
41
41
|
type PaymentCreateRequest = Schemas.PaymentCreateRequest;
|
|
42
|
-
const { PaymentCreateRequestCurrencyCode, PaymentCreateRequestProvider } = Enums;
|
|
43
42
|
|
|
44
43
|
export const CreatePaymentDialog: React.FC = () => {
|
|
45
44
|
const [open, setOpen] = useState(false);
|
|
46
45
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
46
|
+
|
|
47
47
|
const { createPayment } = usePaymentsContext();
|
|
48
|
+
const {
|
|
49
|
+
providerCurrencies,
|
|
50
|
+
isLoadingProviderCurrencies,
|
|
51
|
+
} = useRootPaymentsContext();
|
|
48
52
|
|
|
49
53
|
const form = useForm<PaymentCreateRequest>({
|
|
50
54
|
resolver: zodResolver(PaymentCreateRequestSchema),
|
|
51
55
|
defaultValues: {
|
|
52
56
|
amount_usd: 10,
|
|
53
|
-
currency_code:
|
|
54
|
-
provider: PaymentCreateRequestProvider.NOWPAYMENTS,
|
|
57
|
+
currency_code: 'USDT', // Default to USDT
|
|
55
58
|
},
|
|
56
59
|
});
|
|
57
60
|
|
|
61
|
+
// Group currencies by token and create combobox options
|
|
62
|
+
// Backend automatically selects network, so we group by currency code
|
|
63
|
+
const currencyOptions = useMemo((): ComboboxOption[] => {
|
|
64
|
+
if (!providerCurrencies?.results) return [];
|
|
65
|
+
|
|
66
|
+
const enabledCurrencies = providerCurrencies.results.filter(pc => pc.is_enabled);
|
|
67
|
+
|
|
68
|
+
// Group by currency code and collect all networks
|
|
69
|
+
const currencyMap = new Map<string, { pc: ProviderCurrency, networks: string[] }>();
|
|
70
|
+
|
|
71
|
+
for (const pc of enabledCurrencies) {
|
|
72
|
+
const code = pc.currency.code.toUpperCase();
|
|
73
|
+
if (!currencyMap.has(code)) {
|
|
74
|
+
currencyMap.set(code, { pc, networks: [] });
|
|
75
|
+
}
|
|
76
|
+
if (pc.network?.name) {
|
|
77
|
+
currencyMap.get(code)!.networks.push(pc.network.name);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Array.from(currencyMap.values()).map(({ pc, networks }) => {
|
|
82
|
+
// Show network info only if there's exactly one network, otherwise backend will choose
|
|
83
|
+
let label = pc.currency.code;
|
|
84
|
+
let description = pc.currency.name;
|
|
85
|
+
|
|
86
|
+
if (networks.length === 1) {
|
|
87
|
+
label = `${pc.currency.code} (${networks[0]})`;
|
|
88
|
+
} else if (networks.length > 1) {
|
|
89
|
+
description = `${pc.currency.name} • Multiple networks available`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
// Use currency code (e.g. "USDT") as value for API
|
|
94
|
+
value: pc.currency.code.toUpperCase(),
|
|
95
|
+
label,
|
|
96
|
+
description,
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}, [providerCurrencies]);
|
|
100
|
+
|
|
101
|
+
// Get first ProviderCurrency by currency code (e.g. "USDT")
|
|
102
|
+
const getProviderCurrency = (currencyCode: string) => {
|
|
103
|
+
return providerCurrencies?.results?.find(
|
|
104
|
+
pc => pc.is_enabled && pc.currency.code.toUpperCase() === currencyCode.toUpperCase()
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Calculate crypto amount from USD
|
|
109
|
+
const calculateCryptoAmount = useMemo(() => {
|
|
110
|
+
const amountUsd = form.watch('amount_usd');
|
|
111
|
+
const currencyCode = form.watch('currency_code');
|
|
112
|
+
const pc = getProviderCurrency(currencyCode);
|
|
113
|
+
|
|
114
|
+
if (!pc || !pc.currency.usd_rate || !amountUsd) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const cryptoAmount = amountUsd / pc.currency.usd_rate;
|
|
119
|
+
return {
|
|
120
|
+
amount: cryptoAmount,
|
|
121
|
+
currency: pc.currency.code,
|
|
122
|
+
symbol: pc.currency.symbol || pc.currency.code,
|
|
123
|
+
};
|
|
124
|
+
}, [form.watch('amount_usd'), form.watch('currency_code'), providerCurrencies]);
|
|
125
|
+
|
|
58
126
|
useEventListener(PAYMENTS_DIALOG_EVENTS.OPEN_CREATE_PAYMENT_DIALOG, () => {
|
|
59
127
|
setOpen(true);
|
|
60
128
|
});
|
|
@@ -68,12 +136,29 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
68
136
|
form.reset();
|
|
69
137
|
};
|
|
70
138
|
|
|
139
|
+
// Initialize default currency if not set
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (currencyOptions.length > 0 && !form.getValues('currency_code')) {
|
|
142
|
+
form.setValue('currency_code', currencyOptions[0].value as any);
|
|
143
|
+
}
|
|
144
|
+
}, [currencyOptions, form]);
|
|
145
|
+
|
|
71
146
|
const handleSubmit = async (data: PaymentCreateRequest) => {
|
|
72
147
|
try {
|
|
73
148
|
setIsSubmitting(true);
|
|
74
|
-
|
|
149
|
+
|
|
150
|
+
const result = await createPayment(data);
|
|
75
151
|
handleClose();
|
|
76
152
|
closePaymentsDialog();
|
|
153
|
+
|
|
154
|
+
// The API returns a wrapped response with { success, message, payment }
|
|
155
|
+
// Extract the payment ID from the result
|
|
156
|
+
const paymentData = result as any;
|
|
157
|
+
const paymentId = paymentData?.payment?.id || paymentData?.id;
|
|
158
|
+
|
|
159
|
+
if (paymentId) {
|
|
160
|
+
openPaymentDetails(String(paymentId));
|
|
161
|
+
}
|
|
77
162
|
} catch (error) {
|
|
78
163
|
paymentsLogger.error('Failed to create payment:', error);
|
|
79
164
|
} finally {
|
|
@@ -81,11 +166,6 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
81
166
|
}
|
|
82
167
|
};
|
|
83
168
|
|
|
84
|
-
const currencyOptions = Object.entries(PaymentCreateRequestCurrencyCode).map(([key, value]) => ({
|
|
85
|
-
value,
|
|
86
|
-
label: key,
|
|
87
|
-
}));
|
|
88
|
-
|
|
89
169
|
return (
|
|
90
170
|
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && handleClose()}>
|
|
91
171
|
<DialogContent className="sm:max-w-md">
|
|
@@ -128,20 +208,49 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
128
208
|
render={({ field }) => (
|
|
129
209
|
<FormItem>
|
|
130
210
|
<FormLabel>Currency</FormLabel>
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
211
|
+
<FormControl>
|
|
212
|
+
<Combobox
|
|
213
|
+
options={currencyOptions}
|
|
214
|
+
value={field.value}
|
|
215
|
+
onValueChange={field.onChange}
|
|
216
|
+
placeholder="Select currency..."
|
|
217
|
+
searchPlaceholder="Search currencies..."
|
|
218
|
+
emptyText="No currencies found."
|
|
219
|
+
disabled={isLoadingProviderCurrencies}
|
|
220
|
+
className="w-full"
|
|
221
|
+
renderValue={(option) => {
|
|
222
|
+
if (!option) return null;
|
|
223
|
+
const pc = getProviderCurrency(option.value);
|
|
224
|
+
if (!pc) return option.label;
|
|
225
|
+
return (
|
|
226
|
+
<div className="flex items-center gap-2">
|
|
227
|
+
<TokenIcon symbol={pc.currency.code} size={20} />
|
|
228
|
+
<span>{pc.currency.code}</span>
|
|
229
|
+
{pc.network && (
|
|
230
|
+
<span className="text-xs text-muted-foreground">
|
|
231
|
+
({pc.network.name})
|
|
232
|
+
</span>
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}}
|
|
237
|
+
renderOption={(option) => {
|
|
238
|
+
const pc = getProviderCurrency(option.value);
|
|
239
|
+
if (!pc) return option.label;
|
|
240
|
+
return (
|
|
241
|
+
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
242
|
+
<TokenIcon symbol={pc.currency.code} size={20} className="shrink-0" />
|
|
243
|
+
<div className="flex flex-col flex-1 min-w-0">
|
|
244
|
+
<span className="font-medium truncate">
|
|
245
|
+
{pc.currency.code}
|
|
246
|
+
{pc.network && ` (${pc.network.name})`}
|
|
247
|
+
</span>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
</FormControl>
|
|
145
254
|
<FormDescription>
|
|
146
255
|
The cryptocurrency to use for payment.
|
|
147
256
|
</FormDescription>
|
|
@@ -150,37 +259,59 @@ export const CreatePaymentDialog: React.FC = () => {
|
|
|
150
259
|
)}
|
|
151
260
|
/>
|
|
152
261
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
</
|
|
169
|
-
</
|
|
170
|
-
</
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
262
|
+
{/* Conversion and Fee Information */}
|
|
263
|
+
{calculateCryptoAmount && (() => {
|
|
264
|
+
const pc = getProviderCurrency(form.watch('currency_code'));
|
|
265
|
+
const amountUsd = form.watch('amount_usd');
|
|
266
|
+
if (!pc) return null;
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div className="rounded-sm bg-muted p-4 space-y-3">
|
|
270
|
+
{/* Amount to Send in Crypto */}
|
|
271
|
+
<div className="flex items-center justify-between">
|
|
272
|
+
<span className="text-sm text-muted-foreground">You will send</span>
|
|
273
|
+
<div className="flex items-center gap-2">
|
|
274
|
+
<TokenIcon symbol={calculateCryptoAmount.currency} size={16} />
|
|
275
|
+
<span className="font-mono font-semibold">
|
|
276
|
+
{calculateCryptoAmount.amount.toFixed(8)} {calculateCryptoAmount.currency}
|
|
277
|
+
</span>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* USD Amount Received */}
|
|
282
|
+
<div className="flex items-center justify-between">
|
|
283
|
+
<span className="text-sm text-muted-foreground">You will receive</span>
|
|
284
|
+
<span className="text-lg font-bold">
|
|
285
|
+
${amountUsd?.toFixed(2)} USD
|
|
286
|
+
</span>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
{/* Exchange Rate */}
|
|
290
|
+
<div className="flex items-center justify-between text-xs">
|
|
291
|
+
<span className="text-muted-foreground">Rate</span>
|
|
292
|
+
<span className="font-medium">
|
|
293
|
+
1 {calculateCryptoAmount.currency} = ${pc.currency.usd_rate?.toFixed(2)}
|
|
294
|
+
</span>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
{/* Network Info */}
|
|
298
|
+
{pc.network && (
|
|
299
|
+
<div className="border-t pt-3">
|
|
300
|
+
<div className="flex items-center justify-between">
|
|
301
|
+
<span className="text-sm text-muted-foreground">Network</span>
|
|
302
|
+
<span className="text-sm font-medium">{pc.network.name}</span>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
})()}
|
|
178
309
|
|
|
179
310
|
<DialogFooter>
|
|
180
311
|
<Button type="button" variant="outline" onClick={handleClose} disabled={isSubmitting}>
|
|
181
312
|
Cancel
|
|
182
313
|
</Button>
|
|
183
|
-
<Button type="submit" disabled={isSubmitting
|
|
314
|
+
<Button type="submit" disabled={isSubmitting}>
|
|
184
315
|
{isSubmitting ? (
|
|
185
316
|
<>
|
|
186
317
|
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|