Skip to content

Commit 07f98a5

Browse files
MananTankjnsdls
andauthored
feat: Update Swap UI in ConnectButton (#2759)
Co-authored-by: Jonas Daniels <jonas.daniels@outlook.com>
1 parent 007770c commit 07f98a5

25 files changed

+1447
-723
lines changed

.changeset/curvy-years-mix.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
- Improved Swap UI in ConnectButton Details Modal
6+
- Prevent Modal from closing when clicking on "Switch Network" in the Swap UI
7+
- Fix wrong network name shown in Transaction History

packages/thirdweb/src/react/web/ui/ConnectWallet/ConnectWallet.tsx

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
import styled from "@emotion/styled";
22
import { useContext, useEffect, useMemo, useState } from "react";
3-
import type { Chain } from "../../../../chains/types.js";
43
import { AutoConnect } from "../../../core/hooks/connection/useAutoConnect.js";
5-
import { useWalletConnectionCtx } from "../../../core/hooks/others/useWalletConnectionCtx.js";
64
import {
75
useActiveAccount,
8-
useActiveWalletChain,
96
useActiveWalletConnectionStatus,
10-
useSwitchActiveWalletChain,
117
} from "../../../core/hooks/wallets/wallet-hooks.js";
128
import { WalletConnectionContext } from "../../../core/providers/wallet-connection.js";
139
import {
@@ -122,7 +118,6 @@ function ConnectButtonInner(
122118
},
123119
) {
124120
const activeAccount = useActiveAccount();
125-
const activeWalletChain = useActiveWalletChain();
126121
const contextTheme = useCustomTheme();
127122
const theme = props.theme || contextTheme || "dark";
128123
const connectionStatus = useActiveWalletConnectionStatus();
@@ -140,10 +135,6 @@ function ConnectButtonInner(
140135
// const authConfig = useThirdwebAuthContext();
141136
// const { logout } = useLogout();
142137
// const isNetworkMismatch = useNetworkMismatch();
143-
const isNetworkMismatch =
144-
activeWalletChain?.id !== undefined &&
145-
props.chain?.id &&
146-
activeWalletChain.id !== props.chain.id;
147138

148139
// const [showSignatureModal, setShowSignatureModal] = useState(false);
149140
// const address = useActiveWalletAddress();
@@ -263,16 +254,6 @@ function ConnectButtonInner(
263254
}
264255

265256
// switch network button
266-
if (props.chain && isNetworkMismatch) {
267-
return (
268-
<SwitchNetworkButton
269-
style={props.switchButton?.style}
270-
className={props.switchButton?.className}
271-
switchNetworkBtnTitle={props.switchButton?.label}
272-
targetChain={props.chain}
273-
/>
274-
);
275-
}
276257

277258
// sign in button
278259
// else if (requiresSignIn) {
@@ -316,63 +297,15 @@ function ConnectButtonInner(
316297
// }
317298
}}
318299
chains={props?.chains || []}
300+
chain={props.chain}
301+
switchButton={props.switchButton}
319302
/>
320303
);
321304
})()}
322305
</CustomThemeProvider>
323306
);
324307
}
325308

326-
/**
327-
* @internal
328-
*/
329-
function SwitchNetworkButton(props: {
330-
style?: React.CSSProperties;
331-
className?: string;
332-
switchNetworkBtnTitle?: string;
333-
targetChain: Chain;
334-
}) {
335-
const switchChain = useSwitchActiveWalletChain();
336-
const [switching, setSwitching] = useState(false);
337-
const locale = useWalletConnectionCtx().connectLocale;
338-
339-
const switchNetworkBtnTitle =
340-
props.switchNetworkBtnTitle ?? locale.switchNetwork;
341-
342-
return (
343-
<AnimatedButton
344-
className={`${TW_CONNECT_WALLET}--switch-network ${
345-
props.className || ""
346-
}`}
347-
variant="primary"
348-
type="button"
349-
data-is-loading={switching}
350-
data-test="switch-network-button"
351-
disabled={switching}
352-
onClick={async () => {
353-
setSwitching(true);
354-
try {
355-
await switchChain(props.targetChain);
356-
} catch (e) {
357-
console.error(e);
358-
}
359-
setSwitching(false);
360-
}}
361-
style={{
362-
minWidth: "140px",
363-
...props.style,
364-
}}
365-
aria-label={switching ? locale.switchingNetwork : undefined}
366-
>
367-
{switching ? (
368-
<Spinner size="sm" color="primaryButtonText" />
369-
) : (
370-
switchNetworkBtnTitle
371-
)}
372-
</AnimatedButton>
373-
);
374-
}
375-
376309
const AnimatedButton = /* @__PURE__ */ styled(Button)({
377310
animation: `${fadeInAnimation} 300ms ease`,
378311
});

packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
useActiveWalletChain,
3030
// useConnect,
3131
useDisconnect,
32+
useSwitchActiveWalletChain,
3233
} from "../../../core/hooks/wallets/wallet-hooks.js";
3334
import { shortenString } from "../../../core/utils/addresses.js";
3435
import { ChainIcon } from "../components/ChainIcon.js";
@@ -37,6 +38,7 @@ import { Img } from "../components/Img.js";
3738
import { Modal } from "../components/Modal.js";
3839
import { Skeleton } from "../components/Skeleton.js";
3940
import { Spacer } from "../components/Spacer.js";
41+
import { Spinner } from "../components/Spinner.js";
4042
import { WalletImage } from "../components/WalletImage.js";
4143
import { Container, Line } from "../components/basic.js";
4244
import { Button, IconButton } from "../components/buttons.js";
@@ -53,6 +55,7 @@ import {
5355
spacing,
5456
} from "../design-system/index.js";
5557
import type {
58+
ConnectButtonProps,
5659
ConnectButton_detailsButtonOptions,
5760
ConnectButton_detailsModalOptions,
5861
} from "./ConnectWalletProps.js";
@@ -61,7 +64,7 @@ import { onModalUnmount } from "./constants.js";
6164
import type { SupportedTokens } from "./defaultTokens.js";
6265
import { FundsIcon } from "./icons/FundsIcon.js";
6366
import { WalletIcon } from "./icons/WalletIcon.js";
64-
import { SwapScreen } from "./screens/Buy/SwapScreen.js";
67+
import { BuyScreen } from "./screens/Buy/SwapScreen.js";
6568
import { swapTransactionsStore } from "./screens/Buy/swap/pendingSwapTx.js";
6669
import { ReceiveFunds } from "./screens/ReceiveFunds.js";
6770
import { SendFunds } from "./screens/SendFunds.js";
@@ -90,6 +93,8 @@ export const ConnectedWalletDetails: React.FC<{
9093
theme: "light" | "dark" | Theme;
9194
supportedTokens: SupportedTokens;
9295
chains: Chain[];
96+
chain?: Chain;
97+
switchButton: ConnectButtonProps["switchButton"];
9398
}> = (props) => {
9499
const { connectLocale: locale, client } = useWalletConnectionCtx();
95100
const activeWallet = useActiveWallet();
@@ -176,10 +181,20 @@ export const ConnectedWalletDetails: React.FC<{
176181
// avatarOrWalletIconUrl = smartWalletMetadata.iconUrl;
177182
// }
178183

184+
const isNetworkMismatch =
185+
props.chain && walletChain && walletChain.id !== props.chain.id;
186+
179187
const trigger = props.detailsButton?.render ? (
180188
<div>
181189
<props.detailsButton.render />
182190
</div>
191+
) : props.chain && isNetworkMismatch ? (
192+
<SwitchNetworkButton
193+
style={props.switchButton?.style}
194+
className={props.switchButton?.className}
195+
switchNetworkBtnTitle={props.switchButton?.label}
196+
targetChain={props.chain}
197+
/>
183198
) : (
184199
<WalletInfoButton
185200
type="button"
@@ -630,7 +645,7 @@ export const ConnectedWalletDetails: React.FC<{
630645
// swap tokens
631646
else if (screen === "buy") {
632647
content = (
633-
<SwapScreen
648+
<BuyScreen
634649
client={client}
635650
onBack={() => setScreen("main")}
636651
supportedTokens={props.supportedTokens}
@@ -699,7 +714,7 @@ const MenuButton = /* @__PURE__ */ StyledButton(() => {
699714
lineHeight: 1.3,
700715
transition: "background-color 200ms ease, transform 200ms ease",
701716
"&:hover": {
702-
backgroundColor: theme.colors.walletSelectorButtonHoverBg,
717+
backgroundColor: theme.colors.tertiaryBg,
703718
transform: "scale(1.01)",
704719
svg: {
705720
color: theme.colors.accentText,
@@ -876,3 +891,51 @@ function InAppWalletEmail() {
876891

877892
return null;
878893
}
894+
895+
/**
896+
* @internal
897+
*/
898+
function SwitchNetworkButton(props: {
899+
style?: React.CSSProperties;
900+
className?: string;
901+
switchNetworkBtnTitle?: string;
902+
targetChain: Chain;
903+
}) {
904+
const switchChain = useSwitchActiveWalletChain();
905+
const [switching, setSwitching] = useState(false);
906+
const locale = useWalletConnectionCtx().connectLocale;
907+
908+
const switchNetworkBtnTitle =
909+
props.switchNetworkBtnTitle ?? locale.switchNetwork;
910+
911+
return (
912+
<Button
913+
className={`tw-connect-wallet--switch-network ${props.className || ""}`}
914+
variant="primary"
915+
type="button"
916+
data-is-loading={switching}
917+
data-test="switch-network-button"
918+
disabled={switching}
919+
onClick={async () => {
920+
setSwitching(true);
921+
try {
922+
await switchChain(props.targetChain);
923+
} catch (e) {
924+
console.error(e);
925+
}
926+
setSwitching(false);
927+
}}
928+
style={{
929+
minWidth: "140px",
930+
...props.style,
931+
}}
932+
aria-label={switching ? locale.switchingNetwork : undefined}
933+
>
934+
{switching ? (
935+
<Spinner size="sm" color="primaryButtonText" />
936+
) : (
937+
switchNetworkBtnTitle
938+
)}
939+
</Button>
940+
);
941+
}

packages/thirdweb/src/react/web/ui/ConnectWallet/WalletEntryButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ export const WalletButton = /* @__PURE__ */ StyledButton(() => {
7777
borderRadius: radius.md,
7878
padding: `${spacing.xs} ${spacing.xs}`,
7979
"&:hover": {
80-
backgroundColor: theme.colors.walletSelectorButtonHoverBg,
80+
backgroundColor: theme.colors.tertiaryBg,
8181
transform: "scale(1.01)",
8282
},
8383
'&[data-active="true"]': {
84-
backgroundColor: theme.colors.walletSelectorButtonHoverBg,
84+
backgroundColor: theme.colors.tertiaryBg,
8585
},
8686
transition: "background-color 200ms ease, transform 200ms ease",
8787
};

packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ const ShowAllWalletsIcon = /* @__PURE__ */ StyledDiv(() => {
584584
return {
585585
width: `${iconSize.xl}px`,
586586
height: `${iconSize.xl}px`,
587-
backgroundColor: theme.colors.walletSelectorButtonHoverBg,
587+
backgroundColor: theme.colors.tertiaryBg,
588588
border: `2px solid ${theme.colors.borderColor}`,
589589
borderRadius: radius.md,
590590
display: "grid",
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import styled from "@emotion/styled";
2+
import { useMemo, useState } from "react";
3+
import { isAddress } from "../../../../../../utils/address.js";
4+
import type {
5+
Account,
6+
Wallet,
7+
} from "../../../../../../wallets/interfaces/wallet.js";
8+
import { Spacer } from "../../../components/Spacer.js";
9+
import { Container } from "../../../components/basic.js";
10+
import { Button } from "../../../components/buttons.js";
11+
import { Input } from "../../../components/formElements.js";
12+
import { Text } from "../../../components/text.js";
13+
import { useCustomTheme } from "../../../design-system/CustomThemeProvider.js";
14+
import { AccountSelectorButton } from "./AccountSelectorButton.js";
15+
16+
/**
17+
* @internal
18+
*/
19+
export function AccountSelectionScreen(props: {
20+
activeWallet: Wallet;
21+
activeAccount: Account;
22+
onSelect: (address: string) => void;
23+
}) {
24+
const [address, setAddress] = useState<string>("");
25+
const isValidAddress = useMemo(() => isAddress(address), [address]);
26+
const showError = !!address && !isValidAddress;
27+
28+
return (
29+
<div>
30+
<Text size="lg" color="primaryText">
31+
Send to
32+
</Text>
33+
<Spacer y="lg" />
34+
<Container
35+
flex="row"
36+
center="y"
37+
style={{
38+
flexWrap: "nowrap",
39+
height: "50px",
40+
}}
41+
>
42+
<StyledInput
43+
data-is-error={showError}
44+
value={address}
45+
placeholder="Enter wallet address"
46+
variant="outline"
47+
onChange={(e) => setAddress(e.target.value)}
48+
/>
49+
<Button
50+
variant="accent"
51+
disabled={!isValidAddress}
52+
style={{
53+
height: "100%",
54+
minWidth: "100px",
55+
borderTopLeftRadius: 0,
56+
borderBottomLeftRadius: 0,
57+
}}
58+
onClick={() => {
59+
props.onSelect(address);
60+
}}
61+
>
62+
Confirm
63+
</Button>
64+
</Container>
65+
66+
{showError && (
67+
<>
68+
<Spacer y="xxs" />
69+
<Text color="danger" size="sm">
70+
Invalid address
71+
</Text>
72+
</>
73+
)}
74+
75+
<Spacer y="xl" />
76+
<Text size="sm">Connected</Text>
77+
<Spacer y="xs" />
78+
<AccountSelectorButton
79+
address={props.activeAccount.address}
80+
activeAccount={props.activeAccount}
81+
activeWallet={props.activeWallet}
82+
onClick={() => {
83+
props.onSelect(props.activeAccount.address);
84+
}}
85+
/>
86+
</div>
87+
);
88+
}
89+
90+
const StyledInput = /* @__PURE__ */ styled(Input)(() => {
91+
const theme = useCustomTheme();
92+
return {
93+
border: `1.5px solid ${theme.colors.borderColor}`,
94+
borderTopRightRadius: 0,
95+
borderBottomRightRadius: 0,
96+
height: "100%",
97+
boxSizing: "border-box",
98+
boxShadow: "none",
99+
borderRight: "none",
100+
"&:focus": {
101+
boxShadow: "none",
102+
borderColor: theme.colors.accentText,
103+
},
104+
"&[data-is-error='true']": {
105+
boxShadow: "none",
106+
borderColor: theme.colors.danger,
107+
},
108+
};
109+
});

0 commit comments

Comments
 (0)