@djangocfg/layouts 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/package.json +5 -5
  2. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +47 -65
  3. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +118 -163
  4. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +31 -45
  5. package/src/layouts/PaymentsLayout/components/index.ts +1 -4
  6. package/src/layouts/PaymentsLayout/events.ts +23 -84
  7. package/src/layouts/PaymentsLayout/index.ts +7 -11
  8. package/src/layouts/PaymentsLayout/types.ts +3 -16
  9. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +45 -16
  10. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +16 -12
  11. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  12. package/src/layouts/PaymentsLayout/views/overview/index.tsx +3 -6
  13. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +50 -30
  14. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  15. package/src/layouts/PaymentsLayout/views/payments/index.tsx +1 -2
  16. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +273 -0
  17. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +1 -0
  18. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +5 -17
  19. package/src/layouts/PaymentsLayout/README.md +0 -133
  20. package/src/layouts/PaymentsLayout/components/CreateApiKeyDialog.tsx +0 -172
  21. package/src/layouts/PaymentsLayout/components/DeleteApiKeyDialog.tsx +0 -100
  22. package/src/layouts/PaymentsLayout/context/RootPaymentsContext.tsx +0 -129
  23. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeyMetrics.tsx +0 -109
  24. package/src/layouts/PaymentsLayout/views/apikeys/components/ApiKeysList.tsx +0 -194
  25. package/src/layouts/PaymentsLayout/views/apikeys/components/index.ts +0 -3
  26. package/src/layouts/PaymentsLayout/views/apikeys/index.tsx +0 -19
  27. package/src/layouts/PaymentsLayout/views/overview/components/MetricsCards.tsx +0 -103
  28. package/src/layouts/PaymentsLayout/views/tariffs/index.tsx +0 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Layout system and components for Unrealon applications",
5
5
  "author": {
6
6
  "name": "DjangoCFG",
@@ -53,9 +53,9 @@
53
53
  "check": "tsc --noEmit"
54
54
  },
55
55
  "peerDependencies": {
56
- "@djangocfg/api": "^1.0.4",
57
- "@djangocfg/og-image": "^1.0.4",
58
- "@djangocfg/ui": "^1.0.4",
56
+ "@djangocfg/api": "^1.0.5",
57
+ "@djangocfg/og-image": "^1.0.5",
58
+ "@djangocfg/ui": "^1.0.5",
59
59
  "@hookform/resolvers": "^5.2.0",
60
60
  "consola": "^3.4.2",
61
61
  "lucide-react": "^0.468.0",
@@ -76,7 +76,7 @@
76
76
  "vidstack": "0.6.15"
77
77
  },
