@djangocfg/ext-payments 1.0.13 → 1.0.17

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 (59) hide show
  1. package/dist/config.cjs +5 -8
  2. package/dist/config.js +5 -8
  3. package/dist/hooks.cjs +1 -1
  4. package/dist/hooks.js +1 -1
  5. package/dist/index.cjs +1085 -1107
  6. package/dist/index.d.cts +480 -41
  7. package/dist/index.d.ts +480 -41
  8. package/dist/index.js +1037 -1093
  9. package/package.json +13 -16
  10. package/src/api/generated/ext_payments/CLAUDE.md +7 -3
  11. package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +237 -5
  12. package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +71 -3
  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/WithdrawalCreateRequest.schema.ts +21 -0
  16. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema.ts +42 -0
  17. package/src/api/generated/ext_payments/_utils/schemas/WithdrawalList.schema.ts +29 -0
  18. package/src/api/generated/ext_payments/_utils/schemas/index.ts +5 -0
  19. package/src/api/generated/ext_payments/enums.ts +36 -0
  20. package/src/api/generated/ext_payments/ext_payments__payments/client.ts +58 -5
  21. package/src/api/generated/ext_payments/ext_payments__payments/models.ts +141 -0
  22. package/src/api/generated/ext_payments/schema.json +579 -3
  23. package/src/components/ActivityItem.tsx +118 -0
  24. package/src/components/ActivityList.tsx +93 -0
  25. package/src/components/AddFundsSheet.tsx +258 -0
  26. package/src/components/BalanceHero.tsx +102 -0
  27. package/src/components/PaymentSheet.tsx +290 -0
  28. package/src/components/ResponsiveSheet.tsx +151 -0
  29. package/src/components/WithdrawSheet.tsx +329 -0
  30. package/src/components/index.ts +18 -0
  31. package/src/contexts/WalletContext.tsx +355 -0
  32. package/src/contexts/index.ts +12 -45
  33. package/src/index.ts +6 -18
  34. package/src/contexts/BalancesContext.tsx +0 -63
  35. package/src/contexts/CurrenciesContext.tsx +0 -64
  36. package/src/contexts/OverviewContext.tsx +0 -173
  37. package/src/contexts/PaymentsContext.tsx +0 -122
  38. package/src/contexts/PaymentsExtensionProvider.tsx +0 -56
  39. package/src/contexts/README.md +0 -201
  40. package/src/contexts/RootPaymentsContext.tsx +0 -66
  41. package/src/contexts/types.ts +0 -40
  42. package/src/hooks/index.ts +0 -20
  43. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -90
  44. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -274
  45. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -287
  46. package/src/layouts/PaymentsLayout/components/index.ts +0 -2
  47. package/src/layouts/PaymentsLayout/events.ts +0 -47
  48. package/src/layouts/PaymentsLayout/index.ts +0 -16
  49. package/src/layouts/PaymentsLayout/types.ts +0 -6
  50. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -121
  51. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -139
  52. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  53. package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -21
  54. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -279
  55. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  56. package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -18
  57. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -260
  58. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
  59. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -18
