Skip to content

Commit fbfa14c

Browse files
committed
fix(clerk-js): clean up Solana wallet connection flow in UserProfile with reused Web3SolanaWalletButtons component
Signed-off-by: Kenton Duprey <[email protected]>
1 parent 52e69f3 commit fbfa14c

File tree

2 files changed

+110
-151
lines changed

2 files changed

+110
-151
lines changed

packages/clerk-js/src/ui/components/UserProfile/Web3Form.tsx

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,44 @@
11
import { useReverification, useUser } from '@clerk/shared/react';
22
import type { Web3Provider, Web3Strategy } from '@clerk/shared/types';
33

4-
import { useWizard, Wizard } from '@/ui/common';
54
import { Web3SelectWalletScreen } from '@/ui/components/UserProfile/Web3SelectWalletScreen';
6-
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
5+
import { Action } from '@/ui/elements/Action';
6+
import { useActionContext } from '@/ui/elements/Action/ActionRoot';
7+
import { useCardState } from '@/ui/elements/contexts';
78
import { ProfileSection } from '@/ui/elements/Section';
89
import { getFieldError, handleError } from '@/ui/utils/errorHandler';
910

1011
import { generateWeb3Signature, getWeb3Identifier } from '../../../utils/web3';
1112
import { descriptors, Image, localizationKeys, Text } from '../../customizables';
1213
import { useEnabledThirdPartyProviders } from '../../hooks';
1314