78
78
  "devDependencies": {
79
- "@djangocfg/typescript-config": "^1.0.4",
79
+ "@djangocfg/typescript-config": "^1.0.5",
80
80
  "@types/node": "^24.7.2",
81
81
  "@types/react": "19.2.2",
82
82
  "@types/react-dom": "19.2.1",
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Payments Layout
3
- * Main layout with tabs for different payment-related views
2
+ * Payments Layout (v2.0 - Simplified)
3
+ *
4
+ * Simplified layout with 3 tabs: Overview, Payments, Transactions
5
+ * Removed: API Keys, Tariffs (deprecated in v2.0)
4
6
  */
5
7
 
6
8
  'use client';
@@ -8,18 +10,15 @@
8
10
  import React from 'react';
9
11
  import {
10
12
  PaymentsProvider,
11
- ApiKeysProvider,
12
13
  OverviewProvider,
13
14
  RootPaymentsProvider,
14
15
  } from '@djangocfg/api/cfg/contexts';
15
16
  import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui';
16
- import { Wallet, CreditCard, History, Key, Crown } from 'lucide-react';
17
+ import { Wallet, CreditCard, History } from 'lucide-react';
17
18
  import { OverviewView } from './views/overview';
18
19
  import { PaymentsView } from './views/payments';
19
20
  import { TransactionsView } from './views/transactions';
20
- import { ApiKeysView } from './views/apikeys';
21
- import { TariffsView } from './views/tariffs';
22
- import { CreateApiKeyDialog, DeleteApiKeyDialog, CreatePaymentDialog, PaymentDetailsDialog } from './components';
21
+ import { CreatePaymentDialog, PaymentDetailsDialog } from './components';
23
22
 
24
23
  // ─────────────────────────────────────────────────────────────────────────
25
24
  // Payments Layout
@@ -35,75 +34,58 @@ export const PaymentsLayout: React.FC<PaymentsLayoutProps> = () => {
35
34
  <div className="h-full p-6 space-y-6">
36
35
  {/* Page Header */}
37
36
  <div>
38
- <h1 className="text-3xl font-bold tracking-tight">Payments & Billing</h1>
37
+ <h1 className="text-3xl font-bold tracking-tight">Payments</h1>
39
38
  <p className="text-muted-foreground">
40
- Manage your payments, subscriptions, API keys, and account balance
39
+ Manage your payments, balance, and transaction history
41
40
  </p>
42
41
  </div>
43
42
 
44
- {/* Main Content with Tabs */}
45
- <Tabs defaultValue="overview" className="space-y-6">
46
- <TabsList className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
47
- <TabsTrigger value="overview" className="inline-flex items-center gap-2 px-3 py-1.5">
48
- <Wallet className="h-4 w-4" />
49
- <span className="hidden sm:inline">Overview</span>
50
- </TabsTrigger>
51
- <TabsTrigger value="payments" className="inline-flex items-center gap-2 px-3 py-1.5">
52
- <CreditCard className="h-4 w-4" />
53
- <span className="hidden sm:inline">Payments</span>
54
- </TabsTrigger>
55
- <TabsTrigger value="transactions" className="inline-flex items-center gap-2 px-3 py-1.5">
56
- <History className="h-4 w-4" />
57
- <span className="hidden sm:inline">Transactions</span>
58
- </TabsTrigger>
59
- <TabsTrigger value="apikeys" className="inline-flex items-center gap-2 px-3 py-1.5">
60
- <Key className="h-4 w-4" />
61
- <span className="hidden sm:inline">API Keys</span>
62
- </TabsTrigger>
63
- <TabsTrigger value="tariffs" className="inline-flex items-center gap-2 px-3 py-1.5">
64
- <Crown className="h-4 w-4" />
65
- <span className="hidden sm:inline">Tariffs</span>
66
- </TabsTrigger>
67
- </TabsList>
43
+ {/* Main Content with Tabs */}
44
+ <Tabs defaultValue="overview" className="space-y-6">
45
+ <TabsList className="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground">
46
+ <TabsTrigger value="overview" className="inline-flex items-center gap-2 px-3 py-1.5">
47
+ <Wallet className="h-4 w-4" />
48
+ <span className="hidden sm:inline">Overview</span>
49
+ </TabsTrigger>
50
+ <TabsTrigger value="payments" className="inline-flex items-center gap-2 px-3 py-1.5">
51
+ <CreditCard className="h-4 w-4" />
52
+ <span className="hidden sm:inline">Payments</span>
53
+ </TabsTrigger>
54
+ <TabsTrigger value="transactions" className="inline-flex items-center gap-2 px-3 py-1.5">
55
+ <History className="h-4 w-4" />
56
+ <span className="hidden sm:inline">Transactions</span>
57
+ </TabsTrigger>
58
+ </TabsList>
68
59
 
69
- {/* Each tab wrapped in its own provider - loads only when tab is active */}
70
- <TabsContent value="overview" className="space-y-6">
71
- <OverviewProvider>
60
+ {/* Overview Tab - Balance + Recent Payments */}
61
+ <TabsContent value="overview" className="space-y-6">
62
+ <OverviewProvider>
63
+ <PaymentsProvider>
64
+ <OverviewView />
65
+ <CreatePaymentDialog />
66
+ </PaymentsProvider>
67
+ </OverviewProvider>
68
+ </TabsContent>
69
+
70
+ {/* Payments Tab - Full Payment List */}
71
+ <TabsContent value="payments" className="space-y-6">
72
72
  <PaymentsProvider>
73
- <OverviewView />
73
+ <PaymentsView />
74
74
  <CreatePaymentDialog />
75
75
  </PaymentsProvider>
76
- </OverviewProvider>
77
- </TabsContent>
78
-
79
- <TabsContent value="payments" className="space-y-6">
80
- <PaymentsProvider>
81
- <PaymentsView />
82
- <CreatePaymentDialog />
83
- </PaymentsProvider>
84
- </TabsContent>
85
-
86
- <TabsContent value="transactions" className="space-y-6">
87
- <TransactionsView />
88
- </TabsContent>
76
+ </TabsContent>
89
77
 
90
- <TabsContent value="apikeys" className="space-y-6">
91
- <ApiKeysProvider>
92
- <ApiKeysView />
93
- <CreateApiKeyDialog />
94
- <DeleteApiKeyDialog />
95
- </ApiKeysProvider>
96
- </TabsContent>
78
+ {/* Transactions Tab - Transaction History */}
79
+ <TabsContent value="transactions" className="space-y-6">
80
+ <OverviewProvider>
81
+ <TransactionsView />
82
+ </OverviewProvider>
83
+ </TabsContent>
84
+ </Tabs>
97
85
 
98
- <TabsContent value="tariffs" className="space-y-6">
99
- <TariffsView />
100
- </TabsContent>
101
- </Tabs>
102
-
103
- {/* Global Payment Details Dialog */}
104
- <PaymentDetailsDialog />
86
+ {/* Global Payment Details Dialog */}
87
+ <PaymentDetailsDialog />
105
88
  </div>
106
89
  </RootPaymentsProvider>
107
90
  );
108
91
  };
