Skip to content

Commit

Permalink
[SDK] feat: Redesign payment method selector UI (#6265)
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquim-verges authored Feb 15, 2025
1 parent 562c534 commit 387891d
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 336 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getSta
import { formatNumber } from "../../../../../../utils/formatNumber.js";
import type { Account } from "../../../../../../wallets/interfaces/wallet.js";
import type { WalletId } from "../../../../../../wallets/wallet-types.js";
import { useCustomTheme } from "../../../../../core/design-system/CustomThemeProvider.js";
import {
type Theme,
fontSize,
radius,
spacing,
} from "../../../../../core/design-system/index.js";
import type {
Expand All @@ -25,7 +27,7 @@ import { LoadingScreen } from "../../../../wallets/shared/LoadingScreen.js";
import type { PayEmbedConnectOptions } from "../../../PayEmbed.js";
import { ChainName } from "../../../components/ChainName.js";
import { Spacer } from "../../../components/Spacer.js";
import { Container, Line, ModalHeader } from "../../../components/basic.js";
import { Container, ModalHeader } from "../../../components/basic.js";
import { Button } from "../../../components/buttons.js";
import { Input } from "../../../components/formElements.js";
import { Text } from "../../../components/text.js";
Expand Down Expand Up @@ -54,7 +56,6 @@ import {
} from "./main/useUISelectionStates.js";
import { BuyTokenInput } from "./swap/BuyTokenInput.js";
import { FiatValue } from "./swap/FiatValue.js";
import { PaymentSelectionScreen } from "./swap/PaymentSelectionScreen.js";
import { SwapFlow } from "./swap/SwapFlow.js";
import { SwapScreenContent } from "./swap/SwapScreenContent.js";
import { TokenSelectorScreen } from "./swap/TokenSelectorScreen.js";
Expand Down Expand Up @@ -528,6 +529,16 @@ function BuyScreenContent(props: BuyScreenContentProps) {
screen.id === "buy-with-fiat") &&
payer && (
<TokenSelectedLayout
isBuyWithFiatEnabled={enabledPaymentMethods.buyWithFiatEnabled}
isBuyWithCryptoEnabled={
enabledPaymentMethods.buyWithCryptoEnabled
}
mode={screen.id === "buy-with-fiat" ? "buy" : "swap"}
onModeChange={(mode) => {
setScreen({
id: mode === "swap" ? "buy-with-crypto" : "buy-with-fiat",
});
}}
disabled={
("prefillBuy" in payOptions &&
payOptions.prefillBuy?.allowEdits?.amount === false) ||
Expand All @@ -540,60 +551,9 @@ function BuyScreenContent(props: BuyScreenContentProps) {
setTokenAmount={setTokenAmount}
client={client}
onBack={() => {
if (
enabledPaymentMethods.buyWithCryptoEnabled &&
screen.id === "buy-with-fiat"
) {
setScreen({ id: "select-payment-method" });
} else if (screen.id === "buy-with-crypto") {
setScreen({ id: "select-payment-method" });
} else {
setScreen({ id: "main" });
}
setScreen({ id: "main" });
}}
>
{screen.id === "select-payment-method" && (
<PaymentSelectionScreen
client={client}
mode={payOptions.mode}
sourceSupportedTokens={sourceSupportedTokens}
hiddenWallets={props.hiddenWallets}
payWithFiatEnabled={props.payOptions.buyWithFiat !== false}
toChain={toChain}
toToken={toToken}
fromToken={fromToken}
fromChain={fromChain}
tokenAmount={tokenAmount}
onContinue={() => {
setScreen({ id: "buy-with-crypto" });
}}
onSelectFiat={() => {
setScreen({ id: "buy-with-fiat" });
}}
onPickToken={() => {
setScreen({
id: "select-from-token",
backScreen: {
id: "select-payment-method",
},
});
}}
showAllWallets={!!props.connectOptions?.showAllWallets}
wallets={props.connectOptions?.wallets}
onBack={() => {
// no-op
}}
onConnect={() => {
setScreen({
id: "connect-payer-wallet",
backScreen: {
id: "select-payment-method",
},
});
}}
/>
)}

{screen.id === "buy-with-crypto" && activeAccount && (
<SwapScreenContent
setScreen={setScreen}
Expand Down Expand Up @@ -842,7 +802,7 @@ function MainScreen(props: {
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
props.setScreen({ id: "buy-with-fiat" });
} else {
props.setScreen({ id: "select-payment-method" });
props.setScreen({ id: "buy-with-crypto" });
}
}}
/>
Expand All @@ -865,7 +825,7 @@ function MainScreen(props: {
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
props.setScreen({ id: "buy-with-fiat" });
} else {
props.setScreen({ id: "select-payment-method" });
props.setScreen({ id: "buy-with-crypto" });
}
}}
/>
Expand Down Expand Up @@ -928,7 +888,7 @@ function MainScreen(props: {
if (buyWithFiatEnabled && !buyWithCryptoEnabled) {
props.setScreen({ id: "buy-with-fiat" });
} else {
props.setScreen({ id: "select-payment-method" });
props.setScreen({ id: "buy-with-crypto" });
}
}}
>
Expand All @@ -952,7 +912,12 @@ function TokenSelectedLayout(props: {
client: ThirdwebClient;
onBack: () => void;
disabled?: boolean;
mode: "buy" | "swap";
onModeChange: (mode: "buy" | "swap") => void;
isBuyWithFiatEnabled: boolean;
isBuyWithCryptoEnabled: boolean;
}) {
const theme = useCustomTheme();
return (
<Container>
<Container p="lg">
Expand All @@ -975,12 +940,77 @@ function TokenSelectedLayout(props: {
disabled={props.disabled}
/>

<Spacer y="md" />
<Line />
<Spacer y="lg" />
<Container flex="row" gap="md" center="y">
<Text size="sm"> Pay with </Text>
{props.isBuyWithFiatEnabled && props.isBuyWithCryptoEnabled && (
<Container
flex="row"
style={{
flex: 1,
justifyContent: "center",
borderRadius: radius.xl,
border: `1px solid ${theme.colors.borderColor}`,
alignItems: "stretch",
}}
>
<Button
variant="ghost"
style={{
flex: 1,
background:
props.mode === "swap"
? theme.colors.tertiaryBg
: "transparent",
borderRadius: radius.xl,
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
padding: spacing.xs,
}}
onClick={() => props.onModeChange("swap")}
>
<Text
size="sm"
color={
props.mode === "swap" ? "primaryText" : "secondaryText"
}
>
Crypto
</Text>
</Button>
<div
style={{
width: "1px",
background: theme.colors.borderColor,
}}
/>
<Button
variant="ghost"
style={{
flex: 1,
background:
props.mode === "buy"
? theme.colors.tertiaryBg
: "transparent",
borderRadius: radius.xl,
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
padding: spacing.xs,
}}
onClick={() => props.onModeChange("buy")}
>
<Text
size="sm"
color={props.mode === "buy" ? "primaryText" : "secondaryText"}
>
Card
</Text>
</Button>
</Container>
)}
</Container>

<Text size="sm"> Pay with </Text>
<Spacer y="sm" />
<Spacer y="lg" />

{props.children}
</Container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ export function PayWithCreditCard(props: {
<Skeleton width="100px" height={fontSize.lg} />
) : (
<Text size="lg" color={props.value ? "primaryText" : "secondaryText"}>
{props.value ? `${formatNumber(Number(props.value), 6)}` : "--"}
{props.value
? `${props.currency.symbol}${formatNumber(
Number(props.value),
6,
)}`
: "--"}
</Text>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export function FiatScreenContent(props: {
: undefined;

return (
<Container flex="column" gap="md" animate="fadein">
<Container flex="column" gap="lg" animate="fadein">
{isOpen && (
<>
<DrawerOverlay ref={drawerOverlayRef} />
Expand Down Expand Up @@ -198,74 +198,75 @@ export function FiatScreenContent(props: {
</>
)}

<div>
<PayWithCreditCard
isLoading={fiatQuoteQuery.isLoading}
value={fiatQuoteQuery.data?.fromCurrencyWithFees.amount}
client={client}
currency={selectedCurrency}
onSelectCurrency={showCurrencySelector}
/>
<Container
bg="tertiaryBg"
flex="row"
borderColor="borderColor"
style={{
paddingLeft: spacing.md,
justifyContent: "space-between",
alignItems: "center",
borderWidth: "1px",
borderStyle: "solid",
borderBottom: "none",
}}
>
<Text size="xs" color="secondaryText">
Provider
</Text>
<Button variant="ghost" onClick={showProviders}>
<Container flex="row" center="y" gap="xxs" color="secondaryText">
<Text size="xs">
{preferredProvider
? `${preferredProvider.charAt(0).toUpperCase() + preferredProvider.slice(1).toLowerCase()}`
: fiatQuoteQuery.data?.provider
? `${fiatQuoteQuery.data?.provider.charAt(0).toUpperCase() + fiatQuoteQuery.data?.provider.slice(1).toLowerCase()}`
: ""}
</Text>
<ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
</Container>
</Button>
</Container>
{/* Estimated time + View fees button */}
<EstimatedTimeAndFees
quoteIsLoading={fiatQuoteQuery.isLoading}
estimatedSeconds={fiatQuoteQuery.data?.estimatedDurationSeconds}
onViewFees={showFees}
/>
<Spacer y="md" />
</div>

{/* Error message */}
{errorMsg && (
<Container flex="column" gap="sm">
<div>
{errorMsg.data?.minimumAmountEth ? (
<Text color="danger" size="sm" center multiline>
Minimum amount is{" "}
{formatNumber(Number(errorMsg.data.minimumAmountEth), 6)}{" "}
<TokenSymbol
token={toToken}
chain={toChain}
size="sm"
inline
color="danger"
/>
<PayWithCreditCard
isLoading={fiatQuoteQuery.isLoading}
value={fiatQuoteQuery.data?.fromCurrencyWithFees.amount}
client={client}
currency={selectedCurrency}
onSelectCurrency={showCurrencySelector}
/>
<Container
bg="tertiaryBg"
flex="row"
borderColor="borderColor"
style={{
paddingLeft: spacing.md,
justifyContent: "space-between",
alignItems: "center",
borderWidth: "1px",
borderStyle: "solid",
borderBottom: "none",
}}
>
<Text size="xs" color="secondaryText">
Provider
</Text>
) : (
<Text color="danger" size="sm" center multiline>
{errorMsg.message || defaultMessage}
</Text>
)}
<Button variant="ghost" onClick={showProviders}>
<Container flex="row" center="y" gap="xxs" color="secondaryText">
<Text size="xs">
{preferredProvider
? `${preferredProvider.charAt(0).toUpperCase() + preferredProvider.slice(1).toLowerCase()}`
: fiatQuoteQuery.data?.provider
? `${fiatQuoteQuery.data?.provider.charAt(0).toUpperCase() + fiatQuoteQuery.data?.provider.slice(1).toLowerCase()}`
: ""}
</Text>
<ChevronDownIcon width={iconSize.sm} height={iconSize.sm} />
</Container>
</Button>
</Container>
{/* Estimated time + View fees button */}
<EstimatedTimeAndFees
quoteIsLoading={fiatQuoteQuery.isLoading}
estimatedSeconds={fiatQuoteQuery.data?.estimatedDurationSeconds}
onViewFees={showFees}
/>
</div>
)}

{/* Error message */}
{errorMsg && (
<div>
{errorMsg.data?.minimumAmountEth ? (
<Text color="danger" size="sm" center multiline>
Minimum amount is{" "}
{formatNumber(Number(errorMsg.data.minimumAmountEth), 6)}{" "}
<TokenSymbol
token={toToken}
chain={toChain}
size="sm"
inline
color="danger"
/>
</Text>
) : (
<Text color="danger" size="sm" center multiline>
{errorMsg.message || defaultMessage}
</Text>
)}
</div>
)}
</Container>

{errorMsg?.data?.minimumAmountEth ? (
<Button
Expand Down
Loading

0 comments on commit 387891d

Please sign in to comment.