14-
export const AddWeb3WalletActionMenu = withCardStateProvider(() => {
15+
export const AddWeb3WalletActionMenu = () => {
1516
const card = useCardState();
17+
const { open } = useActionContext();
1618
const { user } = useUser();
19+
const { strategies, strategyToDisplayData } = useEnabledThirdPartyProviders();
20+
const enabledStrategies = strategies.filter(s => s.startsWith('web3')) as Web3Strategy[];
21+
const connectedStrategies = user?.verifiedWeb3Wallets?.map(w => w.verification.strategy) ?? ([] as Web3Strategy[]);
22+
const unconnectedStrategies = enabledStrategies.filter(strategy => {
23+
return !connectedStrategies.includes(strategy) && strategyToDisplayData[strategy];
24+
});
1725

18-
const wizard = useWizard();
26+
if (unconnectedStrategies.length === 0) {
27+
return null;
28+
}
1929

2030
const createWeb3Wallet = useReverification((identifier: string) =>
2131
user?.createWeb3Wallet({ web3Wallet: identifier }),
2232
);
2333

34+
// If the user selects `web3_solana_signature` as their strategy,
35+
// we need to obtain the wallet name to use when connecting and signing the message during the auth flow
36+
//
37+
// Otherwise, our current Web3 providers are all based on the wallet provider name,
38+
// which is sufficient for our current use case when connecting to a wallet.
2439
const connect = async ({ strategy, walletName }: { strategy: Web3Strategy; walletName?: string }) => {
2540
if (strategy === 'web3_solana_signature' && !walletName) {
26-
wizard.nextStep();
41+
open('web3Wallets');
2742
return;
2843
}
2944
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
@@ -54,67 +69,48 @@ export const AddWeb3WalletActionMenu = withCardStateProvider(() => {
5469
}
5570
};
5671

57-
return (
58-
<Wizard {...wizard.props}>
59-
<Web3SelectStrategyScreen onConnect={connect} />
60-
61-
<Web3SelectWalletScreen onConnect={connect} />
62-
</Wizard>
63-
);
64-
});
65-
66-
const Web3SelectStrategyScreen = ({
67-
onConnect,
68-
}: {
69-
onConnect: (params: { strategy: Web3Strategy; walletName?: string }) => Promise<void>;
70-
}) => {
71-
const card = useCardState();
72-
const { user } = useUser();
73-
const { strategies, strategyToDisplayData } = useEnabledThirdPartyProviders();
74-
const enabledStrategies = strategies.filter(s => s.startsWith('web3')) as Web3Strategy[];
75-
const connectedStrategies = user?.verifiedWeb3Wallets?.map(w => w.verification.strategy) ?? ([] as Web3Strategy[]);
76-
const unconnectedStrategies = enabledStrategies.filter(strategy => {
77-
return !connectedStrategies.includes(strategy) && strategyToDisplayData[strategy];
78-
});
79-
80-
if (unconnectedStrategies.length === 0) {
81-
return null;
82-
}
83-
8472
return (
8573
<>
86-
<ProfileSection.ActionMenu
87-
id='web3Wallets'
88-
triggerLocalizationKey={localizationKeys('userProfile.start.web3WalletsSection.primaryButton')}
89-
>
90-
{unconnectedStrategies.map(strategy => (
91-
<ProfileSection.ActionMenuItem
92-
key={strategy}
93-
id={strategyToDisplayData[strategy].id}
94-
onClick={() => onConnect({ strategy })}
95-
isLoading={card.loadingMetadata === strategy}
96-
isDisabled={card.isLoading}
97-
localizationKey={localizationKeys('userProfile.web3WalletPage.web3WalletButtonsBlockButton', {
98-
provider: strategyToDisplayData[strategy].name,
99-
})}
100-
sx={t => ({
101-
justifyContent: 'start',
102-
gap: t.space.$2,
103-
})}
104-
leftIcon={
105-
<Image
106-
elementDescriptor={descriptors.providerIcon}
107-
elementId={descriptors.providerIcon.setId(strategyToDisplayData[strategy].id)}
108-
isLoading={card.loadingMetadata === strategy}
109-
isDisabled={card.isLoading}
110-
src={strategyToDisplayData[strategy].iconUrl}
111-
alt={`Connect ${strategyToDisplayData[strategy].name}`}
112-
sx={theme => ({ width: theme.sizes.$5 })}
113-
/>
114-
}
115-
/>
116-
))}
117-
</ProfileSection.ActionMenu>
74+
<Action.Closed value='web3Wallets'>
75+
<ProfileSection.ActionMenu
76+
id='web3Wallets'
77+
triggerLocalizationKey={localizationKeys('userProfile.start.web3WalletsSection.primaryButton')}
78+
>
79+
{unconnectedStrategies.map(strategy => (
80+
<ProfileSection.ActionMenuItem
81+
key={strategy}
82+
id={strategyToDisplayData[strategy].id}
83+
onClick={() => connect({ strategy })}
84+
isLoading={card.loadingMetadata === strategy}
85+
isDisabled={card.isLoading}
86+
localizationKey={localizationKeys('userProfile.web3WalletPage.web3WalletButtonsBlockButton', {
87+
provider: strategyToDisplayData[strategy].name,
88+
})}
89+
sx={t => ({
90+
justifyContent: 'start',
91+
gap: t.space.$2,
92+
})}
93+
leftIcon={
94+
<Image
95+
elementDescriptor={descriptors.providerIcon}
96+
elementId={descriptors.providerIcon.setId(strategyToDisplayData[strategy].id)}
97+
isLoading={card.loadingMetadata === strategy}
98+
isDisabled={card.isLoading}
99+
src={strategyToDisplayData[strategy].iconUrl}
100+
alt={`Connect ${strategyToDisplayData[strategy].name}`}
101+
sx={theme => ({ width: theme.sizes.$5 })}
102+
/>
103+
}
104+
/>
105+
))}
106+
</ProfileSection.ActionMenu>
107+
</Action.Closed>
108+
<Action.Open value='web3Wallets'>
109+
<Action.Card>
110+
<Web3SelectWalletScreen onConnect={connect} />
111+
</Action.Card>
112+
</Action.Open>
113+
118114
{card.error && (
119115
<Text
120116
colorScheme='danger'
Lines changed: 50 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,32 @@
11
import type { Web3Strategy } from '@clerk/shared/types';
2-
import { WalletReadyState } from '@solana/wallet-adapter-base';
3-
import { ConnectionProvider, useWallet, WalletProvider } from '@solana/wallet-adapter-react';
4-
import { MAINNET_ENDPOINT } from '@solana/wallet-standard';
5-
import { useMemo } from 'react';
2+
import { lazy, Suspense } from 'react';
63

7-
import { Action } from '@/ui/elements/Action';
84
import { useActionContext } from '@/ui/elements/Action/ActionRoot';
95
import { useCardState } from '@/ui/elements/contexts';
106
import { Form } from '@/ui/elements/Form';
7+
import { FormButtonContainer } from '@/ui/elements/FormButtons';
118
import { FormContainer } from '@/ui/elements/FormContainer';
129

13-
import { Button, Grid, Image, localizationKeys, Text } from '../../customizables';
10+
import { Button, descriptors, Flex, localizationKeys, Spinner } from '../../customizables';
11+
12+
const Web3SolanaWalletButtons = lazy(() =>
13+
import(/* webpackChunkName: "web3-wallet-buttons" */ '@/ui/elements/Web3SolanaWalletButtons').then(m => ({
14+
default: m.Web3SolanaWalletButtons,
15+
})),
16+
);
1417

1518
export type Web3SelectWalletProps = {
1619
onConnect: (params: { strategy: Web3Strategy; walletName: string }) => Promise<void>;
1720
};
1821

19-
const Web3SelectWalletInner = ({ onConnect }: Web3SelectWalletProps) => {
22+
export const Web3SelectWalletScreen = ({ onConnect }: Web3SelectWalletProps) => {
2023
const card = useCardState();
21-
const { wallets } = useWallet();
2224
const { close } = useActionContext();
2325

24-
const installedWallets = useMemo(
25-
() =>
26-
wallets
27-
.filter(w => w.readyState === WalletReadyState.Installed)
28-
.map(wallet => ({
29-
name: wallet.adapter.name,
30-
icon: wallet.adapter.icon,
31-
})),
32-
[wallets],
33-
);
34-
35-
if (installedWallets.length === 0) {
36-
return null;
37-
}
38-
39-
const onClick = async (wallet: { name: string; icon: string }) => {
40-
card.setLoading(wallet.name);
26+
const onClick = async ({ walletName }: { walletName: string }) => {
27+
card.setLoading(walletName);
4128
try {
42-
await onConnect({ strategy: 'web3_solana_signature', walletName: wallet.name });
29+
await onConnect({ strategy: 'web3_solana_signature', walletName });
4330
card.setIdle();
4431
} catch (err) {
4532
card.setIdle();
@@ -50,67 +37,43 @@ const Web3SelectWalletInner = ({ onConnect }: Web3SelectWalletProps) => {
5037
};
5138

5239
return (
53-
<Action.Card>
54-
<FormContainer
55-
headerTitle='Add Solana wallet'
56-
headerSubtitle={localizationKeys('userProfile.start.web3WalletsSection.primaryButton')}
57-
>
58-
<Form.Root>
59-
<Form.ControlRow elementId='web3WalletName'>
60-
<Grid
61-
columns={2}
62-
gap={3}
40+
<FormContainer
41+
headerTitle={localizationKeys('userProfile.start.web3WalletsSection.web3SolanaWalletsSection.title')}
42+
headerSubtitle={localizationKeys('userProfile.start.web3WalletsSection.web3SolanaWalletsSection.subtitle')}
43+
>
44+
<Form.Root>
45+
<Suspense
46+
fallback={
47+
<Flex
48+
direction={'row'}
49+
align={'center'}
50+
justify={'center'}
51+
sx={t => ({
52+
height: '100%',
53+
minHeight: t.sizes.$32,
54+
})}
6355
>
64-
{installedWallets.map(wallet => (
65-
<Button
66-
key={wallet.name}
67-
textVariant='buttonLarge'
68-
isDisabled={card.isLoading}
69-
isLoading={card.isLoading && card.loadingMetadata === wallet.name}
70-
sx={theme => ({
71-
gap: theme.space.$4,
72-
justifyContent: 'flex-start',
73-
})}
74-
variant='outline'
75-
onClick={() => onClick(wallet)}
76-
>
77-
{wallet.icon && (
78-
<Image
79-
src={wallet.icon}
80-
alt={wallet.name}
81-
sx={theme => ({ width: theme.sizes.$4, height: 'auto', maxWidth: '100%' })}
82-
/>
83-
)}
84-
<Text
85-
as='span'
86-
truncate
87-
variant='buttonLarge'
88-
>
89-
{wallet.name}
90-
</Text>
91-
</Button>
92-
))}
93-
</Grid>
94-
</Form.ControlRow>
95-
</Form.Root>
96-
</FormContainer>
97-
</Action.Card>
98-
);
99-
};
100-
101-
export const Web3SelectWalletScreen = ({ onConnect }: Web3SelectWalletProps) => {
102-
const network = MAINNET_ENDPOINT;
103-
const wallets = useMemo(() => [], [network]);
104-
return (
105-
<ConnectionProvider endpoint={network}>
106-
<WalletProvider
107-
wallets={wallets}
108-
onError={err => {
109-
console.error(err);
110-
}}
111-
>
112-
<Web3SelectWalletInner onConnect={onConnect} />
113-
</WalletProvider>
114-
</ConnectionProvider>
56+
<Spinner
57+
size={'lg'}
58+
colorScheme={'primary'}
59+
elementDescriptor={descriptors.spinner}
60+
/>
61+
</Flex>
62+
}
63+
>
64+
<Web3SolanaWalletButtons web3AuthCallback={onClick} />
65+
</Suspense>
66+
<FormButtonContainer>
67+
<Button
68+
variant='ghost'
69+
onClick={() => {
70+
close();
71+
}}
72+
localizationKey={localizationKeys('userProfile.formButtonReset')}
73+
elementDescriptor={descriptors.formButtonReset}
74+
/>
75+
</FormButtonContainer>
76+
</Form.Root>
77+
</FormContainer>
11578
);
11679
};

0 commit comments

Comments
 (0)