109
-
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Create Payment Dialog
2
+ * Create Payment Dialog (v2.0 - Simplified)
3
3
  * Dialog for creating new payments
4
4
  */
5
5
 
@@ -21,115 +21,95 @@ import {
21
21
  FormLabel,
22
22
  FormMessage,
23
23
  Input,
24
- Combobox,
24
+ Select,
25
+ SelectContent,
26
+ SelectItem,
27
+ SelectTrigger,
28
+ SelectValue,
25
29
  Button,
26
30
  TokenIcon,
27
- useEventListener,
28
31
  } from '@djangocfg/ui';
29
32
  import { Plus, RefreshCw } from 'lucide-react';
30
33
  import { useForm } from 'react-hook-form';
31
34
  import { zodResolver } from '@hookform/resolvers/zod';
35
+ import { z } from 'zod';
32
36
  import { usePaymentsContext, useRootPaymentsContext } from '@djangocfg/api/cfg/contexts';
33
- import { Schemas } from '@djangocfg/api/cfg/generated';
34
- import { paymentsLogger } from '../../../utils/logger';
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';
37
+ import { PAYMENT_EVENTS, closePaymentsDialog } from '../events';
38
+ import { openPaymentDetailsDialog } from '../events';
39
39
 
40
- const { PaymentCreateRequestSchema } = Schemas;
41
- type PaymentCreateRequest = Schemas.PaymentCreateRequest;
40
+ // Payment creation schema
41
+ const PaymentCreateSchema = z.object({
42
+ amount_usd: z.number().min(0.01, 'Amount must be at least $0.01'),
43
+ currency_code: z.string().min(1, 'Please select a currency'),
44
+ });
45
+
46
+ type PaymentCreateRequest = z.infer<typeof PaymentCreateSchema>;
42
47
 
43
48
  export const CreatePaymentDialog: React.FC = () => {
44
49
  const [open, setOpen] = useState(false);
45
50
  const [isSubmitting, setIsSubmitting] = useState(false);
46
51
 
47
52
  const { createPayment } = usePaymentsContext();
48
- const {
49
- providerCurrencies,
50
- isLoadingProviderCurrencies,
51
- } = useRootPaymentsContext();
53
+ const { currencies, isLoadingCurrencies } = useRootPaymentsContext();
52
54
 
53
55
  const form = useForm<PaymentCreateRequest>({
54
- resolver: zodResolver(PaymentCreateRequestSchema),
56
+ resolver: zodResolver(PaymentCreateSchema),
55
57
  defaultValues: {
56
58
  amount_usd: 10,
57
- currency_code: 'USDT', // Default to USDT
59
+ currency_code: 'USDT',
58
60
  },
59
61
  });
60
62
 
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
- };
63
+ // Extract currencies list from response (handle different possible structures)
64
+ const currenciesList = useMemo(() => {
65
+ const data = currencies?.currencies || currencies?.results || currencies || [];
66
+ return Array.isArray(data) ? data : [];
67
+ }, [currencies]);
68
+
69
+ // Get currency options for select
70
+ const currencyOptions = useMemo(() => {
71
+ return currenciesList
72
+ .filter((curr: any) => curr.is_enabled !== false)
73
+ .map((curr: any) => ({
74
+ code: curr.code || curr.currency_code || curr.symbol,
75
+ name: curr.name || curr.code || curr.currency_code,
76
+ usd_rate: curr.usd_rate || curr.rate || 1,
77
+ network: curr.network || null,
78
+ }));
79
+ }, [currenciesList]);
107
80
 
