Skip to content

Commit d098edc

Browse files
[SDK] feat: Redesign payment method selector UI
1 parent 562c534 commit d098edc

File tree

9 files changed

+286
-336
lines changed

9 files changed

+286
-336
lines changed

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx

Lines changed: 91 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getSta
88
import { formatNumber } from "../../../../../../utils/formatNumber.js";
99
import type { Account } from "../../../../../../wallets/interfaces/wallet.js";
1010
import type { WalletId } from "../../../../../../wallets/wallet-types.js";
11+
import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js";
1112
import {
1213
type Theme,
1314
fontSize,
15+
radius,
1416
spacing,
1517
} from "../../../../../core/design-system/index.js";
1618
import type {
@@ -25,7 +27,7 @@ import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js";
2527
import type { PayEmbedConnectOptions } from "../../../PayEmbed.js";
2628
import { ChainName } from "../../../components/ChainName.js";
2729
import { Spacer } from "../../../components/Spacer.js";
28-
import { Container, Line, ModalHeader } from "../../../components/basic.js";
30+
import { Container, ModalHeader } from "../../../components/basic.js";
2931
import { Button } from "../../../components/buttons.js";
3032
import { Input } from "../../../components/formElements.js";
3133
import { Text } from "../../../components/text.js";
@@ -54,7 +56,6 @@ import {
5456
} from "./main/useUISelectionStates.js";
5557
import { BuyTokenInput } from "./swap/BuyTokenInput.js";
5658
import { FiatValue } from "./swap/FiatValue.js";
57-
import { PaymentSelectionScreen } from "./swap/PaymentSelectionScreen.js";
5859
import { SwapFlow } from "./swap/SwapFlow.js";
5960
import { SwapScreenContent } from "./swap/SwapScreenContent.js";
6061
import { TokenSelectorScreen } from "./swap/TokenSelectorScreen.js";
@@ -528,6 +529,16 @@ function BuyScreenContent(props: BuyScreenContentProps) {
528529
screen.id === "buy-with-fiat") &&
529530
payer && (
530531
<TokenSelectedLayout
532+
isBuyWithFiatEnabled={enabledPaymentMethods.buyWithFiatEnabled}
533+
isBuyWithCryptoEnabled={
534+
enabledPaymentMethods.buyWithCryptoEnabled
535+
}
536+
mode={screen.id === "buy-with-fiat" ? "buy" : "swap"}
537+
onModeChange={(mode) => {
538+
setScreen({
539+
id: mode === "swap" ? "buy-with-crypto" : "buy-with-fiat",
540+
});
541+
}}
531542
disabled={
532543
("prefillBuy" in payOptions &&
533544
payOptions.prefillBuy?.allowEdits?.amount === false) ||
@@ -540,60 +551,9 @@ function BuyScreenContent(props: BuyScreenContentProps) {
540551
setTokenAmount={setTokenAmount}
541552
client={client}
542553
onBack={() => {
543-
if (
544-
enabledPaymentMethods.buyWithCryptoEnabled &&
545-
screen.id === "buy-with-fiat"
546-
) {
547-
setScreen({ id: "select-payment-method" });
548-
} else if (screen.id === "buy-with-crypto") {
549-
setScreen({ id: "select-payment-method" });
550-
} else {
551-
setScreen({ id: "main" });
552-
}
554+
setScreen({ id: "main" });
553555
}}
554556
>
555-
{screen.id === "select-payment-method" && (
556-
<PaymentSelectionScreen
557-
client={client}
558-
mode={payOptions.mode}
559-
sourceSupportedTokens={sourceSupportedTokens}
560-
hiddenWallets={props.hiddenWallets}
561-
payWithFiatEnabled={props.payOptions.buyWithFiat !== false}
562-
toChain={toChain}
563-
toToken={toToken}
564-
fromToken={fromToken}
565-
fromChain={fromChain}
566-
tokenAmount={tokenAmount}
567-
onContinue={() => {
568-
setScreen({ id: "buy-with-crypto" });
569-
}}
570-
onSelectFiat={() => {
571-
setScreen({ id: "buy-with-fiat" });
572-
}}
573-
onPickToken={() => {
574-
setScreen({
575-
id: "select-from-token",
576-
backScreen: {
577-
id: "select-payment-method",
578-
},
579-
});
580-
}}
581-
showAllWallets={!!props.connectOptions?.showAllWallets}
582-
wallets={props.connectOptions?.wallets}
583-
onBack={() => {
584-
// no-op
585-
}}
586-
onConnect={() => {
587-
setScreen({
588-
id: "connect-payer-wallet",
589-
backScreen: {
590-
id: "select-payment-method",
591-
},
592-
});
593-
}}
594-
/>
595-
)}
596-
597557
{screen.id === "buy-with-crypto" && activeAccount && (
598558
<SwapScreenContent
599559
setScreen={setScreen}
@@ -842,7 +802,7 @@ function MainScreen(props: {
842802
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
843803
props.setScreen({ id: "buy-with-fiat" });
844804
} else {
845-
props.setScreen({ id: "select-payment-method" });
805+
props.setScreen({ id: "buy-with-crypto" });
846806
}
847807
}}
848808
/>
@@ -865,7 +825,7 @@ function MainScreen(props: {
865825
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
866826
props.setScreen({ id: "buy-with-fiat" });
867827
} else {
868-
props.setScreen({ id: "select-payment-method" });
828+
props.setScreen({ id: "buy-with-crypto" });
869829
}
870830
}}
871831
/>
@@ -928,7 +888,7 @@ function MainScreen(props: {
928888
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
929889
props.setScreen({ id: "buy-with-fiat" });
930890
} else {
931-
props.setScreen({ id: "select-payment-method" });
891+
props.setScreen({ id: "buy-with-crypto" });
932892
}
933893
}}
934894
>
@@ -952,7 +912,12 @@ function TokenSelectedLayout(props: {
952912
client: ThirdwebClient;
953913
onBack: () => void;
954914
disabled?: boolean;
915+
mode: "buy" | "swap";
916+
onModeChange: (mode: "buy" | "swap") => void;
917+
isBuyWithFiatEnabled: boolean;
918+
isBuyWithCryptoEnabled: boolean;
955919
}) {
920+
const theme = useCustomTheme();
956921
return (
957922
<Container>
958923
<Container p="lg">
@@ -975,12 +940,77 @@ function TokenSelectedLayout(props: {
975940
disabled={props.disabled}
976941
/>
977942

978-
<Spacer y="md" />
979-
<Line />
980943
<Spacer y="lg" />
944+
<Container flex="row" gap="md" center="y">
945+
<Text size="sm"> Pay with </Text>
946+
{props.isBuyWithFiatEnabled && props.isBuyWithCryptoEnabled && (
947+
<Container
948+
flex="row"
949+
style={{
950+
flex: 1,
951+
justifyContent: "center",
952+
borderRadius: radius.xl,
953+
border: `1px solid ${theme.colors.borderColor}`,
954+
alignItems: "stretch",
955+
}}
956+
>
957+
<Button
958+
variant="ghost"
959+
style={{
960+
flex: 1,
961+
background:
962+
props.mode === "swap"
963+
? theme.colors.tertiaryBg
964+
: "transparent",
965+
borderRadius: radius.xl,
966+
borderTopRightRadius: 0,
967+
borderBottomRightRadius: 0,
968+
padding: spacing.xs,
969+
}}
970+
onClick={() => props.onModeChange("swap")}
971+
>
972+
<Text
973+
size="sm"
974+
color={
975+
props.mode === "swap" ? "primaryText" : "secondaryText"
976+
}
977+
>
978+
Crypto
979+
</Text>
980+
</Button>
981+
<div
982+
style={{
983+
width: "1px",
984+
background: theme.colors.borderColor,
985+
}}
986+
/>
987+
<Button
988+
variant="ghost"
989+
style={{
990+
flex: 1,
991+
background:
992+
props.mode === "buy"
993+
? theme.colors.tertiaryBg
994+
: "transparent",
995+
borderRadius: radius.xl,
996+
borderTopLeftRadius: 0,
997+
borderBottomLeftRadius: 0,
998+
padding: spacing.xs,
999+
}}
1000+
onClick={() => props.onModeChange("buy")}
1001+
>
1002+
<Text
1003+
size="sm"
1004+
color={props.mode === "buy" ? "primaryText" : "secondaryText"}
1005+
>
1006+
Card
1007+
</Text>
1008+
</Button>
1009+
</Container>
1010+
)}
1011+
</Container>
9811012

982-
<Text size="sm"> Pay with </Text>
983-
<Spacer y="sm" />
1013+
<Spacer y="lg" />
9841014

9851015
{props.children}
9861016
</Container>

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/PayWIthCreditCard.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,12 @@ export function PayWithCreditCard(props: {
8282
<Skeleton width="100px" height={fontSize.lg} />
8383
) : (
8484
<Text size="lg" color={props.value ? "primaryText" : "secondaryText"}>
85-
{props.value ? `${formatNumber(Number(props.value), 6)}` : "--"}
85+
{props.value
86+
? `${props.currency.symbol}${formatNumber(
87+
Number(props.value),
88+
6,
89+
)}`
90+
: "--"}
8691
</Text>
8792
)}
8893
</div>

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/fiat/FiatScreenContent.tsx

Lines changed: 67 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export function FiatScreenContent(props: {
157157
: undefined;
158158

159159
return (
160-
<Container flex="column" gap="md" animate="fadein">
160+
<Container flex="column" gap="lg" animate="fadein">
161161
{isOpen && (
162162
<>
163163
<DrawerOverlay ref={drawerOverlayRef} />
@@ -198,74 +198,75 @@ export function FiatScreenContent(props: {
198198
</>
199199
)}
200200

201-
<div>
202-
<PayWithCreditCard
203-
isLoading={fiatQuoteQuery.isLoading}
204-
value={fiatQuoteQuery.data?.fromCurrencyWithFees.amount}
205-
client={client}
206-
currency={selectedCurrency}
207-
onSelectCurrency={showCurrencySelector}
208-
/>
209-
<Container
210-
bg="tertiaryBg"
211-
flex="row"
212-
borderColor="borderColor"
213-
style={{
214-
paddingLeft: spacing.md,
215-
justifyContent: "space-between",
216-
alignItems: "center",
217-
borderWidth: "1px",
218-
borderStyle: "solid",
219-
borderBottom: "none",
220-
}}
221-
>
222-
<Text size="xs" color="secondaryText">
223-
Provider
224-
</Text>
225-
<Button variant="ghost" onClick={showProviders}>
226-
<Container flex="row" center="y" gap="xxs" color="secondaryText">
227-
<Text size="xs">
228-
{preferredProvider
229-
? `${preferredProvider.charAt(0).toUpperCase() + preferredProvider.slice(1).toLowerCase()}`
230-
: fiatQuoteQuery.data?.provider
231-
? `${fiatQuoteQuery.data?.provider.charAt(0).toUpperCase() + fiatQuoteQuery.data?.provider.slice(1).toLowerCase()}`
232-
: ""}
233-
</Text>
234-
<ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
235-
</Container>
236-
</Button>
237-
</Container>
238-
{/* Estimated time + View fees button */}
239-
<EstimatedTimeAndFees
240-
quoteIsLoading={fiatQuoteQuery.isLoading}
241-
estimatedSeconds={fiatQuoteQuery.data?.estimatedDurationSeconds}
242-
onViewFees={showFees}
243-
/>
244-
<Spacer y="md" />
245-
</div>
246-
247-
{/* Error message */}
248-
{errorMsg && (
201+
<Container flex="column" gap="sm">
249202
<div>
250-
{errorMsg.data?.minimumAmountEth ? (
251-
<Text color="danger" size="sm" center multiline>
252-
Minimum amount is{" "}
253-
{formatNumber(Number(errorMsg.data.minimumAmountEth), 6)}{" "}
254-
<TokenSymbol
255-
token={toToken}
256-
chain={toChain}
257-
size="sm"
258-
inline
259-
color="danger"
260-
/>
203+
<PayWithCreditCard
204+
isLoading={fiatQuoteQuery.isLoading}
205+
value={fiatQuoteQuery.data?.fromCurrencyWithFees.amount}
206+
client={client}
207+
currency={selectedCurrency}
208+
onSelectCurrency={showCurrencySelector}
209+
/>
210+
<Container
211+
bg="tertiaryBg"
212+
flex="row"
213+
borderColor="borderColor"
214+
style={{
215+
paddingLeft: spacing.md,
216+
justifyContent: "space-between",
217+
alignItems: "center",
218+
borderWidth: "1px",
219+
borderStyle: "solid",
220+
borderBottom: "none",
221+
}}
222+
>
223+
<Text size="xs" color="secondaryText">
224+
Provider
261225
</Text>
262-
) : (
263-
<Text color="danger" size="sm" center multiline>
264-
{errorMsg.message || defaultMessage}
265-
</Text>
266-
)}
226+
<Button variant="ghost" onClick={showProviders}>
227+
<Container flex="row" center="y" gap="xxs" color="secondaryText">
228+
<Text size="xs">
229+
{preferredProvider
230+
? `${preferredProvider.charAt(0).toUpperCase() + preferredProvider.slice(1).toLowerCase()}`
231+
: fiatQuoteQuery.data?.provider
232+
? `${fiatQuoteQuery.data?.provider.charAt(0).toUpperCase() + fiatQuoteQuery.data?.provider.slice(1).toLowerCase()}`
233+
: ""}
234+
</Text>
235+
<ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
236+
</Container>
237+
</Button>
238+
</Container>
239+
{/* Estimated time + View fees button */}
240+
<EstimatedTimeAndFees
241+
quoteIsLoading={fiatQuoteQuery.isLoading}
242+
estimatedSeconds={fiatQuoteQuery.data?.estimatedDurationSeconds}
243+
onViewFees={showFees}
244+
/>
267245
</div>
268-
)}
246+
247+
{/* Error message */}
248+
{errorMsg && (
249+
<div>
250+
{errorMsg.data?.minimumAmountEth ? (
251+
<Text color="danger" size="sm" center multiline>
252+
Minimum amount is{" "}
253+
{formatNumber(Number(errorMsg.data.minimumAmountEth), 6)}{" "}
254+
<TokenSymbol
255+
token={toToken}
256+
chain={toChain}
257+
size="sm"
258+
inline
259+
color="danger"
260+
/>
261+
</Text>
262+
) : (
263+
<Text color="danger" size="sm" center multiline>
264+
{errorMsg.message || defaultMessage}
265+
</Text>
266+
)}
267+
</div>
268+
)}
269+
</Container>
269270

270271
{errorMsg?.data?.minimumAmountEth ? (
271272
<Button

0 commit comments

Comments
 (0)