@djangocfg/ext-payments 1.0.21 → 1.0.23
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 +14 -5
- package/dist/config.js +14 -5
- package/dist/i18n.cjs +434 -0
- package/dist/i18n.d.cts +172 -0
- package/dist/i18n.d.ts +172 -0
- package/dist/i18n.js +406 -0
- package/dist/index.cjs +653 -113
- package/dist/index.js +654 -114
- package/package.json +22 -13
- package/src/components/ActivityList.tsx +15 -4
- package/src/components/AddFundsSheet.tsx +34 -14
- package/src/components/BalanceHero.tsx +17 -5
- package/src/components/CurrencyCombobox.tsx +13 -3
- package/src/components/PaymentSheet.tsx +72 -29
- package/src/components/WithdrawSheet.tsx +36 -15
- package/src/components/WithdrawalSheet.tsx +75 -32
- package/src/i18n/index.ts +26 -0
- package/src/i18n/locales/en.ts +136 -0
- package/src/i18n/locales/ko.ts +136 -0
- package/src/i18n/locales/ru.ts +136 -0
- package/src/i18n/types.ts +169 -0
- package/src/i18n/usePaymentsT.ts +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ext-payments",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "Payments system extension for DjangoCFG",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"types": "./dist/api/hooks.d.ts",
|
|
52
52
|
"import": "./dist/api/hooks.js",
|
|
53
53
|
"require": "./dist/api/hooks.cjs"
|
|
54
|
+
},
|
|
55
|
+
"./i18n": {
|
|
56
|
+
"types": "./dist/i18n.d.ts",
|
|
57
|
+
"import": "./dist/i18n.js",
|
|
58
|
+
"require": "./dist/i18n.cjs"
|
|
54
59
|
}
|
|
55
60
|
},
|
|
56
61
|
"files": [
|
|
@@ -64,29 +69,33 @@
|
|
|
64
69
|
"check": "tsc --noEmit"
|
|
65
70
|
},
|
|
66
71
|
"peerDependencies": {
|
|
67
|
-
"@djangocfg/api": "^2.1.
|
|
68
|
-
"@djangocfg/ext-base": "^1.0.
|
|
69
|
-
"@djangocfg/
|
|
70
|
-
"@djangocfg/ui-
|
|
72
|
+
"@djangocfg/api": "^2.1.124",
|
|
73
|
+
"@djangocfg/ext-base": "^1.0.18",
|
|
74
|
+
"@djangocfg/i18n": "^2.1.124",
|
|
75
|
+
"@djangocfg/ui-core": "^2.1.124",
|
|
76
|
+
"@djangocfg/ui-nextjs": "^2.1.124",
|
|
77
|
+
"@hookform/resolvers": "^5.2.2",
|
|
71
78
|
"consola": "^3.4.2",
|
|
72
79
|
"lucide-react": "^0.545.0",
|
|
80
|
+
"moment": "^2.30.1",
|
|
73
81
|
"next": "^16",
|
|
82
|
+
"next-intl": "^4",
|
|
74
83
|
"p-retry": "^7.0.0",
|
|
75
84
|
"react": "^19",
|
|
76
|
-
"swr": "^2.3.7",
|
|
77
|
-
"zod": "^4.3.4",
|
|
78
|
-
"moment": "^2.30.1",
|
|
79
85
|
"react-hook-form": "^7.69.0",
|
|
80
|
-
"
|
|
86
|
+
"swr": "^2.3.7",
|
|
87
|
+
"zod": "^4.3.4"
|
|
81
88
|
},
|
|
82
89
|
"devDependencies": {
|
|
83
|
-
"@djangocfg/api": "^2.1.
|
|
84
|
-
"@djangocfg/ext-base": "^1.0.
|
|
85
|
-
"@djangocfg/
|
|
86
|
-
"@djangocfg/
|
|
90
|
+
"@djangocfg/api": "^2.1.124",
|
|
91
|
+
"@djangocfg/ext-base": "^1.0.18",
|
|
92
|
+
"@djangocfg/i18n": "^2.1.124",
|
|
93
|
+
"@djangocfg/typescript-config": "^2.1.124",
|
|
94
|
+
"@djangocfg/ui-nextjs": "^2.1.124",
|
|
87
95
|
"@types/node": "^24.7.2",
|
|
88
96
|
"@types/react": "^19.0.0",
|
|
89
97
|
"consola": "^3.4.2",
|
|
98
|
+
"next-intl": "^4.1.0",
|
|
90
99
|
"p-retry": "^7.0.0",
|
|
91
100
|
"swr": "^2.3.7",
|
|
92
101
|
"tsup": "^8.5.0",
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
'use client';
|
|
8
8
|
|
|
9
|
+
import { useMemo } from 'react';
|
|
9
10
|
import { History, ChevronRight } from 'lucide-react';
|
|
10
11
|
|
|
12
|
+
import { usePaymentsT } from '../i18n';
|
|
11
13
|
import { Button, Skeleton } from '@djangocfg/ui-core';
|
|
12
14
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
13
15
|
|
|
@@ -27,8 +29,17 @@ export function ActivityList({
|
|
|
27
29
|
limit = 10,
|
|
28
30
|
className,
|
|
29
31
|
}: ActivityListProps) {
|
|
32
|
+
const pt = usePaymentsT();
|
|
30
33
|
const { activity, isLoadingActivity, hasMoreActivity } = useWallet();
|
|
31
34
|
|
|
35
|
+
// Prepare labels before render
|
|
36
|
+
const labels = useMemo(() => ({
|
|
37
|
+
title: pt('activity.title'),
|
|
38
|
+
noActivity: pt('activity.noActivity'),
|
|
39
|
+
transactionsWillAppear: pt('activity.transactionsWillAppear'),
|
|
40
|
+
viewAll: pt('actions.viewAll'),
|
|
41
|
+
}), [pt]);
|
|
42
|
+
|
|
32
43
|
const displayedActivity = limit ? activity.slice(0, limit) : activity;
|
|
33
44
|
|
|
34
45
|
if (isLoadingActivity) {
|
|
@@ -57,9 +68,9 @@ export function ActivityList({
|
|
|
57
68
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-muted mb-4">
|
|
58
69
|
<History className="h-8 w-8 text-muted-foreground" />
|
|
59
70
|
</div>
|
|
60
|
-
<h3 className="font-semibold mb-1">
|
|
71
|
+
<h3 className="font-semibold mb-1">{labels.noActivity}</h3>
|
|
61
72
|
<p className="text-sm text-muted-foreground">
|
|
62
|
-
|
|
73
|
+
{labels.transactionsWillAppear}
|
|
63
74
|
</p>
|
|
64
75
|
</div>
|
|
65
76
|
);
|
|
@@ -69,10 +80,10 @@ export function ActivityList({
|
|
|
69
80
|
<div className={cn('pt-6', className)}>
|
|
70
81
|
{/* Header */}
|
|
71
82
|
<div className="flex items-center justify-between px-4 py-2">
|
|
72
|
-
<h2 className="font-semibold text-lg">
|
|
83
|
+
<h2 className="font-semibold text-lg">{labels.title}</h2>
|
|
73
84
|
{hasMoreActivity && onViewAll && (
|
|
74
85
|
<Button variant="ghost" size="sm" onClick={onViewAll} className="text-primary">
|
|
75
|
-
|
|
86
|
+
{labels.viewAll}
|
|
76
87
|
<ChevronRight className="h-4 w-4 ml-1" />
|
|
77
88
|
</Button>
|
|
78
89
|
)}
|
|
@@ -13,6 +13,7 @@ import { useForm } from 'react-hook-form';
|
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
15
15
|
|
|
16
|
+
import { usePaymentsT } from '../i18n';
|
|
16
17
|
import {
|
|
17
18
|
Alert,
|
|
18
19
|
AlertDescription,
|
|
@@ -73,10 +74,29 @@ interface AddFundsSaved {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetProps) {
|
|
77
|
+
const pt = usePaymentsT();
|
|
76
78
|
const { currencies, isLoadingCurrencies, addFunds } = useWallet();
|
|
77
79
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
78
80
|
const [error, setError] = useState<string | null>(null);
|
|
79
81
|
|
|
82
|
+
// Prepare labels before render
|
|
83
|
+
const labels = useMemo(() => ({
|
|
84
|
+
title: pt('sheets.addFundsTitle'),
|
|
85
|
+
description: pt('sheets.addFundsDescription'),
|
|
86
|
+
amountUsd: pt('form.amountUsd'),
|
|
87
|
+
payWith: pt('form.payWith'),
|
|
88
|
+
amount: pt('form.amount'),
|
|
89
|
+
serviceFee: pt('estimate.serviceFee'),
|
|
90
|
+
rate: pt('estimate.rate'),
|
|
91
|
+
youWillSend: pt('estimate.youWillSend'),
|
|
92
|
+
youWillReceive: pt('estimate.youWillReceive'),
|
|
93
|
+
gettingRate: pt('estimate.gettingRate'),
|
|
94
|
+
enterAmountToSee: pt('estimate.enterAmountToSee'),
|
|
95
|
+
minimumAmount: pt('estimate.minimumAmount'),
|
|
96
|
+
continue: pt('actions.continue'),
|
|
97
|
+
creating: pt('withdraw.creating'),
|
|
98
|
+
}), [pt]);
|
|
99
|
+
|
|
80
100
|
// Remember last used currency and amount
|
|
81
101
|
const [saved, setSaved] = useLocalStorage<AddFundsSaved>(STORAGE_KEY, {
|
|
82
102
|
currency: '',
|
|
@@ -183,9 +203,9 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
183
203
|
<ResponsiveSheet open={open} onOpenChange={handleOpenChange}>
|
|
184
204
|
<ResponsiveSheetContent className="sm:max-w-md">
|
|
185
205
|
<ResponsiveSheetHeader>
|
|
186
|
-
<ResponsiveSheetTitle>
|
|
206
|
+
<ResponsiveSheetTitle>{labels.title}</ResponsiveSheetTitle>
|
|
187
207
|
<ResponsiveSheetDescription>
|
|
188
|
-
|
|
208
|
+
{labels.description}
|
|
189
209
|
</ResponsiveSheetDescription>
|
|
190
210
|
</ResponsiveSheetHeader>
|
|
191
211
|
|
|
@@ -197,7 +217,7 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
197
217
|
name="amount"
|
|
198
218
|
render={({ field }) => (
|
|
199
219
|
<FormItem>
|
|
200
|
-
<FormLabel>
|
|
220
|
+
<FormLabel>{labels.amountUsd}</FormLabel>
|
|
201
221
|
<FormControl>
|
|
202
222
|
<div className="relative">
|
|
203
223
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground text-lg">
|
|
@@ -225,7 +245,7 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
225
245
|
name="currency"
|
|
226
246
|
render={({ field }) => (
|
|
227
247
|
<FormItem>
|
|
228
|
-
<FormLabel>
|
|
248
|
+
<FormLabel>{labels.payWith}</FormLabel>
|
|
229
249
|
<FormControl>
|
|
230
250
|
<CurrencyCombobox
|
|
231
251
|
options={currencyOptions}
|
|
@@ -245,24 +265,24 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
245
265
|
{isLoadingEstimate ? (
|
|
246
266
|
<div className="flex items-center justify-center py-2">
|
|
247
267
|
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
|
248
|
-
<span className="ml-2 text-sm text-muted-foreground">
|
|
268
|
+
<span className="ml-2 text-sm text-muted-foreground">{labels.gettingRate}</span>
|
|
249
269
|
</div>
|
|
250
270
|
) : displayData ? (
|
|
251
271
|
<>
|
|
252
272
|
{/* Amount breakdown */}
|
|
253
273
|
<div className="flex items-center justify-between text-sm">
|
|
254
|
-
<span className="text-muted-foreground">
|
|
274
|
+
<span className="text-muted-foreground">{labels.amount}</span>
|
|
255
275
|
<span>${formatUsdAmount(displayData.amountToReceive)}</span>
|
|
256
276
|
</div>
|
|
257
277
|
<div className="flex items-center justify-between text-sm">
|
|
258
|
-
<span className="text-muted-foreground">
|
|
278
|
+
<span className="text-muted-foreground">{labels.serviceFee} ({displayData.serviceFeePercent}%)</span>
|
|
259
279
|
<span>+${formatUsdAmount(displayData.serviceFeeUsd)}</span>
|
|
260
280
|
</div>
|
|
261
281
|
|
|
262
282
|
{/* Rate (for non-stablecoins) */}
|
|
263
283
|
{displayData.showRate && (
|
|
264
284
|
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
|
265
|
-
<span>
|
|
285
|
+
<span>{labels.rate}</span>
|
|
266
286
|
<span>1 {displayData.token} = ${displayData.rate}</span>
|
|
267
287
|
</div>
|
|
268
288
|
)}
|
|
@@ -270,7 +290,7 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
270
290
|
{/* You will send */}
|
|
271
291
|
<div className="border-t pt-2 mt-2">
|
|
272
292
|
<div className="flex items-center justify-between">
|
|
273
|
-
<span className="font-medium">
|
|
293
|
+
<span className="font-medium">{labels.youWillSend}</span>
|
|
274
294
|
<div className="text-right">
|
|
275
295
|
<div className="flex items-center gap-2 justify-end">
|
|
276
296
|
<TokenIcon symbol={displayData.token} size={20} />
|
|
@@ -287,20 +307,20 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
287
307
|
|
|
288
308
|
{/* You will receive */}
|
|
289
309
|
<div className="flex items-center justify-between text-sm pt-2">
|
|
290
|
-
<span className="text-muted-foreground">
|
|
310
|
+
<span className="text-muted-foreground">{labels.youWillReceive}</span>
|
|
291
311
|
<span className="font-medium">${formatUsdAmount(displayData.amountToReceive)}</span>
|
|
292
312
|
</div>
|
|
293
313
|
|
|
294
314
|
{/* Minimum warning */}
|
|
295
315
|
{displayData.belowMinimum && displayData.minAmount && (
|
|
296
316
|
<div className="text-sm text-destructive mt-2 pt-2 border-t border-destructive/20">
|
|
297
|
-
|
|
317
|
+
{labels.minimumAmount}: ${displayData.minAmount}
|
|
298
318
|
</div>
|
|
299
319
|
)}
|
|
300
320
|
</>
|
|
301
321
|
) : (
|
|
302
322
|
<div className="text-center text-sm text-muted-foreground py-2">
|
|
303
|
-
|
|
323
|
+
{labels.enterAmountToSee}
|
|
304
324
|
</div>
|
|
305
325
|
)}
|
|
306
326
|
</div>
|
|
@@ -328,10 +348,10 @@ export function AddFundsSheet({ open, onOpenChange, onSuccess }: AddFundsSheetPr
|
|
|
328
348
|
{isSubmitting ? (
|
|
329
349
|
<>
|
|
330
350
|
<RefreshCw className="h-5 w-5 mr-2 animate-spin" />
|
|
331
|
-
|
|
351
|
+
{labels.creating}
|
|
332
352
|
</>
|
|
333
353
|
) : (
|
|
334
|
-
|
|
354
|
+
labels.continue
|
|
335
355
|
)}
|
|
336
356
|
</Button>
|
|
337
357
|
</form>
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
'use client';
|
|
8
8
|
|
|
9
|
+
import { useMemo } from 'react';
|
|
9
10
|
import { Plus, ArrowUpRight, RefreshCw } from 'lucide-react';
|
|
10
11
|
|
|
12
|
+
import { usePaymentsT } from '../i18n';
|
|
11
13
|
import { Button, Skeleton } from '@djangocfg/ui-core';
|
|
12
14
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
13
15
|
|
|
@@ -20,8 +22,18 @@ interface BalanceHeroProps {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export function BalanceHero({ onAddFunds, onWithdraw, className }: BalanceHeroProps) {
|
|
25
|
+
const pt = usePaymentsT();
|
|
23
26
|
const { balance, balanceAmount, isLoadingBalance, refreshWallet } = useWallet();
|
|
24
27
|
|
|
28
|
+
// Prepare labels before render
|
|
29
|
+
const labels = useMemo(() => ({
|
|
30
|
+
available: pt('balance.available'),
|
|
31
|
+
totalDeposited: pt('balance.totalDeposited'),
|
|
32
|
+
totalWithdrawn: pt('balance.totalWithdrawn'),
|
|
33
|
+
addFunds: pt('actions.addFunds'),
|
|
34
|
+
withdraw: pt('actions.withdraw'),
|
|
35
|
+
}), [pt]);
|
|
36
|
+
|
|
25
37
|
const formattedBalance = new Intl.NumberFormat('en-US', {
|
|
26
38
|
style: 'currency',
|
|
27
39
|
currency: 'USD',
|
|
@@ -43,7 +55,7 @@ export function BalanceHero({ onAddFunds, onWithdraw, className }: BalanceHeroPr
|
|
|
43
55
|
<h1 className="text-5xl font-bold tracking-tight tabular-nums">
|
|
44
56
|
{formattedBalance}
|
|
45
57
|
</h1>
|
|
46
|
-
<p className="text-muted-foreground mt-1">
|
|
58
|
+
<p className="text-muted-foreground mt-1">{labels.available}</p>
|
|
47
59
|
</>
|
|
48
60
|
)}
|
|
49
61
|
</div>
|
|
@@ -56,7 +68,7 @@ export function BalanceHero({ onAddFunds, onWithdraw, className }: BalanceHeroPr
|
|
|
56
68
|
className="rounded-full px-6"
|
|
57
69
|
>
|
|
58
70
|
<Plus className="h-5 w-5 mr-2" />
|
|
59
|
-
|
|
71
|
+
{labels.addFunds}
|
|
60
72
|
</Button>
|
|
61
73
|
|
|
62
74
|
<Button
|
|
@@ -66,7 +78,7 @@ export function BalanceHero({ onAddFunds, onWithdraw, className }: BalanceHeroPr
|
|
|
66
78
|
className="rounded-full px-6"
|
|
67
79
|
>
|
|
68
80
|
<ArrowUpRight className="h-5 w-5 mr-2" />
|
|
69
|
-
|
|
81
|
+
{labels.withdraw}
|
|
70
82
|
</Button>
|
|
71
83
|
|
|
72
84
|
<Button
|
|
@@ -86,14 +98,14 @@ export function BalanceHero({ onAddFunds, onWithdraw, className }: BalanceHeroPr
|
|
|
86
98
|
<span className="block font-medium text-foreground">
|
|
87
99
|
${parseFloat(balance.total_deposited || '0').toFixed(2)}
|
|
88
100
|
</span>
|
|
89
|
-
<span>
|
|
101
|
+
<span>{labels.totalDeposited}</span>
|
|
90
102
|
</div>
|
|
91
103
|
<div className="h-8 w-px bg-border" />
|
|
92
104
|
<div className="text-center">
|
|
93
105
|
<span className="block font-medium text-foreground">
|
|
94
106
|
${parseFloat(balance.total_withdrawn || '0').toFixed(2)}
|
|
95
107
|
</span>
|
|
96
|
-
<span>
|
|
108
|
+
<span>{labels.totalWithdrawn}</span>
|
|
97
109
|
</div>
|
|
98
110
|
</div>
|
|
99
111
|
)}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* Currency Combobox with TokenIcon
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { useMemo } from 'react';
|
|
8
|
+
|
|
9
|
+
import { usePaymentsT } from '../i18n';
|
|
7
10
|
import { Combobox, TokenIcon } from '@djangocfg/ui-core';
|
|
8
11
|
import type { CurrencyOption } from '../types';
|
|
9
12
|
|
|
@@ -21,15 +24,22 @@ export function CurrencyCombobox({
|
|
|
21
24
|
value,
|
|
22
25
|
onChange,
|
|
23
26
|
disabled,
|
|
24
|
-
placeholder
|
|
27
|
+
placeholder,
|
|
25
28
|
}: CurrencyComboboxProps) {
|
|
29
|
+
const pt = usePaymentsT();
|
|
30
|
+
|
|
31
|
+
const labels = useMemo(() => ({
|
|
32
|
+
selectCurrency: pt('form.selectCurrency'),
|
|
33
|
+
search: pt('form.search'),
|
|
34
|
+
}), [pt]);
|
|
35
|
+
|
|
26
36
|
return (
|
|
27
37
|
<Combobox
|
|
28
38
|
options={options}
|
|
29
39
|
value={value}
|
|
30
40
|
onValueChange={onChange}
|
|
31
|
-
placeholder={placeholder}
|
|
32
|
-
searchPlaceholder=
|
|
41
|
+
placeholder={placeholder || labels.selectCurrency}
|
|
42
|
+
searchPlaceholder={labels.search}
|
|
33
43
|
disabled={disabled}
|
|
34
44
|
className="h-14"
|
|
35
45
|
renderOption={(option) => (
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import moment from 'moment';
|
|
20
20
|
import useSWR from 'swr';
|
|
21
21
|
|
|
22
|
+
import { usePaymentsT } from '../i18n';
|
|
22
23
|
import {
|
|
23
24
|
Button,
|
|
24
25
|
CopyButton,
|
|
@@ -50,37 +51,32 @@ interface PaymentSheetProps {
|
|
|
50
51
|
// Status Config
|
|
51
52
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
52
53
|
|
|
53
|
-
const statusConfig: Record<string, { icon: any; color: string; bg: string;
|
|
54
|
+
const statusConfig: Record<string, { icon: any; color: string; bg: string; animate?: boolean }> = {
|
|
54
55
|
pending: {
|
|
55
56
|
icon: Clock,
|
|
56
57
|
color: 'text-yellow-500',
|
|
57
58
|
bg: 'bg-yellow-500/10',
|
|
58
|
-
label: 'Waiting for payment',
|
|
59
59
|
},
|
|
60
60
|
confirming: {
|
|
61
61
|
icon: RefreshCw,
|
|
62
62
|
color: 'text-blue-500',
|
|
63
63
|
bg: 'bg-blue-500/10',
|
|
64
|
-
label: 'Confirming',
|
|
65
64
|
animate: true,
|
|
66
65
|
},
|
|
67
66
|
completed: {
|
|
68
67
|
icon: CheckCircle2,
|
|
69
68
|
color: 'text-green-500',
|
|
70
69
|
bg: 'bg-green-500/10',
|
|
71
|
-
label: 'Completed',
|
|
72
70
|
},
|
|
73
71
|
failed: {
|
|
74
72
|
icon: XCircle,
|
|
75
73
|
color: 'text-red-500',
|
|
76
74
|
bg: 'bg-red-500/10',
|
|
77
|
-
label: 'Failed',
|
|
78
75
|
},
|
|
79
76
|
expired: {
|
|
80
77
|
icon: AlertCircle,
|
|
81
78
|
color: 'text-muted-foreground',
|
|
82
79
|
bg: 'bg-muted',
|
|
83
|
-
label: 'Expired',
|
|
84
80
|
},
|
|
85
81
|
};
|
|
86
82
|
|
|
@@ -89,9 +85,47 @@ const statusConfig: Record<string, { icon: any; color: string; bg: string; label
|
|
|
89
85
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
90
86
|
|
|
91
87
|
export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: PaymentSheetProps) {
|
|
88
|
+
const pt = usePaymentsT();
|
|
92
89
|
const { getPaymentDetails } = useWallet();
|
|
93
90
|
const [timeLeft, setTimeLeft] = useState<string>('');
|
|
94
91
|
|
|
92
|
+
// Prepare labels before render
|
|
93
|
+
const labels = useMemo(() => ({
|
|
94
|
+
title: pt('sheets.paymentTitle'),
|
|
95
|
+
// Status labels
|
|
96
|
+
waiting: pt('paymentStatus.waiting'),
|
|
97
|
+
confirming: pt('paymentStatus.confirming'),
|
|
98
|
+
completed: pt('paymentStatus.completed'),
|
|
99
|
+
failed: pt('paymentStatus.failed'),
|
|
100
|
+
expired: pt('paymentStatus.expired'),
|
|
101
|
+
paymentExpired: pt('paymentStatus.paymentExpired'),
|
|
102
|
+
// Descriptions
|
|
103
|
+
sendCrypto: pt('paymentDescriptions.sendCrypto'),
|
|
104
|
+
expiredDesc: pt('paymentDescriptions.expired'),
|
|
105
|
+
completedDesc: pt('paymentDescriptions.completed'),
|
|
106
|
+
failedDesc: pt('paymentDescriptions.failed'),
|
|
107
|
+
confirmingDesc: pt('paymentDescriptions.confirming'),
|
|
108
|
+
createNew: pt('paymentDescriptions.createNew'),
|
|
109
|
+
// Form
|
|
110
|
+
amountToSend: pt('form.amountToSend'),
|
|
111
|
+
equivalent: pt('form.equivalent'),
|
|
112
|
+
network: pt('form.network'),
|
|
113
|
+
paymentAddress: pt('form.paymentAddress'),
|
|
114
|
+
transactionHash: pt('form.transactionHash'),
|
|
115
|
+
paymentId: pt('form.paymentId'),
|
|
116
|
+
orderId: pt('form.orderId'),
|
|
117
|
+
created: pt('form.created'),
|
|
118
|
+
// Actions
|
|
119
|
+
tryAgain: pt('actions.tryAgain'),
|
|
120
|
+
refreshStatus: pt('actions.refreshStatus'),
|
|
121
|
+
createNewPayment: pt('actions.createNewPayment'),
|
|
122
|
+
openInPaymentProvider: pt('actions.openInPaymentProvider'),
|
|
123
|
+
// Withdraw
|
|
124
|
+
expiresIn: pt('withdraw.expiresIn'),
|
|
125
|
+
// Messages
|
|
126
|
+
failedToLoadPayment: pt('messages.failedToLoadPayment'),
|
|
127
|
+
}), [pt]);
|
|
128
|
+
|
|
95
129
|
// Fetch payment details when sheet is open
|
|
96
130
|
const { data: payment, isLoading, error, mutate } = useSWR<PaymentDetail>(
|
|
97
131
|
open && paymentId ? ['payment-details', paymentId] : null,
|
|
@@ -145,24 +179,33 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
145
179
|
const isConfirming = status === 'confirming';
|
|
146
180
|
const canPay = isPending && !isExpired;
|
|
147
181
|
|
|
182
|
+
// Status label from translations
|
|
183
|
+
const statusLabels: Record<string, string> = {
|
|
184
|
+
pending: labels.waiting,
|
|
185
|
+
confirming: labels.confirming,
|
|
186
|
+
completed: labels.completed,
|
|
187
|
+
failed: labels.failed,
|
|
188
|
+
expired: labels.expired,
|
|
189
|
+
};
|
|
190
|
+
|
|
148
191
|
// Description text
|
|
149
192
|
let description = '';
|
|
150
|
-
if (canPay) description =
|
|
151
|
-
else if (isExpired) description =
|
|
152
|
-
else if (isCompleted) description =
|
|
153
|
-
else if (isFailed) description =
|
|
154
|
-
else if (isConfirming) description =
|
|
193
|
+
if (canPay) description = labels.sendCrypto;
|
|
194
|
+
else if (isExpired) description = labels.expiredDesc;
|
|
195
|
+
else if (isCompleted) description = labels.completedDesc;
|
|
196
|
+
else if (isFailed) description = labels.failedDesc;
|
|
197
|
+
else if (isConfirming) description = labels.confirmingDesc;
|
|
155
198
|
|
|
156
199
|
// Status badge
|
|
157
200
|
const statusBadge = {
|
|
158
201
|
bg: isExpired ? 'bg-muted' : config.bg,
|
|
159
202
|
iconColor: isExpired ? 'text-muted-foreground' : config.color,
|
|
160
203
|
iconAnimate: config.animate,
|
|
161
|
-
label: isExpired ?
|
|
204
|
+
label: isExpired ? labels.paymentExpired : statusLabels[status],
|
|
162
205
|
subtitle: canPay && timeLeft
|
|
163
|
-
?
|
|
206
|
+
? `${labels.expiresIn} ${timeLeft}`
|
|
164
207
|
: isExpired
|
|
165
|
-
?
|
|
208
|
+
? labels.createNew
|
|
166
209
|
: null,
|
|
167
210
|
};
|
|
168
211
|
|
|
@@ -192,7 +235,7 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
192
235
|
amountUsd,
|
|
193
236
|
createdAt,
|
|
194
237
|
};
|
|
195
|
-
}, [payment, timeLeft]);
|
|
238
|
+
}, [payment, timeLeft, labels]);
|
|
196
239
|
|
|
197
240
|
const { config, canPay, isExpired, description, statusBadge, qrCodeUrl, amountUsd, createdAt } = displayData;
|
|
198
241
|
const StatusIcon = config.icon;
|
|
@@ -201,7 +244,7 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
201
244
|
<ResponsiveSheet open={open} onOpenChange={onOpenChange}>
|
|
202
245
|
<ResponsiveSheetContent className="sm:max-w-lg">
|
|
203
246
|
<ResponsiveSheetHeader>
|
|
204
|
-
<ResponsiveSheetTitle>
|
|
247
|
+
<ResponsiveSheetTitle>{labels.title}</ResponsiveSheetTitle>
|
|
205
248
|
<ResponsiveSheetDescription>{description}</ResponsiveSheetDescription>
|
|
206
249
|
</ResponsiveSheetHeader>
|
|
207
250
|
|
|
@@ -219,8 +262,8 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
219
262
|
{error && (
|
|
220
263
|
<div className="flex flex-col items-center justify-center py-12">
|
|
221
264
|
<XCircle className="h-12 w-12 text-destructive mb-4" />
|
|
222
|
-
<p className="text-sm text-muted-foreground mb-4">
|
|
223
|
-
<Button onClick={() => mutate()}>
|
|
265
|
+
<p className="text-sm text-muted-foreground mb-4">{labels.failedToLoadPayment}</p>
|
|
266
|
+
<Button onClick={() => mutate()}>{labels.tryAgain}</Button>
|
|
224
267
|
</div>
|
|
225
268
|
)}
|
|
226
269
|
|
|
@@ -240,7 +283,7 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
240
283
|
{/* Amount */}
|
|
241
284
|
<div className="bg-muted rounded-xl p-4 space-y-3">
|
|
242
285
|
<div className="flex items-center justify-between">
|
|
243
|
-
<span className="text-muted-foreground">
|
|
286
|
+
<span className="text-muted-foreground">{labels.amountToSend}</span>
|
|
244
287
|
<div className="flex items-center gap-2">
|
|
245
288
|
<TokenIcon symbol={payment.currency_code} size={24} />
|
|
246
289
|
<span className="font-mono font-bold text-lg">
|
|
@@ -249,12 +292,12 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
249
292
|
</div>
|
|
250
293
|
</div>
|
|
251
294
|
<div className="flex items-center justify-between text-sm">
|
|
252
|
-
<span className="text-muted-foreground">
|
|
295
|
+
<span className="text-muted-foreground">{labels.equivalent}</span>
|
|
253
296
|
<span className="font-semibold">{amountUsd}</span>
|
|
254
297
|
</div>
|
|
255
298
|
{payment.currency_network && (
|
|
256
299
|
<div className="flex items-center justify-between text-sm pt-2 border-t">
|
|
257
|
-
<span className="text-muted-foreground">
|
|
300
|
+
<span className="text-muted-foreground">{labels.network}</span>
|
|
258
301
|
<span className="font-medium">{payment.currency_network}</span>
|
|
259
302
|
</div>
|
|
260
303
|
)}
|
|
@@ -270,7 +313,7 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
270
313
|
{/* Payment Address */}
|
|
271
314
|
{payment.pay_address && canPay && (
|
|
272
315
|
<div className="space-y-2">
|
|
273
|
-
<label className="text-sm font-medium">
|
|
316
|
+
<label className="text-sm font-medium">{labels.paymentAddress}</label>
|
|
274
317
|
<div className="flex items-center gap-2">
|
|
275
318
|
<div className="flex-1 p-3 bg-muted rounded-xl font-mono text-sm break-all">
|
|
276
319
|
{payment.pay_address}
|
|
@@ -290,14 +333,14 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
290
333
|
onCreateNew();
|
|
291
334
|
}}
|
|
292
335
|
>
|
|
293
|
-
|
|
336
|
+
{labels.createNewPayment}
|
|
294
337
|
</Button>
|
|
295
338
|
)}
|
|
296
339
|
|
|
297
340
|
{/* Transaction Hash (if completed) */}
|
|
298
341
|
{payment.transaction_hash && (
|
|
299
342
|
<div className="space-y-2">
|
|
300
|
-
<label className="text-sm font-medium">
|
|
343
|
+
<label className="text-sm font-medium">{labels.transactionHash}</label>
|
|
301
344
|
<div className="p-3 bg-muted rounded-xl font-mono text-sm break-all">
|
|
302
345
|
{payment.transaction_hash}
|
|
303
346
|
</div>
|
|
@@ -312,24 +355,24 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
312
355
|
onClick={() => window.open(payment.payment_url!, '_blank')}
|
|
313
356
|
>
|
|
314
357
|
<ExternalLink className="h-4 w-4 mr-2" />
|
|
315
|
-
|
|
358
|
+
{labels.openInPaymentProvider}
|
|
316
359
|
</Button>
|
|
317
360
|
)}
|
|
318
361
|
|
|
319
362
|
{/* Metadata */}
|
|
320
363
|
<div className="space-y-2 text-xs text-muted-foreground pt-4 border-t">
|
|
321
364
|
<div className="flex justify-between">
|
|
322
|
-
<span>
|
|
365
|
+
<span>{labels.paymentId}</span>
|
|
323
366
|
<span className="font-mono">{payment.id}</span>
|
|
324
367
|
</div>
|
|
325
368
|
{payment.internal_payment_id && (
|
|
326
369
|
<div className="flex justify-between">
|
|
327
|
-
<span>
|
|
370
|
+
<span>{labels.orderId}</span>
|
|
328
371
|
<span className="font-mono">{payment.internal_payment_id}</span>
|
|
329
372
|
</div>
|
|
330
373
|
)}
|
|
331
374
|
<div className="flex justify-between">
|
|
332
|
-
<span>
|
|
375
|
+
<span>{labels.created}</span>
|
|
333
376
|
<span>{createdAt}</span>
|
|
334
377
|
</div>
|
|
335
378
|
</div>
|
|
@@ -341,7 +384,7 @@ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: Pay
|
|
|
341
384
|
onClick={() => mutate()}
|
|
342
385
|
>
|
|
343
386
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
344
|
-
|
|
387
|
+
{labels.refreshStatus}
|
|
345
388
|
</Button>
|
|
346
389
|
</div>
|
|
347
390
|
)}
|