108
81
  // Calculate crypto amount from USD
109
82
  const calculateCryptoAmount = useMemo(() => {
110
83
  const amountUsd = form.watch('amount_usd');
111
84
  const currencyCode = form.watch('currency_code');
112
- const pc = getProviderCurrency(currencyCode);
85
+ const currency = currencyOptions.find((c: any) => c.code === currencyCode);
113
86
 
114
- if (!pc || !pc.currency.usd_rate || !amountUsd) {
87
+ if (!currency || !currency.usd_rate || !amountUsd) {
115
88
  return null;
116
89
  }
117
90
 
118
- const cryptoAmount = amountUsd / pc.currency.usd_rate;
91
+ const cryptoAmount = amountUsd / currency.usd_rate;
119
92
  return {
120
93
  amount: cryptoAmount,
121
- currency: pc.currency.code,
122
- symbol: pc.currency.symbol || pc.currency.code,
94
+ currency: currency.code,
95
+ rate: currency.usd_rate,
96
+ network: currency.network,
123
97
  };
124
- }, [form.watch('amount_usd'), form.watch('currency_code'), providerCurrencies]);
98
+ }, [form.watch('amount_usd'), form.watch('currency_code'), currencyOptions]);
125
99
 
126
- useEventListener(PAYMENTS_DIALOG_EVENTS.OPEN_CREATE_PAYMENT_DIALOG, () => {
127
- setOpen(true);
128
- });
100
+ // Listen for open/close events
101
+ useEffect(() => {
102
+ const handleOpen = () => setOpen(true);
103
+ const handleClose = () => setOpen(false);
129
104
 
130
- useEventListener(PAYMENTS_DIALOG_EVENTS.CLOSE_PAYMENTS_DIALOG, () => {
131
- setOpen(false);
132
- });
105
+ window.addEventListener(PAYMENT_EVENTS.OPEN_CREATE_PAYMENT_DIALOG, handleOpen);
106
+ window.addEventListener(PAYMENT_EVENTS.CLOSE_DIALOG, handleClose);
107
+
108
+ return () => {
109
+ window.removeEventListener(PAYMENT_EVENTS.OPEN_CREATE_PAYMENT_DIALOG, handleOpen);
110
+ window.removeEventListener(PAYMENT_EVENTS.CLOSE_DIALOG, handleClose);
111
+ };
112
+ }, []);
133
113
 