@@ -0,0 +1,290 @@
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
+ }
47
+
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+ // Status Config
50
+ // ─────────────────────────────────────────────────────────────────────────────
51
+
52
+ const statusConfig: Record<string, { icon: any; color: string; bg: string; label: string; animate?: boolean }> = {
53
+ pending: {
54
+ icon: Clock,
55
+ color: 'text-yellow-500',
56
+ bg: 'bg-yellow-500/10',
57
+ label: 'Waiting for payment',
58
+ },
59
+ confirming: {
60
+ icon: RefreshCw,
61
+ color: 'text-blue-500',
62
+ bg: 'bg-blue-500/10',
63
+ label: 'Confirming',
64
+ animate: true,
65
+ },
66
+ completed: {
67
+ icon: CheckCircle2,
68
+ color: 'text-green-500',
69
+ bg: 'bg-green-500/10',
70
+ label: 'Completed',
71
+ },
72
+ failed: {
73
+ icon: XCircle,
74
+ color: 'text-red-500',
75
+ bg: 'bg-red-500/10',
76
+ label: 'Failed',
77
+ },
78
+ expired: {
79
+ icon: AlertCircle,
80
+ color: 'text-muted-foreground',
81
+ bg: 'bg-muted',
82
+ label: 'Expired',
83
+ },
84
+ };
85
+
86
+ // ─────────────────────────────────────────────────────────────────────────────
87
+ // Component
88
+ // ─────────────────────────────────────────────────────────────────────────────
89
+
90
+ export function PaymentSheet({ paymentId, open, onOpenChange }: PaymentSheetProps) {
91
+ const { getPaymentDetails } = useWallet();
92
+ const [timeLeft, setTimeLeft] = useState<string>('');
93
+
94
+ // Fetch payment details when sheet is open
95
+ const { data: payment, isLoading, error, mutate } = useSWR<PaymentDetail>(
96
+ open && paymentId ? ['payment-details', paymentId] : null,
97
+ () => getPaymentDetails(paymentId!),
98
+ { refreshInterval: 10000 }
99
+ );
100
+
101
+ // Calculate time left
102
+ useEffect(() => {
103
+ if (!payment?.expires_at) return;
104
+
105
+ const updateTimeLeft = () => {
106
+ const now = moment();
107
+ const expires = moment.utc(payment.expires_at!);
108
+ const diff = expires.diff(now);
109
+
110
+ if (diff <= 0) {
111
+ setTimeLeft('Expired');
112
+ return;
113
+ }
114
+
115
+ const duration = moment.duration(diff);
116
+ const hours = Math.floor(duration.asHours());
117
+ const minutes = duration.minutes();
118
+ const seconds = duration.seconds();
119
+
120
+ setTimeLeft(`${hours}h ${minutes}m ${seconds}s`);
121
+ };
122
+
123
+ updateTimeLeft();
124
+ const interval = setInterval(updateTimeLeft, 1000);
125
+ return () => clearInterval(interval);
126
+ }, [payment?.expires_at]);
127
+
128
+ // Map status
129
+ const status = useMemo(() => {
130
+ const s = payment?.status?.toLowerCase();
131
+ if (s === 'completed' || s === 'success' || s === 'finished') return 'completed';
132
+ if (s === 'confirming' || s === 'partially_paid') return 'confirming';
133
+ if (s === 'expired') return 'expired';
134
+ if (s === 'failed' || s === 'error' || s === 'cancelled') return 'failed';
135
+ return 'pending';
136
+ }, [payment?.status]);
137
+
138
+ const config = statusConfig[status];
139
+ const StatusIcon = config.icon;
140
+
141
+ // QR code URL
142
+ const qrCodeUrl = payment?.pay_address
143
+ ? `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(payment.pay_address)}`
144
+ : null;
145
+
146
+ const isPending = status === 'pending';
147
+
148
+ return (
149
+ <ResponsiveSheet open={open} onOpenChange={onOpenChange}>
150
+ <ResponsiveSheetContent className="sm:max-w-lg">
151
+ <ResponsiveSheetHeader>
152
+ <ResponsiveSheetTitle>Payment Details</ResponsiveSheetTitle>
153
+ <ResponsiveSheetDescription>
154
+ {isPending ? 'Send cryptocurrency to complete payment' : 'Payment information'}
155
+ </ResponsiveSheetDescription>
156
+ </ResponsiveSheetHeader>
157
+
158
+ <div className="p-4 sm:p-0 sm:mt-4 overflow-y-auto max-h-[70vh]">
159
+ {isLoading && (
160
+ <div className="space-y-6">
161
+ <div className="flex items-center justify-center">
162
+ <Skeleton className="h-48 w-48 rounded-xl" />
163
+ </div>
164
+ <Skeleton className="h-12 w-full" />
165
+ <Skeleton className="h-24 w-full" />
166
+ </div>
167
+ )}
168
+
169
+ {error && (
170
+ <div className="flex flex-col items-center justify-center py-12">
171
+ <XCircle className="h-12 w-12 text-destructive mb-4" />
172
+ <p className="text-sm text-muted-foreground mb-4">Failed to load payment</p>
173
+ <Button onClick={() => mutate()}>Try Again</Button>
174
+ </div>
175
+ )}
176
+
177
+ {payment && !isLoading && (
178
+ <div className="space-y-6">
179
+ {/* Status Badge */}
180
+ <div className={cn('flex items-center gap-3 p-4 rounded-xl', config.bg)}>
181
+ <StatusIcon className={cn('h-6 w-6', config.color, config.animate && 'animate-spin')} />
182
+ <div className="flex-1">
183
+ <div className="font-semibold">{config.label}</div>
184
+ {isPending && timeLeft && (
185
+ <div className="text-sm text-muted-foreground">
186
+ Expires in {timeLeft}
187
+ </div>
188
+ )}
189
+ </div>
190
+ </div>
191
+
192
+ {/* Amount */}
193
+ <div className="bg-muted rounded-xl p-4 space-y-3">
194
+ <div className="flex items-center justify-between">
195
+ <span className="text-muted-foreground">Amount to send</span>
196
+ <div className="flex items-center gap-2">
197
+ <TokenIcon symbol={payment.currency_code} size={24} />
198
+ <span className="font-mono font-bold text-lg">
199
+ {payment.pay_amount} {payment.currency_code}
200
+ </span>
201
+ </div>
202
+ </div>
203
+ <div className="flex items-center justify-between text-sm">
204
+ <span className="text-muted-foreground">Equivalent</span>
205
+ <span className="font-semibold">${parseFloat(payment.amount_usd).toFixed(2)} USD</span>
206
+ </div>
207
+ {payment.currency_network && (
208
+ <div className="flex items-center justify-between text-sm pt-2 border-t">
209
+ <span className="text-muted-foreground">Network</span>
210
+ <span className="font-medium">{payment.currency_network}</span>
211
+ </div>
212
+ )}
213
+ </div>
214
+
215
+ {/* QR Code */}
216
+ {qrCodeUrl && isPending && (
217
+ <div className="flex justify-center p-6 bg-white rounded-xl">
218
+ <img src={qrCodeUrl} alt="Payment QR Code" className="w-48 h-48" />
219
+ </div>
220
+ )}
221
+
222
+ {/* Payment Address */}
223
+ {payment.pay_address && isPending && (
224
+ <div className="space-y-2">
225
+ <label className="text-sm font-medium">Payment Address</label>
226
+ <div className="flex items-center gap-2">
227
+ <div className="flex-1 p-3 bg-muted rounded-xl font-mono text-sm break-all">
228
+ {payment.pay_address}
229
+ </div>
230
+ <CopyButton value={payment.pay_address} variant="outline" className="shrink-0" />
231
+ </div>
232
+ </div>
233
+ )}
234
+
235
+ {/* Transaction Hash (if completed) */}
236
+ {payment.transaction_hash && (
237
+ <div className="space-y-2">
238
+ <label className="text-sm font-medium">Transaction Hash</label>
239
+ <div className="p-3 bg-muted rounded-xl font-mono text-sm break-all">
240
+ {payment.transaction_hash}
241
+ </div>
242
+ </div>
243
+ )}
244
+
245
+ {/* External Link */}
246
+ {payment.payment_url && isPending && (
247
+ <Button
248
+ variant="outline"
249
+ className="w-full"
250
+ onClick={() => window.open(payment.payment_url!, '_blank')}
251
+ >
252
+ <ExternalLink className="h-4 w-4 mr-2" />
253
+ Open in Payment Provider
254
+ </Button>
255
+ )}
256
+
257
+ {/* Metadata */}
258
+ <div className="space-y-2 text-xs text-muted-foreground pt-4 border-t">
259
+ <div className="flex justify-between">
260
+ <span>Payment ID</span>
261
+ <span className="font-mono">{payment.id}</span>
262
+ </div>
263
+ {payment.internal_payment_id && (
264
+ <div className="flex justify-between">
265
+ <span>Order #</span>
266
+ <span className="font-mono">{payment.internal_payment_id}</span>
267
+ </div>
268
+ )}
269
+ <div className="flex justify-between">
270
+ <span>Created</span>
271
+ <span>{moment.utc(payment.created_at).local().format('MMM D, YYYY HH:mm')}</span>
272
+ </div>
273
+ </div>
274
+
275
+ {/* Refresh Button */}
276
+ <Button
277
+ variant="ghost"
278
+ className="w-full"
279
+ onClick={() => mutate()}
280
+ >
281
+ <RefreshCw className="h-4 w-4 mr-2" />
282
+ Refresh Status
283
+ </Button>
284
+ </div>
285
+ )}
286
+ </div>
287
+ </ResponsiveSheetContent>
288
+ </ResponsiveSheet>
289
+ );
290
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Responsive Sheet (Apple-style)
3
+ *
4
+ * Dialog on desktop, Drawer (pull-down) on mobile.
5
+ * Uses useIsMobile hook for responsive behavior.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import * as React from 'react';
11
+
12
+ import {
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogFooter,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ Drawer,
20
+ DrawerContent,
21
+ DrawerDescription,
22
+ DrawerFooter,
23
+ DrawerHeader,
24
+ DrawerTitle,
25
+ useIsMobile,
26
+ } from '@djangocfg/ui-core';
27
+
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+ // Props
30
+ // ─────────────────────────────────────────────────────────────────────────────
31
+
32
+ interface ResponsiveSheetProps {
33
+ open: boolean;
34
+ onOpenChange: (open: boolean) => void;
35
+ children: React.ReactNode;
36
+ }
37
+
38
+ interface ResponsiveSheetHeaderProps {
39
+ children: React.ReactNode;
40
+ className?: string;
41
+ }
42
+
43
+ interface ResponsiveSheetTitleProps {
44
+ children: React.ReactNode;
45
+ className?: string;
46
+ }
47
+
48
+ interface ResponsiveSheetDescriptionProps {
49
+ children: React.ReactNode;
50
+ className?: string;
51
+ }
52
+
53
+ interface ResponsiveSheetContentProps {
54
+ children: React.ReactNode;
55
+ className?: string;
56
+ }
57
+
58
+ interface ResponsiveSheetFooterProps {
59
+ children: React.ReactNode;
60
+ className?: string;
61
+ }
62
+
63
+ // ─────────────────────────────────────────────────────────────────────────────
64
+ // Context
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+
67
+ const ResponsiveSheetContext = React.createContext<{ isMobile: boolean }>({ isMobile: false });
68
+
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Components
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+
73
+ export function ResponsiveSheet({ open, onOpenChange, children }: ResponsiveSheetProps) {
74
+ const isMobile = useIsMobile();
75
+
76
+ if (isMobile) {
77
+ return (
78
+ <ResponsiveSheetContext.Provider value={{ isMobile: true }}>
79
+ <Drawer open={open} onOpenChange={onOpenChange}>
80
+ {children}
81
+ </Drawer>
82
+ </ResponsiveSheetContext.Provider>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <ResponsiveSheetContext.Provider value={{ isMobile: false }}>
88
+ <Dialog open={open} onOpenChange={onOpenChange}>
89
+ {children}
90
+ </Dialog>
91
+ </ResponsiveSheetContext.Provider>
92
+ );
93
+ }
94
+
95
+ export function ResponsiveSheetContent({ children, className }: ResponsiveSheetContentProps) {
96
+ const { isMobile } = React.useContext(ResponsiveSheetContext);
97
+
98
+ if (isMobile) {
99
+ return (
100
+ <DrawerContent className={className}>
101
+ {children}
102
+ </DrawerContent>
103
+ );
104
+ }
105
+
106
+ return (
107
+ <DialogContent className={className}>
108
+ {children}
109
+ </DialogContent>
110
+ );
111
+ }
112
+
113
+ export function ResponsiveSheetHeader({ children, className }: ResponsiveSheetHeaderProps) {
114
+ const { isMobile } = React.useContext(ResponsiveSheetContext);
115
+
116
+ if (isMobile) {
117
+ return <DrawerHeader className={className}>{children}</DrawerHeader>;
118
+ }
119
+
120
+ return <DialogHeader className={className}>{children}</DialogHeader>;
121
+ }
122
+
123
+ export function ResponsiveSheetTitle({ children, className }: ResponsiveSheetTitleProps) {
124
+ const { isMobile } = React.useContext(ResponsiveSheetContext);
125
+
126
+ if (isMobile) {
127
+ return <DrawerTitle className={className}>{children}</DrawerTitle>;
128
+ }
129
+
130
+ return <DialogTitle className={className}>{children}</DialogTitle>;
131
+ }
132
+
133
+ export function ResponsiveSheetDescription({ children, className }: ResponsiveSheetDescriptionProps) {
134
+ const { isMobile } = React.useContext(ResponsiveSheetContext);
135
+
136
+ if (isMobile) {
137
+ return <DrawerDescription className={className}>{children}</DrawerDescription>;
138
+ }
139
+
140
+ return <DialogDescription className={className}>{children}</DialogDescription>;
141
+ }
142
+
143
+ export function ResponsiveSheetFooter({ children, className }: ResponsiveSheetFooterProps) {
144
+ const { isMobile } = React.useContext(ResponsiveSheetContext);
145
+
146
+ if (isMobile) {
147
+ return <DrawerFooter className={className}>{children}</DrawerFooter>;
148
+ }
149
+
150
+ return <DialogFooter className={className}>{children}</DialogFooter>;
151
+ }