@b3dotfun/sdk 0.0.49-alpha.0 → 0.0.49-alpha.1
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/cjs/anyspend/react/components/AnySpend.d.ts +2 -1
- package/dist/cjs/anyspend/react/components/AnySpend.js +8 -4
- package/dist/cjs/anyspend/react/components/AnySpendCustom.js +12 -3
- package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +10 -3
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +2 -2
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
- package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +2 -2
- package/dist/cjs/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
- package/dist/cjs/anyspend/react/components/common/FeeBreakDown.js +19 -0
- package/dist/cjs/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
- package/dist/cjs/anyspend/react/components/common/FeeDetailPanel.js +116 -0
- package/dist/cjs/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
- package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +36 -21
- package/dist/cjs/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
- package/dist/cjs/anyspend/react/components/common/PointsDetailPanel.js +1 -2
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
- package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +1 -0
- package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
- package/dist/cjs/anyspend/types/api.d.ts +119 -176
- package/dist/cjs/global-account/react/components/ui/tooltip.js +1 -1
- package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
- package/dist/esm/anyspend/react/components/AnySpend.js +8 -4
- package/dist/esm/anyspend/react/components/AnySpendCustom.js +14 -5
- package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +10 -3
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +4 -4
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
- package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +4 -4
- package/dist/esm/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
- package/dist/esm/anyspend/react/components/common/FeeBreakDown.js +16 -0
- package/dist/esm/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
- package/dist/esm/anyspend/react/components/common/FeeDetailPanel.js +113 -0
- package/dist/esm/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
- package/dist/esm/anyspend/react/components/common/PanelOnramp.js +38 -23
- package/dist/esm/anyspend/react/components/common/PanelOnrampPayment.js +2 -1
- package/dist/esm/anyspend/react/components/common/PointsDetailPanel.js +2 -3
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
- package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +1 -0
- package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
- package/dist/esm/anyspend/types/api.d.ts +119 -176
- package/dist/esm/global-account/react/components/ui/tooltip.js +1 -1
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
- package/dist/types/anyspend/react/components/common/CryptoPaySection.d.ts +3 -1
- package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +3 -1
- package/dist/types/anyspend/react/components/common/FeeBreakDown.d.ts +12 -0
- package/dist/types/anyspend/react/components/common/FeeDetailPanel.d.ts +8 -0
- package/dist/types/anyspend/react/components/common/PanelOnramp.d.ts +2 -1
- package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +3 -1
- package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +24 -0
- package/dist/types/anyspend/types/api.d.ts +119 -176
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +20 -0
- package/src/anyspend/react/components/AnySpendCustom.tsx +69 -7
- package/src/anyspend/react/components/AnyspendDepositHype.tsx +23 -0
- package/src/anyspend/react/components/common/CryptoPaySection.tsx +14 -2
- package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +14 -2
- package/src/anyspend/react/components/common/FeeBreakDown.tsx +105 -0
- package/src/anyspend/react/components/common/FeeDetailPanel.tsx +307 -0
- package/src/anyspend/react/components/common/PanelOnramp.tsx +58 -27
- package/src/anyspend/react/components/common/PanelOnrampPayment.tsx +18 -6
- package/src/anyspend/react/components/common/PointsDetailPanel.tsx +1 -13
- package/src/anyspend/react/hooks/useAnyspendFlow.ts +1 -0
- package/src/anyspend/types/api.ts +121 -176
- package/src/global-account/react/components/ui/tooltip.tsx +11 -9
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { ShinyButton } from "@b3dotfun/sdk/global-account/react";
|
|
2
|
+
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
3
|
+
import { ChevronDown } from "lucide-react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { components } from "../../../types/api";
|
|
6
|
+
|
|
7
|
+
interface FeeDetailPanelProps {
|
|
8
|
+
fee: components["schemas"]["Fee"];
|
|
9
|
+
transactionAmountUsd?: number;
|
|
10
|
+
onBack: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Fee tier definitions
|
|
14
|
+
const CRYPTO_FEE_TIERS = [
|
|
15
|
+
{ min: 0, max: 10, bps: 120, label: "$0 – $10" },
|
|
16
|
+
{ min: 10, max: 500, bps: 80, label: "$10 – $500" },
|
|
17
|
+
{ min: 500, max: 5000, bps: 60, label: "$500 – $5,000" },
|
|
18
|
+
{ min: 5000, max: 50000, bps: 40, label: "$5,000 – $50,000" },
|
|
19
|
+
{ min: 50000, max: Infinity, bps: 28, label: "$50,000+" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const FIAT_FEE_TIERS = [
|
|
23
|
+
{ min: 0.01, max: 25, fee: "$1", label: "$0.01 – $25" },
|
|
24
|
+
{ min: 25.01, max: 250, fee: "2%", label: "$25.01 – $250" },
|
|
25
|
+
{ min: 251, max: Infinity, fee: "3%", label: "$251+" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Whale discount tiers based on $ANY holdings
|
|
29
|
+
const WHALE_DISCOUNT_TIERS = [
|
|
30
|
+
{ minAny: 100000, discountPercent: 50, label: "Tier 1: 100k $ANY" },
|
|
31
|
+
{ minAny: 500000, discountPercent: 75, label: "Tier 2: 500k $ANY" },
|
|
32
|
+
{ minAny: 1000000, discountPercent: 100, label: "Tier 3: 1M+ $ANY" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export function FeeDetailPanel({ fee, transactionAmountUsd, onBack }: FeeDetailPanelProps) {
|
|
36
|
+
// Detect if this is a fiat onramp order (Stripe) vs regular crypto swap
|
|
37
|
+
// stripeweb2_fee = Stripe/fiat onramp (uses FIAT_FEE_TIERS)
|
|
38
|
+
// standard_fee = Regular crypto swap (uses CRYPTO_FEE_TIERS)
|
|
39
|
+
const isStripeFee = fee.type === "stripeweb2_fee";
|
|
40
|
+
|
|
41
|
+
// Convert basis points to percentage
|
|
42
|
+
const bpsToPercent = (bps: number) => (bps / 100).toFixed(2);
|
|
43
|
+
|
|
44
|
+
// Check if discount is active
|
|
45
|
+
const hasWhaleDiscount = fee.anyspendWhaleDiscountBps > 0;
|
|
46
|
+
const hasPartnerDiscount = fee.anyspendPartnerDiscountBps > 0;
|
|
47
|
+
|
|
48
|
+
// Find current tier based on transaction amount
|
|
49
|
+
const getCurrentCryptoTier = (amount?: number) => {
|
|
50
|
+
if (!amount) return null;
|
|
51
|
+
return CRYPTO_FEE_TIERS.find(tier => amount >= tier.min && amount < tier.max);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getCurrentFiatTier = (amount?: number) => {
|
|
55
|
+
if (!amount) return null;
|
|
56
|
+
return FIAT_FEE_TIERS.find(tier => amount >= tier.min && amount < tier.max);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const currentCryptoTier = getCurrentCryptoTier(transactionAmountUsd);
|
|
60
|
+
const currentFiatTier = getCurrentFiatTier(transactionAmountUsd);
|
|
61
|
+
|
|
62
|
+
// The whale discount is a percentage discount on the base fee itself
|
|
63
|
+
// Example: 50% discount on 80 bps fee = 40 bps discount, final fee = 40 bps
|
|
64
|
+
// So: finalFee = baseFee - (baseFee * discountPercent / 100)
|
|
65
|
+
// Which means: discountPercent = ((baseFee - finalFee) / baseFee) * 100
|
|
66
|
+
const baseFee = fee.type === "standard_fee" ? fee.anyspendFeeBps : 0;
|
|
67
|
+
|
|
68
|
+
// The whale discount percentage (50%, 75%, or 100%)
|
|
69
|
+
const whaleDiscountPercent =
|
|
70
|
+
baseFee > 0 && hasWhaleDiscount ? Math.round(((baseFee - fee.finalFeeBps) / baseFee) * 100) : 0;
|
|
71
|
+
|
|
72
|
+
// Determine which whale tier based on the discount percentage
|
|
73
|
+
const currentWhaleTier = WHALE_DISCOUNT_TIERS.find(
|
|
74
|
+
tier => Math.abs(whaleDiscountPercent - tier.discountPercent) <= 5,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Calculate partner discount percentage
|
|
78
|
+
const partnerDiscountPercent =
|
|
79
|
+
baseFee > 0 && hasPartnerDiscount ? Math.round((fee.anyspendPartnerDiscountBps / baseFee) * 100) : 0;
|
|
80
|
+
|
|
81
|
+
// State for expanding tier lists
|
|
82
|
+
const [showAllFeeTiers, setShowAllFeeTiers] = useState(false);
|
|
83
|
+
const [showAllDiscountTiers, setShowAllDiscountTiers] = useState(false);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="mx-auto flex w-[460px] max-w-full flex-col items-center gap-3">
|
|
87
|
+
<div className="flex w-full flex-col gap-3">
|
|
88
|
+
<div className="text-center">
|
|
89
|
+
<h3 className="text-as-primary text-lg font-bold">Fee Breakdown</h3>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
{/* Base Fee Schedule Section */}
|
|
93
|
+
<div className="bg-as-surface-secondary border-as-border-secondary rounded-2xl border p-4">
|
|
94
|
+
<h4 className="text-as-primary mb-3 text-sm font-semibold">
|
|
95
|
+
{isStripeFee ? "Fiat Fee Schedule" : "Base Fee Schedule"}
|
|
96
|
+
</h4>
|
|
97
|
+
<div className="space-y-1.5">
|
|
98
|
+
{isStripeFee
|
|
99
|
+
? FIAT_FEE_TIERS.map((tier, idx) => {
|
|
100
|
+
const isCurrentTier = currentFiatTier?.label === tier.label;
|
|
101
|
+
const currentTierIndex = FIAT_FEE_TIERS.findIndex(t => t.label === currentFiatTier?.label);
|
|
102
|
+
|
|
103
|
+
// Show all tiers if expanded, otherwise show up to current tier
|
|
104
|
+
if (!showAllFeeTiers && currentTierIndex !== -1 && idx > currentTierIndex) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
key={idx}
|
|
111
|
+
className={cn(
|
|
112
|
+
"flex items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
|
113
|
+
isCurrentTier ? "bg-as-brand/10 text-as-brand font-semibold" : "text-as-primary/60",
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
<span>{tier.label}</span>
|
|
117
|
+
<span>CC Fee + {tier.fee}</span>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
})
|
|
121
|
+
: CRYPTO_FEE_TIERS.map((tier, idx) => {
|
|
122
|
+
const isCurrentTier = currentCryptoTier?.label === tier.label;
|
|
123
|
+
const currentTierIndex = CRYPTO_FEE_TIERS.findIndex(t => t.label === currentCryptoTier?.label);
|
|
124
|
+
|
|
125
|
+
// Show all tiers if expanded, otherwise show up to current tier
|
|
126
|
+
if (!showAllFeeTiers && currentTierIndex !== -1 && idx > currentTierIndex) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<div
|
|
132
|
+
key={idx}
|
|
133
|
+
className={cn(
|
|
134
|
+
"flex items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
|
135
|
+
isCurrentTier ? "bg-as-brand/10 text-as-brand font-semibold" : "text-as-primary/60",
|
|
136
|
+
)}
|
|
137
|
+
>
|
|
138
|
+
<span>{tier.label}</span>
|
|
139
|
+
<span>{bpsToPercent(tier.bps)}%</span>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
})}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{/* Show expand button if there are lower tiers */}
|
|
146
|
+
{(() => {
|
|
147
|
+
const currentTierIndex = isStripeFee
|
|
148
|
+
? FIAT_FEE_TIERS.findIndex(t => t.label === currentFiatTier?.label)
|
|
149
|
+
: CRYPTO_FEE_TIERS.findIndex(t => t.label === currentCryptoTier?.label);
|
|
150
|
+
const totalTiers = isStripeFee ? FIAT_FEE_TIERS.length : CRYPTO_FEE_TIERS.length;
|
|
151
|
+
const hasMoreTiers = currentTierIndex !== -1 && currentTierIndex < totalTiers - 1;
|
|
152
|
+
|
|
153
|
+
if (hasMoreTiers) {
|
|
154
|
+
return (
|
|
155
|
+
<button
|
|
156
|
+
onClick={() => setShowAllFeeTiers(!showAllFeeTiers)}
|
|
157
|
+
className="text-as-primary/60 hover:text-as-primary mt-2 flex w-full items-center justify-center gap-1 text-xs transition-colors"
|
|
158
|
+
>
|
|
159
|
+
<span>{showAllFeeTiers ? "Show less" : "Show higher tiers"}</span>
|
|
160
|
+
<ChevronDown className={cn("h-3.5 w-3.5 transition-transform", showAllFeeTiers && "rotate-180")} />
|
|
161
|
+
</button>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
})()}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{/* Whale Discount Tiers - Always show for crypto (not fiat) */}
|
|
169
|
+
{!isStripeFee && (
|
|
170
|
+
<div className="bg-as-surface-secondary border-as-border-secondary rounded-2xl border p-4">
|
|
171
|
+
<h4 className="text-as-primary mb-3 text-sm font-semibold">
|
|
172
|
+
{hasWhaleDiscount ? "Whale Discount Tiers" : hasPartnerDiscount ? "Partner Discount" : "Discount Tiers"}
|
|
173
|
+
</h4>
|
|
174
|
+
<div className="space-y-1.5">
|
|
175
|
+
{hasPartnerDiscount && !hasWhaleDiscount ? (
|
|
176
|
+
<div className="flex items-center justify-between rounded-lg bg-green-500/10 px-3 py-2.5 text-sm font-semibold text-green-600">
|
|
177
|
+
<span>Partner Discount</span>
|
|
178
|
+
<span>{partnerDiscountPercent}% discount</span>
|
|
179
|
+
</div>
|
|
180
|
+
) : (
|
|
181
|
+
<>
|
|
182
|
+
{WHALE_DISCOUNT_TIERS.map((tier, idx) => {
|
|
183
|
+
const isCurrentTier = currentWhaleTier?.label === tier.label;
|
|
184
|
+
const currentTierIndex = WHALE_DISCOUNT_TIERS.findIndex(t => t.label === currentWhaleTier?.label);
|
|
185
|
+
|
|
186
|
+
// If no whale discount, show only first tier; otherwise show up to current tier
|
|
187
|
+
if (!showAllDiscountTiers) {
|
|
188
|
+
if (!hasWhaleDiscount && idx > 0) {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (hasWhaleDiscount && currentTierIndex !== -1 && idx > currentTierIndex) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div
|
|
198
|
+
key={idx}
|
|
199
|
+
className={cn(
|
|
200
|
+
"flex items-center justify-between rounded-lg px-3 py-2.5 text-sm transition-colors",
|
|
201
|
+
isCurrentTier ? "bg-green-500/10 font-semibold text-green-600" : "text-as-primary/60",
|
|
202
|
+
)}
|
|
203
|
+
>
|
|
204
|
+
<span>{tier.label}</span>
|
|
205
|
+
<span>{tier.discountPercent}% discount</span>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
})}
|
|
209
|
+
|
|
210
|
+
{/* Show expand button */}
|
|
211
|
+
{(() => {
|
|
212
|
+
const currentTierIndex = WHALE_DISCOUNT_TIERS.findIndex(t => t.label === currentWhaleTier?.label);
|
|
213
|
+
const hasMoreTiers = hasWhaleDiscount
|
|
214
|
+
? currentTierIndex !== -1 && currentTierIndex < WHALE_DISCOUNT_TIERS.length - 1
|
|
215
|
+
: WHALE_DISCOUNT_TIERS.length > 1;
|
|
216
|
+
|
|
217
|
+
if (hasMoreTiers) {
|
|
218
|
+
return (
|
|
219
|
+
<button
|
|
220
|
+
onClick={() => setShowAllDiscountTiers(!showAllDiscountTiers)}
|
|
221
|
+
className="text-as-primary/60 hover:text-as-primary mt-2 flex w-full items-center justify-center gap-1 text-xs transition-colors"
|
|
222
|
+
>
|
|
223
|
+
<span>{showAllDiscountTiers ? "Show less" : "Show all tiers"}</span>
|
|
224
|
+
<ChevronDown
|
|
225
|
+
className={cn("h-3.5 w-3.5 transition-transform", showAllDiscountTiers && "rotate-180")}
|
|
226
|
+
/>
|
|
227
|
+
</button>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
})()}
|
|
232
|
+
</>
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{/* Transaction Summary */}
|
|
239
|
+
{transactionAmountUsd && (
|
|
240
|
+
<div className="bg-as-surface-secondary border-as-border-secondary rounded-2xl border p-4">
|
|
241
|
+
<div className="space-y-2 text-sm">
|
|
242
|
+
<div className="flex items-center justify-between">
|
|
243
|
+
<span className="text-as-secondary">Transaction</span>
|
|
244
|
+
<span className="text-as-primary font-semibold">${transactionAmountUsd.toFixed(2)}</span>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
{isStripeFee && currentFiatTier && (
|
|
248
|
+
<div className="flex items-center justify-between">
|
|
249
|
+
<span className="text-as-secondary">{currentFiatTier.label}</span>
|
|
250
|
+
<span className="text-as-primary">CC Fee + {currentFiatTier.fee}</span>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{!isStripeFee && currentCryptoTier && (
|
|
255
|
+
<>
|
|
256
|
+
<div className="flex items-center justify-between">
|
|
257
|
+
<span className="text-as-secondary">Base Fee ({bpsToPercent(currentCryptoTier.bps)}%)</span>
|
|
258
|
+
<span className="text-as-primary font-medium">
|
|
259
|
+
${((transactionAmountUsd * currentCryptoTier.bps) / 10000).toFixed(2)}
|
|
260
|
+
</span>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
{hasWhaleDiscount && currentWhaleTier && (
|
|
264
|
+
<div className="flex items-center justify-between">
|
|
265
|
+
<span className="text-green-600">Discount ({currentWhaleTier.discountPercent}% off)</span>
|
|
266
|
+
<span className="font-medium text-green-600">
|
|
267
|
+
-$
|
|
268
|
+
{((transactionAmountUsd * baseFee * currentWhaleTier.discountPercent) / 100 / 10000).toFixed(2)}
|
|
269
|
+
</span>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{hasPartnerDiscount && (
|
|
274
|
+
<div className="flex items-center justify-between">
|
|
275
|
+
<span className="text-green-600">Partner Discount ({partnerDiscountPercent}% off)</span>
|
|
276
|
+
<span className="font-medium text-green-600">
|
|
277
|
+
-${((transactionAmountUsd * baseFee * partnerDiscountPercent) / 100 / 10000).toFixed(2)}
|
|
278
|
+
</span>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
|
|
282
|
+
<div className="border-as-border-secondary border-t pt-2">
|
|
283
|
+
<div className="flex items-center justify-between">
|
|
284
|
+
<span className="text-as-primary font-semibold">Total Fee</span>
|
|
285
|
+
<span className="text-as-brand font-semibold">
|
|
286
|
+
${((transactionAmountUsd * fee.finalFeeBps) / 10000).toFixed(2)}
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</>
|
|
291
|
+
)}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
|
|
296
|
+
<ShinyButton
|
|
297
|
+
accentColor={"hsl(var(--as-brand))"}
|
|
298
|
+
onClick={onBack}
|
|
299
|
+
className={cn("as-main-button !bg-as-brand relative w-full")}
|
|
300
|
+
textClassName={cn("text-white")}
|
|
301
|
+
>
|
|
302
|
+
Back to {isStripeFee ? "Payment" : "Swap"}
|
|
303
|
+
</ShinyButton>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { useCoinbaseOnrampOptions
|
|
1
|
+
import { useCoinbaseOnrampOptions } from "@b3dotfun/sdk/anyspend/react";
|
|
2
2
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
3
3
|
import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
|
|
4
4
|
import { ALL_CHAINS } from "@b3dotfun/sdk/anyspend/utils/chain";
|
|
5
5
|
import { Input, useGetGeo, useProfile } from "@b3dotfun/sdk/global-account/react";
|
|
6
6
|
import { cn, formatUsername } from "@b3dotfun/sdk/shared/utils";
|
|
7
7
|
import { formatAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
|
|
8
|
-
import { ChevronRight, Wallet } from "lucide-react";
|
|
8
|
+
import { ChevronRight, Info, Wallet } from "lucide-react";
|
|
9
9
|
import { useRef } from "react";
|
|
10
10
|
import { toast } from "sonner";
|
|
11
11
|
import { useFeatureFlags } from "../../contexts/FeatureFlagsContext";
|
|
@@ -30,6 +30,7 @@ export function PanelOnramp({
|
|
|
30
30
|
hideDstToken = false,
|
|
31
31
|
anyspendQuote,
|
|
32
32
|
onShowPointsDetail,
|
|
33
|
+
onShowFeeDetail,
|
|
33
34
|
customUsdInputValues = ["5", "10", "20", "25"],
|
|
34
35
|
}: {
|
|
35
36
|
srcAmountOnRamp: string;
|
|
@@ -48,39 +49,46 @@ export function PanelOnramp({
|
|
|
48
49
|
hideDstToken?: boolean;
|
|
49
50
|
anyspendQuote?: GetQuoteResponse;
|
|
50
51
|
onShowPointsDetail?: () => void;
|
|
52
|
+
onShowFeeDetail?: () => void;
|
|
51
53
|
customUsdInputValues?: string[];
|
|
52
54
|
}) {
|
|
53
55
|
const featureFlags = useFeatureFlags();
|
|
54
|
-
// Get geo-based onramp options to access fee information
|
|
55
|
-
const { stripeWeb2Support } = useGeoOnrampOptions(srcAmountOnRamp);
|
|
56
56
|
|
|
57
|
-
// Helper function to get fees from
|
|
57
|
+
// Helper function to get fees from anyspend quote
|
|
58
58
|
const getFeeFromApi = (paymentMethod: FiatPaymentMethod): number | null => {
|
|
59
|
+
// Try to get fee from anyspend quote first (most accurate)
|
|
60
|
+
if (anyspendQuote?.data?.fee) {
|
|
61
|
+
const fee = anyspendQuote.data.fee;
|
|
62
|
+
if (fee.type === "stripeweb2_fee") {
|
|
63
|
+
// Calculate total fee in USD from originalAmount - finalAmount
|
|
64
|
+
const originalAmount = Number(fee.originalAmount) / 1e6; // Convert from wei to USD
|
|
65
|
+
const finalAmount = Number(fee.finalAmount) / 1e6;
|
|
66
|
+
return originalAmount - finalAmount;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Fallback to payment method defaults
|
|
59
71
|
switch (paymentMethod) {
|
|
60
72
|
case FiatPaymentMethod.COINBASE_PAY:
|
|
61
|
-
// Coinbase
|
|
62
|
-
return 0;
|
|
73
|
+
return 0; // Coinbase has no additional fees
|
|
63
74
|
case FiatPaymentMethod.STRIPE:
|
|
64
|
-
|
|
65
|
-
if (stripeWeb2Support && "formattedFeeUsd" in stripeWeb2Support) {
|
|
66
|
-
return parseFloat(stripeWeb2Support.formattedFeeUsd) || 0;
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
75
|
+
return null; // No quote available yet
|
|
69
76
|
default:
|
|
70
|
-
return null;
|
|
77
|
+
return null;
|
|
71
78
|
}
|
|
72
79
|
};
|
|
73
80
|
|
|
74
81
|
// Helper function to get total amount from API (for Stripe) or calculate it (for others)
|
|
75
82
|
const getTotalAmount = (paymentMethod: FiatPaymentMethod): number => {
|
|
76
83
|
const baseAmount = parseFloat(srcAmountOnRamp) || 5;
|
|
77
|
-
const fee = getFeeFromApi(paymentMethod);
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
85
|
+
// Try to get from anyspend quote first (most accurate)
|
|
86
|
+
if (anyspendQuote?.data?.fee?.type === "stripeweb2_fee") {
|
|
87
|
+
return Number(anyspendQuote.data.fee.originalAmount) / 1e6; // Convert from wei to USD
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
const fee = getFeeFromApi(paymentMethod);
|
|
91
|
+
|
|
84
92
|
// For Coinbase or when fee is available, calculate manually
|
|
85
93
|
if (fee !== null) {
|
|
86
94
|
return baseAmount + fee;
|
|
@@ -257,13 +265,16 @@ export function PanelOnramp({
|
|
|
257
265
|
|
|
258
266
|
<div className="">
|
|
259
267
|
<div className="flex items-center justify-between">
|
|
260
|
-
<div className="flex items-center gap-
|
|
261
|
-
<span className="text-as-tertiarry text-sm">
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
<div className="flex items-center gap-1.5">
|
|
269
|
+
<span className="text-as-tertiarry text-sm">Total</span>
|
|
270
|
+
{anyspendQuote?.data?.fee && onShowFeeDetail && (
|
|
271
|
+
<button
|
|
272
|
+
onClick={onShowFeeDetail}
|
|
273
|
+
className="text-as-primary/40 hover:text-as-primary/60 transition-colors"
|
|
274
|
+
>
|
|
275
|
+
<Info className="h-4 w-4" />
|
|
276
|
+
</button>
|
|
277
|
+
)}
|
|
267
278
|
{featureFlags.showPoints &&
|
|
268
279
|
anyspendQuote?.data?.pointsAmount &&
|
|
269
280
|
anyspendQuote?.data?.pointsAmount > 0 && (
|
|
@@ -274,9 +285,29 @@ export function PanelOnramp({
|
|
|
274
285
|
/>
|
|
275
286
|
)}
|
|
276
287
|
</div>
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
288
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
289
|
+
<span className="text-as-primary font-semibold">
|
|
290
|
+
${getTotalAmount(selectedPaymentMethod || FiatPaymentMethod.NONE).toFixed(2)}
|
|
291
|
+
</span>
|
|
292
|
+
{(() => {
|
|
293
|
+
// For fiat payments, show the fee from the payment method
|
|
294
|
+
const fiatFee = getFeeFromApi(selectedPaymentMethod || FiatPaymentMethod.NONE);
|
|
295
|
+
if (fiatFee !== null && fiatFee > 0) {
|
|
296
|
+
return <span className="text-as-secondary text-xs">incl. ${fiatFee.toFixed(2)} fee</span>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// For crypto payments (standard_fee), calculate from the quote
|
|
300
|
+
if (anyspendQuote?.data?.fee?.type === "standard_fee" && anyspendQuote.data.currencyIn?.amountUsd) {
|
|
301
|
+
const cryptoFee =
|
|
302
|
+
(Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) / 10000;
|
|
303
|
+
if (cryptoFee > 0) {
|
|
304
|
+
return <span className="text-as-secondary text-xs">incl. ${cryptoFee.toFixed(2)} fee</span>;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return null;
|
|
309
|
+
})()}
|
|
310
|
+
</div>
|
|
280
311
|
</div>
|
|
281
312
|
</div>
|
|
282
313
|
</div>
|
|
@@ -194,12 +194,24 @@ function PanelOnrampPaymentInner(props: PanelOnrampPaymentProps) {
|
|
|
194
194
|
<div className="border-b3-react-border border-t pt-3">
|
|
195
195
|
<div className="flex items-center justify-between">
|
|
196
196
|
<p className="text-b3-react-foreground font-semibold">Amount</p>
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
197
|
+
<div className="flex flex-col items-end gap-0.5">
|
|
198
|
+
<p
|
|
199
|
+
className="text-b3-react-foreground hover:text-b3-react-foreground/80 cursor-pointer text-xl font-semibold transition-colors"
|
|
200
|
+
onClick={onBack}
|
|
201
|
+
>
|
|
202
|
+
${parseFloat(srcAmountOnRamp).toFixed(2)}
|
|
203
|
+
</p>
|
|
204
|
+
{anyspendQuote?.data?.fee?.type === "standard_fee" && anyspendQuote.data.currencyIn?.amountUsd && (
|
|
205
|
+
<p className="text-b3-react-foreground/60 text-xs">
|
|
206
|
+
incl. $
|
|
207
|
+
{(
|
|
208
|
+
(Number(anyspendQuote.data.currencyIn.amountUsd) * anyspendQuote.data.fee.finalFeeBps) /
|
|
209
|
+
10000
|
|
210
|
+
).toFixed(2)}{" "}
|
|
211
|
+
fee
|
|
212
|
+
</p>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
203
215
|
</div>
|
|
204
216
|
</div>
|
|
205
217
|
</div>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ShinyButton } from "@b3dotfun/sdk/global-account/react";
|
|
2
2
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
3
|
-
import { ArrowDown } from "lucide-react";
|
|
4
3
|
import Link from "next/link";
|
|
5
4
|
|
|
6
5
|
interface PointsDetailPanelProps {
|
|
@@ -11,17 +10,6 @@ interface PointsDetailPanelProps {
|
|
|
11
10
|
export function PointsDetailPanel({ pointsAmount = 0, onBack }: PointsDetailPanelProps) {
|
|
12
11
|
return (
|
|
13
12
|
<div className="mx-auto flex w-[460px] max-w-full flex-col items-center gap-4">
|
|
14
|
-
<div className="flex w-full items-center justify-between">
|
|
15
|
-
<Button
|
|
16
|
-
variant="ghost"
|
|
17
|
-
onClick={onBack}
|
|
18
|
-
className="text-as-primary/70 hover:text-as-primary flex items-center gap-2"
|
|
19
|
-
>
|
|
20
|
-
<ArrowDown className="h-4 w-4 rotate-90" />
|
|
21
|
-
Back
|
|
22
|
-
</Button>
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
13
|
<div className="flex flex-col items-center gap-4 text-center">
|
|
26
14
|
<h3 className="text-as-primary text-xl font-bold">Earn Points with Every Swap</h3>
|
|
27
15
|
<p className="text-as-primary/70 text-balance text-sm leading-relaxed">
|