@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.
- package/dist/config.cjs +5 -8
- package/dist/config.js +5 -8
- package/dist/hooks.cjs +1 -1
- package/dist/hooks.js +1 -1
- package/dist/index.cjs +1085 -1107
- package/dist/index.d.cts +480 -41
- package/dist/index.d.ts +480 -41
- package/dist/index.js +1037 -1093
- package/package.json +13 -16
- package/src/api/generated/ext_payments/CLAUDE.md +7 -3
- package/src/api/generated/ext_payments/_utils/fetchers/ext_payments__payments.ts +237 -5
- package/src/api/generated/ext_payments/_utils/hooks/ext_payments__payments.ts +71 -3
- package/src/api/generated/ext_payments/_utils/schemas/PaginatedWithdrawalListList.schema.ts +24 -0
- package/src/api/generated/ext_payments/_utils/schemas/PaymentCreateRequest.schema.ts +21 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalCreateRequest.schema.ts +21 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalDetail.schema.ts +42 -0
- package/src/api/generated/ext_payments/_utils/schemas/WithdrawalList.schema.ts +29 -0
- package/src/api/generated/ext_payments/_utils/schemas/index.ts +5 -0
- package/src/api/generated/ext_payments/enums.ts +36 -0
- package/src/api/generated/ext_payments/ext_payments__payments/client.ts +58 -5
- package/src/api/generated/ext_payments/ext_payments__payments/models.ts +141 -0
- package/src/api/generated/ext_payments/schema.json +579 -3
- package/src/components/ActivityItem.tsx +118 -0
- package/src/components/ActivityList.tsx +93 -0
- package/src/components/AddFundsSheet.tsx +258 -0
- package/src/components/BalanceHero.tsx +102 -0
- package/src/components/PaymentSheet.tsx +290 -0
- package/src/components/ResponsiveSheet.tsx +151 -0
- package/src/components/WithdrawSheet.tsx +329 -0
- package/src/components/index.ts +18 -0
- package/src/contexts/WalletContext.tsx +355 -0
- package/src/contexts/index.ts +12 -45
- package/src/index.ts +6 -18
- package/src/contexts/BalancesContext.tsx +0 -63
- package/src/contexts/CurrenciesContext.tsx +0 -64
- package/src/contexts/OverviewContext.tsx +0 -173
- package/src/contexts/PaymentsContext.tsx +0 -122
- package/src/contexts/PaymentsExtensionProvider.tsx +0 -56
- package/src/contexts/README.md +0 -201
- package/src/contexts/RootPaymentsContext.tsx +0 -66
- package/src/contexts/types.ts +0 -40
- package/src/hooks/index.ts +0 -20
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -90
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -274
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -287
- package/src/layouts/PaymentsLayout/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/events.ts +0 -47
- package/src/layouts/PaymentsLayout/index.ts +0 -16
- package/src/layouts/PaymentsLayout/types.ts +0 -6
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -121
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -139
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -21
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -279
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -18
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -260
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -18
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Withdraw Sheet (Apple-style)
|
|
3
|
+
*
|
|
4
|
+
* Responsive: Dialog on desktop, Drawer on mobile
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
10
|
+
import { RefreshCw, AlertCircle } from 'lucide-react';
|
|
11
|
+
import { useForm } from 'react-hook-form';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
Alert,
|
|
17
|
+
AlertDescription,
|
|
18
|
+
Button,
|
|
19
|
+
Combobox,
|
|
20
|
+
Form,
|
|
21
|
+
FormControl,
|
|
22
|
+
FormField,
|
|
23
|
+
FormItem,
|
|
24
|
+
FormLabel,
|
|
25
|
+
FormMessage,
|
|
26
|
+
Input,
|
|
27
|
+
TokenIcon,
|
|
28
|
+
ResponsiveSheet,
|
|
29
|
+
ResponsiveSheetContent,
|
|
30
|
+
ResponsiveSheetDescription,
|
|
31
|
+
ResponsiveSheetHeader,
|
|
32
|
+
ResponsiveSheetTitle,
|
|
33
|
+
} from '@djangocfg/ui-core';
|
|
34
|
+
|
|
35
|
+
import { useWallet } from '../contexts/WalletContext';
|
|
36
|
+
import type { WithdrawalDetail } from '../api/generated/ext_payments/_utils/schemas';
|
|
37
|
+
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Schema
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const WithdrawSchema = z.object({
|
|
43
|
+
amount: z.number().min(10, 'Minimum $10.00'),
|
|
44
|
+
currency: z.string().min(1, 'Select a currency'),
|
|
45
|
+
wallet_address: z.string().min(26, 'Invalid wallet address'),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
type WithdrawForm = z.infer<typeof WithdrawSchema>;
|
|
49
|
+
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
// Props
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
interface WithdrawSheetProps {
|
|
55
|
+
open: boolean;
|
|
56
|
+
onOpenChange: (open: boolean) => void;
|
|
57
|
+
onSuccess?: (withdrawal: WithdrawalDetail) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
// Fee Config
|
|
62
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
const SERVICE_FEE_PERCENT = 0.01; // 1%
|
|
65
|
+
const NETWORK_FEE_USD = 1.00; // $1
|
|
66
|
+
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
// Component
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
export function WithdrawSheet({ open, onOpenChange, onSuccess }: WithdrawSheetProps) {
|
|
72
|
+
const { currencies, isLoadingCurrencies, withdraw, balanceAmount } = useWallet();
|
|
73
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
74
|
+
const [error, setError] = useState<string | null>(null);
|
|
75
|
+
|
|
76
|
+
const form = useForm<WithdrawForm>({
|
|
77
|
+
resolver: zodResolver(WithdrawSchema),
|
|
78
|
+
defaultValues: {
|
|
79
|
+
amount: 10,
|
|
80
|
+
currency: '',
|
|
81
|
+
wallet_address: '',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Currency options for combobox
|
|
86
|
+
const currencyOptions = useMemo(() => {
|
|
87
|
+
return currencies.map((c) => ({
|
|
88
|
+
value: c.code,
|
|
89
|
+
label: c.network ? `${c.code} (${c.network})` : c.code,
|
|
90
|
+
rate: c.rate,
|
|
91
|
+
network: c.network,
|
|
92
|
+
}));
|
|
93
|
+
}, [currencies]);
|
|
94
|
+
|
|
95
|
+
// Set default currency when loaded
|
|
96
|
+
useMemo(() => {
|
|
97
|
+
if (currencyOptions.length > 0 && !form.getValues('currency')) {
|
|
98
|
+
const usdt = currencyOptions.find(c => c.value.includes('USDT'));
|
|
99
|
+
form.setValue('currency', usdt?.value || currencyOptions[0].value);
|
|
100
|
+
}
|
|
101
|
+
}, [currencyOptions, form]);
|
|
102
|
+
|
|
103
|
+
// Calculate fees and final amount
|
|
104
|
+
const selectedCurrency = currencyOptions.find(c => c.value === form.watch('currency'));
|
|
105
|
+
const amount = form.watch('amount') || 0;
|
|
106
|
+
|
|
107
|
+
const feeBreakdown = useMemo(() => {
|
|
108
|
+
const serviceFee = amount * SERVICE_FEE_PERCENT;
|
|
109
|
+
const networkFee = NETWORK_FEE_USD;
|
|
110
|
+
const totalFee = serviceFee + networkFee;
|
|
111
|
+
const finalAmount = Math.max(0, amount - totalFee);
|
|
112
|
+
const cryptoAmount = selectedCurrency?.rate ? finalAmount / selectedCurrency.rate : null;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
serviceFee,
|
|
116
|
+
networkFee,
|
|
117
|
+
totalFee,
|
|
118
|
+
finalAmount,
|
|
119
|
+
cryptoAmount,
|
|
120
|
+
};
|
|
121
|
+
}, [amount, selectedCurrency]);
|
|
122
|
+
|
|
123
|
+
// Check if user has enough balance
|
|
124
|
+
const insufficientBalance = amount > balanceAmount;
|
|
125
|
+
|
|
126
|
+
// Handle submit
|
|
127
|
+
const handleSubmit = useCallback(async (data: WithdrawForm) => {
|
|
128
|
+
try {
|
|
129
|
+
setIsSubmitting(true);
|
|
130
|
+
setError(null);
|
|
131
|
+
|
|
132
|
+
const result = await withdraw({
|
|
133
|
+
amount_usd: String(data.amount),
|
|
134
|
+
currency_code: data.currency,
|
|
135
|
+
wallet_address: data.wallet_address,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
form.reset();
|
|
139
|
+
onOpenChange(false);
|
|
140
|
+
onSuccess?.(result);
|
|
141
|
+
} catch (err: any) {
|
|
142
|
+
const message = err?.response?.data?.error
|
|
143
|
+
|| err?.response?.data?.message
|
|
144
|
+
|| err?.response?.data?.detail
|
|
145
|
+
|| err?.message
|
|
146
|
+
|| 'Failed to create withdrawal request';
|
|
147
|
+
setError(message);
|
|
148
|
+
} finally {
|
|
149
|
+
setIsSubmitting(false);
|
|
150
|
+
}
|
|
151
|
+
}, [withdraw, form, onOpenChange, onSuccess]);
|
|
152
|
+
|
|
153
|
+
// Reset on close
|
|
154
|
+
const handleOpenChange = useCallback((open: boolean) => {
|
|
155
|
+
if (!open) {
|
|
156
|
+
setError(null);
|
|
157
|
+
form.reset();
|
|
158
|
+
}
|
|
159
|
+
onOpenChange(open);
|
|
160
|
+
}, [form, onOpenChange]);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<ResponsiveSheet open={open} onOpenChange={handleOpenChange}>
|
|
164
|
+
<ResponsiveSheetContent className="sm:max-w-md">
|
|
165
|
+
<ResponsiveSheetHeader>
|
|
166
|
+
<ResponsiveSheetTitle>Withdraw</ResponsiveSheetTitle>
|
|
167
|
+
<ResponsiveSheetDescription>
|
|
168
|
+
Withdraw funds to your cryptocurrency wallet
|
|
169
|
+
</ResponsiveSheetDescription>
|
|
170
|
+
</ResponsiveSheetHeader>
|
|
171
|
+
|
|
172
|
+
<Form {...form}>
|
|
173
|
+
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6 p-4 sm:p-0 sm:mt-4">
|
|
174
|
+
{/* Amount Input */}
|
|
175
|
+
<FormField
|
|
176
|
+
control={form.control}
|
|
177
|
+
name="amount"
|
|
178
|
+
render={({ field }) => (
|
|
179
|
+
<FormItem>
|
|
180
|
+
<FormLabel>Amount (USD)</FormLabel>
|
|
181
|
+
<FormControl>
|
|
182
|
+
<div className="relative">
|
|
183
|
+
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground text-lg">
|
|
184
|
+
$
|
|
185
|
+
</span>
|
|
186
|
+
<Input
|
|
187
|
+
type="number"
|
|
188
|
+
step="0.01"
|
|
189
|
+
min="10"
|
|
190
|
+
placeholder="10.00"
|
|
191
|
+
className="pl-8 text-2xl h-14 font-semibold"
|
|
192
|
+
{...field}
|
|
193
|
+
onChange={(e) => field.onChange(parseFloat(e.target.value) || 0)}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</FormControl>
|
|
197
|
+
<FormMessage />
|
|
198
|
+
{insufficientBalance && (
|
|
199
|
+
<p className="text-sm text-destructive mt-1">
|
|
200
|
+
Insufficient balance (Available: ${balanceAmount.toFixed(2)})
|
|
201
|
+
</p>
|
|
202
|
+
)}
|
|
203
|
+
</FormItem>
|
|
204
|
+
)}
|
|
205
|
+
/>
|
|
206
|
+
|
|
207
|
+
{/* Currency Selection */}
|
|
208
|
+
<FormField
|
|
209
|
+
control={form.control}
|
|
210
|
+
name="currency"
|
|
211
|
+
render={({ field }) => (
|
|
212
|
+
<FormItem>
|
|
213
|
+
<FormLabel>Withdraw as</FormLabel>
|
|
214
|
+
<FormControl>
|
|
215
|
+
<Combobox
|
|
216
|
+
options={currencyOptions}
|
|
217
|
+
value={field.value}
|
|
218
|
+
onValueChange={field.onChange}
|
|
219
|
+
placeholder="Select currency..."
|
|
220
|
+
searchPlaceholder="Search..."
|
|
221
|
+
disabled={isLoadingCurrencies}
|
|
222
|
+
className="h-14"
|
|
223
|
+
renderOption={(option) => (
|
|
224
|
+
<div className="flex items-center gap-3 flex-1">
|
|
225
|
+
<TokenIcon symbol={option.value} size={24} />
|
|
226
|
+
<span className="font-medium">{option.label}</span>
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
renderValue={(option) => option && (
|
|
230
|
+
<div className="flex items-center gap-3">
|
|
231
|
+
<TokenIcon symbol={option.value} size={24} />
|
|
232
|
+
<span className="font-medium">{option.label}</span>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
/>
|
|
236
|
+
</FormControl>
|
|
237
|
+
<FormMessage />
|
|
238
|
+
</FormItem>
|
|
239
|
+
)}
|
|
240
|
+
/>
|
|
241
|
+
|
|
242
|
+
{/* Wallet Address */}
|
|
243
|
+
<FormField
|
|
244
|
+
control={form.control}
|
|
245
|
+
name="wallet_address"
|
|
246
|
+
render={({ field }) => (
|
|
247
|
+
<FormItem>
|
|
248
|
+
<FormLabel>Wallet Address</FormLabel>
|
|
249
|
+
<FormControl>
|
|
250
|
+
<Input
|
|
251
|
+
placeholder="Enter your wallet address"
|
|
252
|
+
className="font-mono text-sm"
|
|
253
|
+
{...field}
|
|
254
|
+
/>
|
|
255
|
+
</FormControl>
|
|
256
|
+
<FormMessage />
|
|
257
|
+
</FormItem>
|
|
258
|
+
)}
|
|
259
|
+
/>
|
|
260
|
+
|
|
261
|
+
{/* Fee Breakdown */}
|
|
262
|
+
{amount >= 10 && selectedCurrency && (
|
|
263
|
+
<div className="bg-muted rounded-xl p-4 space-y-2">
|
|
264
|
+
<div className="flex items-center justify-between text-sm">
|
|
265
|
+
<span className="text-muted-foreground">Amount</span>
|
|
266
|
+
<span>${amount.toFixed(2)}</span>
|
|
267
|
+
</div>
|
|
268
|
+
<div className="flex items-center justify-between text-sm">
|
|
269
|
+
<span className="text-muted-foreground">Service fee (1%)</span>
|
|
270
|
+
<span className="text-destructive">-${feeBreakdown.serviceFee.toFixed(2)}</span>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="flex items-center justify-between text-sm">
|
|
273
|
+
<span className="text-muted-foreground">Network fee</span>
|
|
274
|
+
<span className="text-destructive">-${feeBreakdown.networkFee.toFixed(2)}</span>
|
|
275
|
+
</div>
|
|
276
|
+
<div className="border-t pt-2 mt-2">
|
|
277
|
+
<div className="flex items-center justify-between">
|
|
278
|
+
<span className="font-medium">You will receive</span>
|
|
279
|
+
<div className="text-right">
|
|
280
|
+
<div className="font-semibold">${feeBreakdown.finalAmount.toFixed(2)}</div>
|
|
281
|
+
{feeBreakdown.cryptoAmount !== null && (
|
|
282
|
+
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
283
|
+
<TokenIcon symbol={selectedCurrency.value} size={16} />
|
|
284
|
+
<span className="font-mono">{feeBreakdown.cryptoAmount.toFixed(8)}</span>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
|
|
293
|
+
{/* Warning */}
|
|
294
|
+
<Alert>
|
|
295
|
+
<AlertCircle className="h-4 w-4" />
|
|
296
|
+
<AlertDescription>
|
|
297
|
+
Withdrawal requests require admin approval. Processing may take 24-48 hours.
|
|
298
|
+
</AlertDescription>
|
|
299
|
+
</Alert>
|
|
300
|
+
|
|
301
|
+
{/* Error */}
|
|
302
|
+
{error && (
|
|
303
|
+
<Alert variant="destructive">
|
|
304
|
+
<AlertDescription>{error}</AlertDescription>
|
|
305
|
+
</Alert>
|
|
306
|
+
)}
|
|
307
|
+
|
|
308
|
+
{/* Submit Button */}
|
|
309
|
+
<Button
|
|
310
|
+
type="submit"
|
|
311
|
+
size="lg"
|
|
312
|
+
className="w-full h-14 text-lg rounded-xl"
|
|
313
|
+
disabled={isSubmitting || currencyOptions.length === 0 || insufficientBalance || feeBreakdown.finalAmount <= 0}
|
|
314
|
+
>
|
|
315
|
+
{isSubmitting ? (
|
|
316
|
+
<>
|
|
317
|
+
<RefreshCw className="h-5 w-5 mr-2 animate-spin" />
|
|
318
|
+
Submitting...
|
|
319
|
+
</>
|
|
320
|
+
) : (
|
|
321
|
+
'Request Withdrawal'
|
|
322
|
+
)}
|
|
323
|
+
</Button>
|
|
324
|
+
</form>
|
|
325
|
+
</Form>
|
|
326
|
+
</ResponsiveSheetContent>
|
|
327
|
+
</ResponsiveSheet>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Components (Apple-style)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { BalanceHero } from './BalanceHero';
|
|
6
|
+
export { ActivityList } from './ActivityList';
|
|
7
|
+
export { ActivityItem } from './ActivityItem';
|
|
8
|
+
export { AddFundsSheet } from './AddFundsSheet';
|
|
9
|
+
export { WithdrawSheet } from './WithdrawSheet';
|
|
10
|
+
export { PaymentSheet } from './PaymentSheet';
|
|
11
|
+
export {
|
|
12
|
+
ResponsiveSheet,
|
|
13
|
+
ResponsiveSheetContent,
|
|
14
|
+
ResponsiveSheetHeader,
|
|
15
|
+
ResponsiveSheetTitle,
|
|
16
|
+
ResponsiveSheetDescription,
|
|
17
|
+
ResponsiveSheetFooter,
|
|
18
|
+
} from './ResponsiveSheet';
|