134
114
  const handleClose = () => {
135
115
  setOpen(false);
@@ -139,7 +119,7 @@ export const CreatePaymentDialog: React.FC = () => {
139
119
  // Initialize default currency if not set
140
120
  useEffect(() => {
141
121
  if (currencyOptions.length > 0 && !form.getValues('currency_code')) {
142
- form.setValue('currency_code', currencyOptions[0].value as any);
122
+ form.setValue('currency_code', currencyOptions[0].code);
143
123
  }
144
124
  }, [currencyOptions, form]);
145
125
 
@@ -147,20 +127,19 @@ export const CreatePaymentDialog: React.FC = () => {
147
127
  try {
148
128
  setIsSubmitting(true);
149
129
 
150
- const result = await createPayment(data);
130
+ const result = await createPayment();
151
131
  handleClose();
152
132
  closePaymentsDialog();
153
133
 
154
- // The API returns a wrapped response with { success, message, payment }
155
- // Extract the payment ID from the result
134
+ // Extract payment ID from result
156
135
  const paymentData = result as any;
157
136
  const paymentId = paymentData?.payment?.id || paymentData?.id;
158
137
 
159
138
  if (paymentId) {
160
- openPaymentDetails(String(paymentId));
139
+ openPaymentDetailsDialog(String(paymentId));
161
140
  }
162
141
  } catch (error) {
163
- paymentsLogger.error('Failed to create payment:', error);
142
+ console.error('Failed to create payment:', error);
164
143
  } finally {
165
144
  setIsSubmitting(false);
166
145
  }
@@ -208,49 +187,32 @@ export const CreatePaymentDialog: React.FC = () => {
208
187
  render={({ field }) => (
209
188
  <FormItem>
210
189
  <FormLabel>Currency</FormLabel>
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 (
190
+ <Select
191
+ onValueChange={field.onChange}
192
+ defaultValue={field.value}
193
+ disabled={isLoadingCurrencies}
194
+ >
195
+ <FormControl>
196
+ <SelectTrigger>
197
+ <SelectValue placeholder="Select currency..." />
198
+ </SelectTrigger>
199
+ </FormControl>
200
+ <SelectContent>
201
+ {currencyOptions.map((curr: any) => (
202
+ <SelectItem key={curr.code} value={curr.code}>
226
203
  <div className="flex items-center gap-2">
227
- <TokenIcon symbol={pc.currency.code} size={20} />
228
- <span>{pc.currency.code}</span>
229
- {pc.network && (
204
+ <TokenIcon symbol={curr.code} size={16} />
205
+ <span>{curr.code}</span>
206
+ {curr.network && (
230
207
  <span className="text-xs text-muted-foreground">
231
- ({pc.network.name})
208
+ ({curr.network})
232
209
  </span>
233
210
  )}
234
211
  </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>
212
+ </SelectItem>
213
+ ))}
214
+ </SelectContent>
215
+ </Select>
254
216
  <FormDescription>
255
217
  The cryptocurrency to use for payment.
256
218
  </FormDescription>
@@ -259,59 +221,53 @@ export const CreatePaymentDialog: React.FC = () => {
259
221
  )}
260
222
  />
261
223
 
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
224
+ {/* Conversion Information */}
225
+ {calculateCryptoAmount && (
226
+ <div className="rounded-sm bg-muted p-4 space-y-3">
227
+ {/* Amount to Send in Crypto */}
228
+ <div className="flex items-center justify-between">
229
+ <span className="text-sm text-muted-foreground">You will send</span>
230
+ <div className="flex items-center gap-2">
231
+ <TokenIcon symbol={calculateCryptoAmount.currency} size={16} />
232
+ <span className="font-mono font-semibold">
233
+ {calculateCryptoAmount.amount.toFixed(8)} {calculateCryptoAmount.currency}
286
234
  </span>
287
235
  </div>
236
+ </div>
288
237
 
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>
238
+ {/* USD Amount Received */}
239
+ <div className="flex items-center justify-between">
240
+ <span className="text-sm text-muted-foreground">You will receive</span>
241
+ <span className="text-lg font-bold">
242
+ ${form.watch('amount_usd')?.toFixed(2)} USD
243
+ </span>
244
+ </div>
296
245
 
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
- )}
246
+ {/* Exchange Rate */}
247
+ <div className="flex items-center justify-between text-xs">
248
+ <span className="text-muted-foreground">Rate</span>
249
+ <span className="font-medium">
250
+ 1 {calculateCryptoAmount.currency} = ${calculateCryptoAmount.rate?.toFixed(2)}
251
+ </span>
306
252
  </div>
307
- );
308
- })()}
253
+
254
+ {/* Network Info */}
255
+ {calculateCryptoAmount.network && (
256
+ <div className="border-t pt-3">
257
+ <div className="flex items-center justify-between">
258
+ <span className="text-sm text-muted-foreground">Network</span>
259
+ <span className="text-sm font-medium">{calculateCryptoAmount.network}</span>
260
+ </div>
261
+ </div>
262
+ )}
263
+ </div>
264
+ )}
309
265
 
310
266
  <DialogFooter>
311
267
  <Button type="button" variant="outline" onClick={handleClose} disabled={isSubmitting}>
312
268
  Cancel
313
269
  </Button>
314
- <Button type="submit" disabled={isSubmitting}>
270
+ <Button type="submit" disabled={isSubmitting || currencyOptions.length === 0}>
315
271
  {isSubmitting ? (
316
272
  <>
317
273
  <RefreshCw className="h-4 w-4 mr-2 animate-spin" />
@@ -331,4 +287,3 @@ export const CreatePaymentDialog: React.FC = () => {
331
287
  </Dialog>
332
288
  );
333
289
  };
334
-