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