|
1 | 1 | "use client";
|
| 2 | +import { useMemo } from "react"; |
2 | 3 | import type { ThirdwebClient } from "../../../../../client/client.js";
|
| 4 | +import { checksumAddress } from "../../../../../utils/address.js"; |
| 5 | +import { toTokens } from "../../../../../utils/units.js"; |
3 | 6 | import { useCustomTheme } from "../../../../core/design-system/CustomThemeProvider.js";
|
4 | 7 | import {
|
5 | 8 | iconSize,
|
6 | 9 | radius,
|
7 | 10 | spacing,
|
8 | 11 | } from "../../../../core/design-system/index.js";
|
| 12 | +import { useBuyWithFiatQuotesForProviders } from "../../../../core/hooks/pay/useBuyWithFiatQuotesForProviders.js"; |
9 | 13 | import { Img } from "../../components/Img.js";
|
10 | 14 | import { Spacer } from "../../components/Spacer.js";
|
| 15 | +import { Spinner } from "../../components/Spinner.js"; |
11 | 16 | import { Container } from "../../components/basic.js";
|
12 | 17 | import { Button } from "../../components/buttons.js";
|
13 | 18 | import { Text } from "../../components/text.js";
|
14 | 19 |
|
15 | 20 | export interface FiatProviderSelectionProps {
|
16 | 21 | client: ThirdwebClient;
|
17 | 22 | onProviderSelected: (provider: "coinbase" | "stripe" | "transak") => void;
|
| 23 | + toChainId: number; |
| 24 | + toTokenAddress: string; |
| 25 | + toAddress: string; |
| 26 | + toAmount?: string; |
18 | 27 | }
|
19 | 28 |
|
| 29 | +const PROVIDERS = [ |
| 30 | + { |
| 31 | + id: "coinbase" as const, |
| 32 | + name: "Coinbase", |
| 33 | + description: "Fast and secure payments", |
| 34 | + iconUri: "https://i.ibb.co/LDJ3Rk2t/Frame-5.png", |
| 35 | + }, |
| 36 | + { |
| 37 | + id: "stripe" as const, |
| 38 | + name: "Stripe", |
| 39 | + description: "Trusted payment processing", |
| 40 | + iconUri: "https://i.ibb.co/CpgQC2Lf/images-3.png", |
| 41 | + }, |
| 42 | + { |
| 43 | + id: "transak" as const, |
| 44 | + name: "Transak", |
| 45 | + description: "Global payment solution", |
| 46 | + iconUri: "https://i.ibb.co/Xx2r882p/Transak-official-symbol-1.png", |
| 47 | + }, |
| 48 | +]; |
| 49 | + |
20 | 50 | export function FiatProviderSelection({
|
21 |
| - client, |
22 | 51 | onProviderSelected,
|
| 52 | + client, |
| 53 | + toChainId, |
| 54 | + toTokenAddress, |
| 55 | + toAddress, |
| 56 | + toAmount, |
23 | 57 | }: FiatProviderSelectionProps) {
|
24 | 58 | const theme = useCustomTheme();
|
25 | 59 |
|
26 |
| - const providers = [ |
27 |
| - { |
28 |
| - id: "coinbase" as const, |
29 |
| - name: "Coinbase", |
30 |
| - description: "Fast and secure payments", |
31 |
| - iconUri: "https://i.ibb.co/LDJ3Rk2t/Frame-5.png", |
32 |
| - }, |
33 |
| - { |
34 |
| - id: "stripe" as const, |
35 |
| - name: "Stripe", |
36 |
| - description: "Trusted payment processing", |
37 |
| - iconUri: "https://i.ibb.co/CpgQC2Lf/images-3.png", |
38 |
| - }, |
39 |
| - { |
40 |
| - id: "transak" as const, |
41 |
| - name: "Transak", |
42 |
| - description: "Global payment solution", |
43 |
| - iconUri: "https://i.ibb.co/Xx2r882p/Transak-official-symbol-1.png", |
44 |
| - }, |
45 |
| - ]; |
| 60 | + // Fetch quotes for all providers |
| 61 | + const quoteQueries = useBuyWithFiatQuotesForProviders({ |
| 62 | + client, |
| 63 | + chainId: toChainId, |
| 64 | + tokenAddress: checksumAddress(toTokenAddress), |
| 65 | + receiver: checksumAddress(toAddress), |
| 66 | + amount: toAmount || "0", |
| 67 | + currency: "USD", |
| 68 | + }); |
| 69 | + |
| 70 | + const quotes = useMemo(() => { |
| 71 | + return quoteQueries.map((q) => q.data).filter((q) => !!q); |
| 72 | + }, [quoteQueries]); |
46 | 73 |
|
47 | 74 | // TODO: add a "remember my choice" checkbox
|
48 | 75 |
|
49 | 76 | return (
|
50 | 77 | <>
|
51 |
| - <Text size="md" color="primaryText"> |
52 |
| - Select Payment Provider |
53 |
| - </Text> |
54 |
| - <Spacer y="md" /> |
55 | 78 | <Container flex="column" gap="sm">
|
56 |
| - {providers.map((provider) => ( |
57 |
| - <Button |
58 |
| - key={provider.id} |
59 |
| - variant="secondary" |
60 |
| - fullWidth |
61 |
| - onClick={() => onProviderSelected(provider.id)} |
62 |
| - style={{ |
63 |
| - border: `1px solid ${theme.colors.borderColor}`, |
64 |
| - borderRadius: radius.md, |
65 |
| - padding: `${spacing.sm} ${spacing.md}`, |
66 |
| - backgroundColor: theme.colors.tertiaryBg, |
67 |
| - textAlign: "left", |
68 |
| - }} |
69 |
| - > |
70 |
| - <Container |
71 |
| - flex="row" |
72 |
| - gap="md" |
73 |
| - style={{ width: "100%", alignItems: "center" }} |
74 |
| - > |
75 |
| - <Container |
76 |
| - style={{ |
77 |
| - width: `${iconSize.md}px`, |
78 |
| - height: `${iconSize.md}px`, |
79 |
| - borderRadius: "50%", |
80 |
| - display: "flex", |
81 |
| - alignItems: "center", |
82 |
| - justifyContent: "center", |
83 |
| - padding: spacing.xs, |
84 |
| - overflow: "hidden", |
85 |
| - }} |
86 |
| - > |
87 |
| - <Img |
88 |
| - src={provider.iconUri} |
89 |
| - alt={provider.name} |
90 |
| - width={iconSize.md} |
91 |
| - height={iconSize.md} |
92 |
| - client={client} |
93 |
| - /> |
94 |
| - </Container> |
95 |
| - <Container flex="column" gap="3xs" style={{ flex: 1 }}> |
96 |
| - <Text size="sm" color="primaryText" style={{ fontWeight: 600 }}> |
97 |
| - {provider.name} |
98 |
| - </Text> |
99 |
| - </Container> |
100 |
| - </Container> |
101 |
| - </Button> |
102 |
| - ))} |
| 79 | + {quotes.length > 0 ? ( |
| 80 | + quotes |
| 81 | + .sort((a, b) => a.currencyAmount - b.currencyAmount) |
| 82 | + .map((quote, index) => { |
| 83 | + const provider = PROVIDERS.find( |
| 84 | + (p) => p.id === quote.intent.onramp, |
| 85 | + ); |
| 86 | + if (!provider) { |
| 87 | + return null; |
| 88 | + } |
| 89 | + |
| 90 | + return ( |
| 91 | + <Container |
| 92 | + key={provider.id} |
| 93 | + animate="fadein" |
| 94 | + style={{ |
| 95 | + animationDelay: `${index * 100}ms`, |
| 96 | + }} |
| 97 | + > |
| 98 | + <Button |
| 99 | + variant="secondary" |
| 100 | + fullWidth |
| 101 | + onClick={() => onProviderSelected(provider.id)} |
| 102 | + style={{ |
| 103 | + border: `1px solid ${theme.colors.borderColor}`, |
| 104 | + borderRadius: radius.md, |
| 105 | + padding: `${spacing.sm} ${spacing.md}`, |
| 106 | + backgroundColor: theme.colors.tertiaryBg, |
| 107 | + textAlign: "left", |
| 108 | + }} |
| 109 | + > |
| 110 | + <Container |
| 111 | + flex="row" |
| 112 | + gap="sm" |
| 113 | + style={{ width: "100%", alignItems: "center" }} |
| 114 | + > |
| 115 | + <Container |
| 116 | + style={{ |
| 117 | + width: `${iconSize.md}px`, |
| 118 | + height: `${iconSize.md}px`, |
| 119 | + borderRadius: "50%", |
| 120 | + display: "flex", |
| 121 | + alignItems: "center", |
| 122 | + justifyContent: "center", |
| 123 | + padding: spacing.xs, |
| 124 | + overflow: "hidden", |
| 125 | + }} |
| 126 | + > |
| 127 | + <Img |
| 128 | + src={provider.iconUri} |
| 129 | + alt={provider.name} |
| 130 | + width={iconSize.md} |
| 131 | + height={iconSize.md} |
| 132 | + client={client} |
| 133 | + /> |
| 134 | + </Container> |
| 135 | + <Container flex="column" gap="3xs" style={{ flex: 1 }}> |
| 136 | + <Text |
| 137 | + size="md" |
| 138 | + color="primaryText" |
| 139 | + style={{ fontWeight: 600 }} |
| 140 | + > |
| 141 | + {provider.name} |
| 142 | + </Text> |
| 143 | + </Container> |
| 144 | + <Container |
| 145 | + flex="column" |
| 146 | + gap="3xs" |
| 147 | + style={{ alignItems: "flex-end" }} |
| 148 | + > |
| 149 | + <Text |
| 150 | + size="sm" |
| 151 | + color="primaryText" |
| 152 | + style={{ fontWeight: 500 }} |
| 153 | + > |
| 154 | + $ |
| 155 | + {quote.currencyAmount.toLocaleString(undefined, { |
| 156 | + minimumFractionDigits: 2, |
| 157 | + maximumFractionDigits: 2, |
| 158 | + })}{" "} |
| 159 | + {quote.currency} |
| 160 | + </Text> |
| 161 | + <Text size="xs" color="secondaryText"> |
| 162 | + {toTokens( |
| 163 | + quote.destinationAmount, |
| 164 | + quote.destinationToken.decimals, |
| 165 | + )}{" "} |
| 166 | + {quote.destinationToken.symbol} |
| 167 | + </Text> |
| 168 | + </Container> |
| 169 | + </Container> |
| 170 | + </Button> |
| 171 | + </Container> |
| 172 | + ); |
| 173 | + }) |
| 174 | + ) : ( |
| 175 | + <Container flex="column" center="both" style={{ minHeight: "120px" }}> |
| 176 | + <Spinner size="lg" color="secondaryText" /> |
| 177 | + <Spacer y="sm" /> |
| 178 | + <Text size="sm" color="secondaryText" center> |
| 179 | + Generating quotes... |
| 180 | + </Text> |
| 181 | + </Container> |
| 182 | + )} |
103 | 183 | </Container>
|
104 | 184 | </>
|
105 | 185 | );
|
|
0 commit comments