@djangocfg/ext-payments 1.0.14 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/config.cjs +5 -8
  2. package/dist/config.js +5 -8
  3. package/dist/index.cjs +1906 -1043
  4. package/dist/index.d.cts +644 -59
  5. package/dist/index.d.ts +644 -59
  6. package/dist/index.js +1886 -1040
  7. package/package.json +13 -16
  8. package/src/WalletPage.tsx +100 -0
  9. package/src/api/generated/ext_payments/CLAUDE.md +10 -4
  10. package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +268 -5
  11. package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +102 -3
  12. package/src/api/generated/ext_payments/_utils/schemas/Balance.schema.ts +1 -1
  13. package/src/api/generated/ext_payments/_utils/schemas/PaginatedWithdrawalListList.schema.ts +24 -0
  14. package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateRequest.schema.ts +21 -0
  15. package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateResponse.schema.ts +22 -0
  16. package/src/api/generated/ext_payments/_utils/schemas/PaymentDetail.schema.ts +3 -3
  17. package/src/api/generated/ext_payments/_utils/schemas/PaymentList.schema.ts +2 -2
  18. package/src/api/generated/ext_payments/_utils/schemas/Transaction.schema.ts +1 -1
  19. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCancelResponse.schema.ts +22 -0
  20. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateRequest.schema.ts +21 -0
  21. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateResponse.schema.ts +22 -0
  22. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema.ts +42 -0
  23. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalList.schema.ts +29 -0
  24. package/src/api/generated/ext_payments/_utils/schemas/index.ts +8 -0
  25. package/src/api/generated/ext_payments/client.ts +1 -1
  26. package/src/api/generated/ext_payments/enums.ts +36 -0
  27. package/src/api/generated/ext_payments/ext_payments__payments/client.ts +104 -6
  28. package/src/api/generated/ext_payments/ext_payments__payments/models.ts +168 -8
  29. package/src/api/generated/ext_payments/index.ts +1 -1
  30. package/src/api/generated/ext_payments/schema.json +752 -42
  31. package/src/components/ActivityItem.tsx +118 -0
  32. package/src/components/ActivityList.tsx +93 -0
  33. package/src/components/AddFundsSheet.tsx +342 -0
  34. package/src/components/BalanceHero.tsx +102 -0
  35. package/src/components/CurrencyCombobox.tsx +49 -0
  36. package/src/components/PaymentSheet.tsx +352 -0
  37. package/src/components/WithdrawSheet.tsx +355 -0
  38. package/src/components/WithdrawalSheet.tsx +332 -0
  39. package/src/components/index.ts +11 -0
  40. package/src/config.ts +1 -0
  41. package/src/contexts/WalletContext.tsx +356 -0
  42. package/src/contexts/index.ts +13 -42
  43. package/src/contexts/types.ts +43 -37
  44. package/src/hooks/index.ts +3 -20
  45. package/src/hooks/useCurrencyOptions.ts +79 -0
  46. package/src/hooks/useEstimate.ts +113 -0
  47. package/src/hooks/useWithdrawalEstimate.ts +117 -0
  48. package/src/index.ts +9 -18
  49. package/src/types/index.ts +78 -0
  50. package/src/utils/errors.ts +36 -0
  51. package/src/utils/format.ts +65 -0
  52. package/src/utils/index.ts +3 -0
  53. package/src/contexts/BalancesContext.tsx +0 -63
  54. package/src/contexts/CurrenciesContext.tsx +0 -64
  55. package/src/contexts/OverviewContext.tsx +0 -173
  56. package/src/contexts/PaymentsContext.tsx +0 -122
  57. package/src/contexts/PaymentsExtensionProvider.tsx +0 -56
  58. package/src/contexts/README.md +0 -201
  59. package/src/contexts/RootPaymentsContext.tsx +0 -66
  60. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -90
  61. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -274
  62. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -287
  63. package/src/layouts/PaymentsLayout/components/index.ts +0 -2
  64. package/src/layouts/PaymentsLayout/events.ts +0 -47
  65. package/src/layouts/PaymentsLayout/index.ts +0 -16
  66. package/src/layouts/PaymentsLayout/types.ts +0 -6
  67. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -121
  68. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -139
  69. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  70. package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -21
  71. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -279
  72. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  73. package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -18
  74. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -260
  75. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
  76. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -18
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Currency Combobox with TokenIcon
5
+ */
6
+
7
+ import { Combobox, TokenIcon } from '@djangocfg/ui-core';
8
+ import type { CurrencyOption } from '../types';
9
+
10
+ interface CurrencyComboboxProps {
11
+ options: CurrencyOption[];
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ disabled?: boolean;
15
+ placeholder?: string;
16
+ label?: string;
17
+ }
18
+
19
+ export function CurrencyCombobox({
20
+ options,
21
+ value,
22
+ onChange,
23
+ disabled,
24
+ placeholder = 'Select currency...',
25
+ }: CurrencyComboboxProps) {
26
+ return (
27
+ <Combobox
28
+ options={options}
29
+ value={value}
30
+ onValueChange={onChange}
31
+ placeholder={placeholder}
32
+ searchPlaceholder="Search..."
33
+ disabled={disabled}
34
+ className="h-14"
35
+ renderOption={(option) => (
36
+ <div className="flex items-center gap-3 flex-1">
37
+ <TokenIcon symbol={option.value} size={24} />
38
+ <span className="font-medium">{option.label}</span>
39
+ </div>
40
+ )}
41
+ renderValue={(option) => option && (
42
+ <div className="flex items-center gap-3">
43
+ <TokenIcon symbol={option.value} size={24} />
44
+ <span className="font-medium">{option.label}</span>
45
+ </div>
46
+ )}
47
+ />
48
+ );
49
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Payment Sheet (Apple-style)
3
+ *
4
+ * Responsive: Dialog on desktop, Drawer on mobile
5
+ * Shows payment details with QR code and address
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { useEffect, useState, useMemo } from 'react';
11
+ import {
12
+ AlertCircle,
13
+ CheckCircle2,
14
+ Clock,
15
+ ExternalLink,
16
+ RefreshCw,
17
+ XCircle,
18
+ } from 'lucide-react';
19
+ import moment from 'moment';
20
+ import useSWR from 'swr';
21
+
22
+ import {
23
+ Button,
24
+ CopyButton,
25
+ Skeleton,
26
+ TokenIcon,
27
+ ResponsiveSheet,
28
+ ResponsiveSheetContent,
29
+ ResponsiveSheetDescription,
30
+ ResponsiveSheetHeader,
31
+ ResponsiveSheetTitle,
32
+ } from '@djangocfg/ui-core';
33
+ import { cn } from '@djangocfg/ui-core/lib';
34
+
35
+ import { useWallet } from '../contexts/WalletContext';
36
+ import type { PaymentDetail } from '../api/generated/ext_payments/_utils/schemas';
37
+
38
+ // ─────────────────────────────────────────────────────────────────────────────
39
+ // Props
40
+ // ─────────────────────────────────────────────────────────────────────────────
41
+
42
+ interface PaymentSheetProps {
43
+ paymentId: string | null;
44
+ open: boolean;
45
+ onOpenChange: (open: boolean) => void;
46
+ onCreateNew?: () => void;
47
+ }
48
+
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ // Status Config
51
+ // ─────────────────────────────────────────────────────────────────────────────
52
+
53
+ const statusConfig: Record<string, { icon: any; color: string; bg: string; label: string; animate?: boolean }> = {
54
+ pending: {
55
+ icon: Clock,
56
+ color: 'text-yellow-500',
57
+ bg: 'bg-yellow-500/10',
58
+ label: 'Waiting for payment',
59
+ },
60
+ confirming: {
61
+ icon: RefreshCw,
62
+ color: 'text-blue-500',
63
+ bg: 'bg-blue-500/10',
64
+ label: 'Confirming',
65
+ animate: true,
66
+ },
67
+ completed: {
68
+ icon: CheckCircle2,
69
+ color: 'text-green-500',
70
+ bg: 'bg-green-500/10',
71
+ label: 'Completed',
72
+ },
73
+ failed: {
74
+ icon: XCircle,
75
+ color: 'text-red-500',
76
+ bg: 'bg-red-500/10',
77
+ label: 'Failed',
78
+ },
79
+ expired: {
80
+ icon: AlertCircle,
81
+ color: 'text-muted-foreground',
82
+ bg: 'bg-muted',
83
+ label: 'Expired',
84
+ },
85
+ };
86
+
87
+ // ─────────────────────────────────────────────────────────────────────────────
88
+ // Component
89
+ // ─────────────────────────────────────────────────────────────────────────────
90
+
91
+ export function PaymentSheet({ paymentId, open, onOpenChange, onCreateNew }: PaymentSheetProps) {
92
+ const { getPaymentDetails } = useWallet();
93
+ const [timeLeft, setTimeLeft] = useState<string>('');
94
+
95
+ // Fetch payment details when sheet is open
96
+ const { data: payment, isLoading, error, mutate } = useSWR<PaymentDetail>(
97
+ open && paymentId ? ['payment-details', paymentId] : null,
98
+ () => getPaymentDetails(paymentId!),
99
+ { refreshInterval: 10000 }
100
+ );
101
+
102
+ // Calculate time left
103
+ useEffect(() => {
104
+ if (!payment?.expires_at) return;
105
+
106
+ const updateTimeLeft = () => {
107
+ const now = moment();
108
+ const expires = moment.utc(payment.expires_at!);
109
+ const diff = expires.diff(now);
110
+
111
+ if (diff <= 0) {
112
+ setTimeLeft('Expired');
113
+ return;
114
+ }
115
+
116
+ const duration = moment.duration(diff);
117
+ const hours = Math.floor(duration.asHours());
118
+ const minutes = duration.minutes();
119
+ const seconds = duration.seconds();
120
+
121
+ setTimeLeft(`${hours}h ${minutes}m ${seconds}s`);
122
+ };
123
+
124
+ updateTimeLeft();
125
+ const interval = setInterval(updateTimeLeft, 1000);
126
+ return () => clearInterval(interval);
127
+ }, [payment?.expires_at]);
128
+
129
+ // Prepare all display data before render
130
+ const displayData = useMemo(() => {
131
+ // Map status
132
+ const s = payment?.status?.toLowerCase();
133
+ let status: string;
134
+ if (s === 'completed' || s === 'success' || s === 'finished') status = 'completed';
135
+ else if (s === 'confirming' || s === 'partially_paid') status = 'confirming';
136
+ else if (s === 'expired') status = 'expired';
137
+ else if (s === 'failed' || s === 'error' || s === 'cancelled') status = 'failed';
138
+ else status = 'pending';
139
+
140
+ const config = statusConfig[status];
141
+ const isPending = status === 'pending';
142
+ const isExpired = status === 'expired' || timeLeft === 'Expired';
143
+ const isCompleted = status === 'completed';
144
+ const isFailed = status === 'failed';
145
+ const isConfirming = status === 'confirming';
146
+ const canPay = isPending && !isExpired;
147
+
148
+ // Description text
149
+ let description = '';
150
+ if (canPay) description = 'Send cryptocurrency to complete payment';
151
+ else if (isExpired) description = 'This payment has expired';
152
+ else if (isCompleted) description = 'Payment completed successfully';
153
+ else if (isFailed) description = 'Payment failed';
154
+ else if (isConfirming) description = 'Confirming your payment';
155
+
156
+ // Status badge
157
+ const statusBadge = {
158
+ bg: isExpired ? 'bg-muted' : config.bg,
159
+ iconColor: isExpired ? 'text-muted-foreground' : config.color,
160
+ iconAnimate: config.animate,
161
+ label: isExpired ? 'Payment Expired' : config.label,
162
+ subtitle: canPay && timeLeft
163
+ ? `Expires in ${timeLeft}`
164
+ : isExpired
165
+ ? 'Please create a new payment to continue'
166
+ : null,
167
+ };
168
+
169
+ // QR code URL
170
+ const qrCodeUrl = payment?.pay_address && canPay
171
+ ? `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(payment.pay_address)}`
172
+ : null;
173
+
174
+ // Formatted values
175
+ const amountUsd = payment?.amount_usd ? `$${parseFloat(payment.amount_usd).toFixed(2)} USD` : '';
176
+ const createdAt = payment?.created_at
177
+ ? moment.utc(payment.created_at).local().format('MMM D, YYYY HH:mm')
178
+ : '';
179
+
180
+ return {
181
+ status,
182
+ config,
183
+ isPending,
184
+ isExpired,
185
+ isCompleted,
186
+ isFailed,
187
+ isConfirming,
188
+ canPay,
189
+ description,
190
+ statusBadge,
191
+ qrCodeUrl,
192
+ amountUsd,
193
+ createdAt,
194
+ };
195
+ }, [payment, timeLeft]);
196
+
197
+ const { config, canPay, isExpired, description, statusBadge, qrCodeUrl, amountUsd, createdAt } = displayData;
198
+ const StatusIcon = config.icon;
199
+
200
+ return (
201
+ <ResponsiveSheet open={open} onOpenChange={onOpenChange}>
202
+ <ResponsiveSheetContent className="sm:max-w-lg">
203
+ <ResponsiveSheetHeader>
204
+ <ResponsiveSheetTitle>Payment Details</ResponsiveSheetTitle>
205
+ <ResponsiveSheetDescription>{description}</ResponsiveSheetDescription>
206
+ </ResponsiveSheetHeader>
207
+
208
+ <div className="p-4 sm:p-0 sm:mt-4 overflow-y-auto max-h-[70vh]">
209
+ {isLoading && (
210
+ <div className="space-y-6">
211
+ <div className="flex items-center justify-center">
212
+ <Skeleton className="h-48 w-48 rounded-xl" />
213
+ </div>
214
+ <Skeleton className="h-12 w-full" />
215
+ <Skeleton className="h-24 w-full" />
216
+ </div>
217
+ )}
218
+
219
+ {error && (
220
+ <div className="flex flex-col items-center justify-center py-12">
221
+ <XCircle className="h-12 w-12 text-destructive mb-4" />
222
+ <p className="text-sm text-muted-foreground mb-4">Failed to load payment</p>
223
+ <Button onClick={() => mutate()}>Try Again</Button>
224
+ </div>
225
+ )}
226
+
227
+ {payment && !isLoading && (
228
+ <div className="space-y-6">
229
+ {/* Status Badge */}
230
+ <div className={cn('flex items-center gap-3 p-4 rounded-xl', statusBadge.bg)}>
231
+ <StatusIcon className={cn('h-6 w-6', statusBadge.iconColor, statusBadge.iconAnimate && 'animate-spin')} />
232
+ <div className="flex-1">
233
+ <div className="font-semibold">{statusBadge.label}</div>
234
+ {statusBadge.subtitle && (
235
+ <div className="text-sm text-muted-foreground">{statusBadge.subtitle}</div>
236
+ )}
237
+ </div>
238
+ </div>
239
+
240
+ {/* Amount */}
241
+ <div className="bg-muted rounded-xl p-4 space-y-3">
242
+ <div className="flex items-center justify-between">
243
+ <span className="text-muted-foreground">Amount to send</span>
244
+ <div className="flex items-center gap-2">
245
+ <TokenIcon symbol={payment.currency_code} size={24} />
246
+ <span className="font-mono font-bold text-lg">
247
+ {payment.pay_amount} {payment.currency_code}
248
+ </span>
249
+ </div>
250
+ </div>
251
+ <div className="flex items-center justify-between text-sm">
252
+ <span className="text-muted-foreground">Equivalent</span>
253
+ <span className="font-semibold">{amountUsd}</span>
254
+ </div>
255
+ {payment.currency_network && (
256
+ <div className="flex items-center justify-between text-sm pt-2 border-t">
257
+ <span className="text-muted-foreground">Network</span>
258
+ <span className="font-medium">{payment.currency_network}</span>
259
+ </div>
260
+ )}
261
+ </div>
262
+
263
+ {/* QR Code */}
264
+ {qrCodeUrl && (
265
+ <div className="flex justify-center p-6 bg-white rounded-xl">
266
+ <img src={qrCodeUrl} alt="Payment QR Code" className="w-48 h-48" />
267
+ </div>
268
+ )}
269
+
270
+ {/* Payment Address */}
271
+ {payment.pay_address && canPay && (
272
+ <div className="space-y-2">
273
+ <label className="text-sm font-medium">Payment Address</label>
274
+ <div className="flex items-center gap-2">
275
+ <div className="flex-1 p-3 bg-muted rounded-xl font-mono text-sm break-all">
276
+ {payment.pay_address}
277
+ </div>
278
+ <CopyButton value={payment.pay_address} variant="outline" className="shrink-0" />
279
+ </div>
280
+ </div>
281
+ )}
282
+
283
+ {/* Expired - Create New Payment */}
284
+ {isExpired && onCreateNew && (
285
+ <Button
286
+ size="lg"
287
+ className="w-full"
288
+ onClick={() => {
289
+ onOpenChange(false);
290
+ onCreateNew();
291
+ }}
292
+ >
293
+ Create New Payment
294
+ </Button>
295
+ )}
296
+
297
+ {/* Transaction Hash (if completed) */}
298
+ {payment.transaction_hash && (
299
+ <div className="space-y-2">
300
+ <label className="text-sm font-medium">Transaction Hash</label>
301
+ <div className="p-3 bg-muted rounded-xl font-mono text-sm break-all">
302
+ {payment.transaction_hash}
303
+ </div>
304
+ </div>
305
+ )}
306
+
307
+ {/* External Link */}
308
+ {payment.payment_url && canPay && (
309
+ <Button
310
+ variant="outline"
311
+ className="w-full"
312
+ onClick={() => window.open(payment.payment_url!, '_blank')}
313
+ >
314
+ <ExternalLink className="h-4 w-4 mr-2" />
315
+ Open in Payment Provider
316
+ </Button>
317
+ )}
318
+
319
+ {/* Metadata */}
320
+ <div className="space-y-2 text-xs text-muted-foreground pt-4 border-t">
321
+ <div className="flex justify-between">
322
+ <span>Payment ID</span>
323
+ <span className="font-mono">{payment.id}</span>
324
+ </div>
325
+ {payment.internal_payment_id && (
326
+ <div className="flex justify-between">
327
+ <span>Order #</span>
328
+ <span className="font-mono">{payment.internal_payment_id}</span>
329
+ </div>
330
+ )}
331
+ <div className="flex justify-between">
332
+ <span>Created</span>
333
+ <span>{createdAt}</span>
334
+ </div>
335
+ </div>
336
+
337
+ {/* Refresh Button */}
338
+ <Button
339
+ variant="ghost"
340
+ className="w-full"
341
+ onClick={() => mutate()}
342
+ >
343
+ <RefreshCw className="h-4 w-4 mr-2" />
344
+ Refresh Status
345
+ </Button>
346
+ </div>
347
+ )}
348
+ </div>
349
+ </ResponsiveSheetContent>
350
+ </ResponsiveSheet>
351
+ );
352
+ }