Skip to content

Commit 375a32d

Browse files
authored
feat(clerk-js,localizations,types): Add ability for users to connect a Solana enabled wallet via <UserProfile /> (#7435)
Signed-off-by: Kenton Duprey <[email protected]>
1 parent 175883b commit 375a32d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+974
-559
lines changed

.changeset/afraid-apes-cough.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/localizations': minor
3+
'@clerk/clerk-js': minor
4+
'@clerk/shared': minor
5+
---
6+
7+
Add Web3 Solana support to `<UserProfile />`

packages/clerk-js/bundlewatch.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.js", "maxSize": "922.1KB" },
3+
{ "path": "./dist/clerk.js", "maxSize": "922.6KB" },
44
{ "path": "./dist/clerk.browser.js", "maxSize": "87KB" },
55
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "129KB" },
66
{ "path": "./dist/clerk.headless*.js", "maxSize": "66KB" },

packages/clerk-js/src/ui/common/WalletInitialIcon.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ export const WalletInitialIcon = (props: WalletInitialIconProps) => {
1616
return (
1717
<Box
1818
as='span'
19-
elementDescriptor={[descriptors.walletIcon, descriptors.web3WalletButtonsWalletInitialIcon]}
20-
elementId={descriptors.web3WalletButtonsWalletInitialIcon.setId(id)}
19+
elementDescriptor={[descriptors.walletIcon, descriptors.web3SolanaWalletButtonsWalletInitialIcon]}
20+
elementId={descriptors.web3SolanaWalletButtonsWalletInitialIcon.setId(id)}
2121
sx={t => ({
2222
...common.centeredFlex('inline-flex'),
2323
width: t.space.$4,

packages/clerk-js/src/ui/components/SignIn/SignInFactorOneSolanaWalletsCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
99
import { Header } from '@/ui/elements/Header';
1010
import { web3CallbackErrorHandler } from '@/ui/utils/web3CallbackErrorHandler';
1111

12-
const Web3WalletButtons = lazy(() =>
12+
const Web3SolanaWalletButtons = lazy(() =>
1313
import(/* webpackChunkName: "web3-wallet-buttons" */ '@/ui/elements/Web3SolanaWalletButtons').then(m => ({
1414
default: m.Web3SolanaWalletButtons,
1515
})),
@@ -60,7 +60,7 @@ const SignInFactorOneSolanaWalletsCardInner = () => {
6060
</Flex>
6161
}
6262
>
63-
<Web3WalletButtons
63+
<Web3SolanaWalletButtons
6464
web3AuthCallback={({ walletName }) => {
6565
return clerk
6666
.authenticateWithWeb3({

packages/clerk-js/src/ui/components/SignUp/SignUpStartSolanaWalletsCard.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { useClerk } from '@clerk/shared/react';
22
import { lazy, Suspense } from 'react';
33

44
import { withRedirectToAfterSignUp, withRedirectToSignUpTask } from '@/ui/common/withRedirect';
5-
import { descriptors, Flex, Flow, localizationKeys } from '@/ui/customizables';
5+
import { descriptors, Flex, Flow, localizationKeys, Spinner } from '@/ui/customizables';
66
import { BackLink } from '@/ui/elements/BackLink';
77
import { Card } from '@/ui/elements/Card';
88
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
99
import { Header } from '@/ui/elements/Header';
1010
import { web3CallbackErrorHandler } from '@/ui/utils/web3CallbackErrorHandler';
1111

12-
const Web3WalletButtons = lazy(() =>
12+
const Web3SolanaWalletButtons = lazy(() =>
1313
import(/* webpackChunkName: "web3-wallet-buttons" */ '@/ui/elements/Web3SolanaWalletButtons').then(m => ({
1414
default: m.Web3SolanaWalletButtons,
1515
})),
@@ -41,8 +41,26 @@ const SignUpStartSolanaWalletsCardInner = () => {
4141
direction='col'
4242
gap={4}
4343
>
44-
<Suspense fallback={null}>
45-
<Web3WalletButtons
44+
<Suspense
45+
fallback={
46+
<Flex
47+
direction={'row'}
48+
align={'center'}
49+
justify={'center'}
50+
sx={t => ({
51+
height: '100%',
52+
minHeight: t.sizes.$32,
53+
})}
54+
>
55+
<Spinner
56+
size={'lg'}
57+
colorScheme={'primary'}
58+
elementDescriptor={descriptors.spinner}
59+
/>
60+
</Flex>
61+
}
62+
>
63+
<Web3SolanaWalletButtons
4664
web3AuthCallback={({ walletName }) => {
4765
return clerk
4866
.authenticateWithWeb3({
Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,52 @@
11
import { useReverification, useUser } from '@clerk/shared/react';
22
import type { Web3Provider, Web3Strategy } from '@clerk/shared/types';
33

4-
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
4+
import { Web3SelectSolanaWalletScreen } from '@/ui/components/UserProfile/Web3SelectSolanaWalletScreen';
5+
import { Action } from '@/ui/elements/Action';
6+
import { useActionContext } from '@/ui/elements/Action/ActionRoot';
7+
import { useCardState } from '@/ui/elements/contexts';
58
import { ProfileSection } from '@/ui/elements/Section';
69
import { getFieldError, handleError } from '@/ui/utils/errorHandler';
710

811
import { generateWeb3Signature, getWeb3Identifier } from '../../../utils/web3';
912
import { descriptors, Image, localizationKeys, Text } from '../../customizables';
1013
import { useEnabledThirdPartyProviders } from '../../hooks';
1114

12-
export const AddWeb3WalletActionMenu = withCardStateProvider(({ onClick }: { onClick?: () => void }) => {
15+
export const AddWeb3WalletActionMenu = () => {
1316
const card = useCardState();
17+
const { open } = useActionContext();
1418
const { user } = useUser();
1519
const { strategies, strategyToDisplayData } = useEnabledThirdPartyProviders();
16-
1720
const enabledStrategies = strategies.filter(s => s.startsWith('web3')) as Web3Strategy[];
18-
const connectedStrategies = user?.verifiedWeb3Wallets.map(w => w.verification.strategy) as Web3Strategy[];
21+
const connectedStrategies = user?.verifiedWeb3Wallets?.map(w => w.verification.strategy) ?? ([] as Web3Strategy[]);
1922
const unconnectedStrategies = enabledStrategies.filter(strategy => {
20-
return !connectedStrategies.includes(strategy);
23+
return !connectedStrategies.includes(strategy) && strategyToDisplayData[strategy];
2124
});
25+
26+
if (unconnectedStrategies.length === 0) {
27+
return null;
28+
}
29+
2230
const createWeb3Wallet = useReverification((identifier: string) =>
2331
user?.createWeb3Wallet({ web3Wallet: identifier }),
2432
);
2533

26-
const connect = async (strategy: Web3Strategy) => {
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.
39+
const connect = async ({ strategy, walletName }: { strategy: Web3Strategy; walletName?: string }) => {
40+
if (strategy === 'web3_solana_signature' && !walletName) {
41+
open('web3Wallets');
42+
return;
43+
}
2744
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
2845
card.setError(undefined);
2946

3047
try {
3148
card.setLoading(strategy);
32-
const identifier = await getWeb3Identifier({ provider });
49+
const identifier = await getWeb3Identifier({ provider, walletName });
3350

3451
if (!user) {
3552
throw new Error('user is not defined');
@@ -38,7 +55,7 @@ export const AddWeb3WalletActionMenu = withCardStateProvider(({ onClick }: { onC
3855
let web3Wallet = await createWeb3Wallet(identifier);
3956
web3Wallet = await web3Wallet?.prepareVerification({ strategy });
4057
const message = web3Wallet?.verification.message as string;
41-
const signature = await generateWeb3Signature({ identifier, nonce: message, provider });
58+
const signature = await generateWeb3Signature({ identifier, nonce: message, provider, walletName });
4259
await web3Wallet?.attemptVerification({ signature });
4360
card.setIdle();
4461
} catch (err) {
@@ -52,45 +69,48 @@ export const AddWeb3WalletActionMenu = withCardStateProvider(({ onClick }: { onC
5269
}
5370
};
5471

55-
if (unconnectedStrategies.length === 0) {
56-
return null;
57-
}
58-
5972
return (
6073
<>
61-
<ProfileSection.ActionMenu
62-
id='web3Wallets'
63-
triggerLocalizationKey={localizationKeys('userProfile.start.web3WalletsSection.primaryButton')}
64-
onClick={onClick}
65-
>
66-
{unconnectedStrategies.map(strategy => (
67-
<ProfileSection.ActionMenuItem
68-
key={strategy}
69-
id={strategyToDisplayData[strategy].id}
70-
onClick={() => connect(strategy)}
71-
isLoading={card.loadingMetadata === strategy}
72-
isDisabled={card.isLoading}
73-
localizationKey={localizationKeys('userProfile.web3WalletPage.web3WalletButtonsBlockButton', {
74-
provider: strategyToDisplayData[strategy].name,
75-
})}
76-
sx={t => ({
77-
justifyContent: 'start',
78-
gap: t.space.$2,
79-
})}
80-
leftIcon={
81-
<Image
82-
elementDescriptor={descriptors.providerIcon}
83-
elementId={descriptors.providerIcon.setId(strategyToDisplayData[strategy].id)}
84-
isLoading={card.loadingMetadata === strategy}
85-
isDisabled={card.isLoading}
86-
src={strategyToDisplayData[strategy].iconUrl}
87-
alt={`Connect ${strategyToDisplayData[strategy].name}`}
88-
sx={theme => ({ width: theme.sizes.$5 })}
89-
/>
90-
}
91-
/>
92-
))}
93-
</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+
<Web3SelectSolanaWalletScreen onConnect={connect} />
111+
</Action.Card>
112+
</Action.Open>
113+
94114
{card.error && (
95115
<Text
96116
colorScheme='danger'
@@ -104,4 +124,4 @@ export const AddWeb3WalletActionMenu = withCardStateProvider(({ onClick }: { onC
104124
)}
105125
</>
106126
);
107-
});
127+
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const Web3Section = withCardStateProvider(
108108
);
109109
})}
110110
</ProfileSection.ItemList>
111-
{shouldAllowCreation && <AddWeb3WalletActionMenu onClick={() => setActionValue(null)} />}
111+
{shouldAllowCreation && <AddWeb3WalletActionMenu />}
112112
</Action.Root>
113113
</ProfileSection.Root>
114114
);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { Web3Strategy } from '@clerk/shared/types';
2+
import { lazy, Suspense } from 'react';
3+
4+
import { useActionContext } from '@/ui/elements/Action/ActionRoot';
5+
import { useCardState } from '@/ui/elements/contexts';
6+
import { Form } from '@/ui/elements/Form';
7+
import { FormButtonContainer } from '@/ui/elements/FormButtons';
8+
import { FormContainer } from '@/ui/elements/FormContainer';
9+
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+
);
17+
18+
export type Web3SelectWalletProps = {
19+
onConnect: (params: { strategy: Web3Strategy; walletName: string }) => Promise<void>;
20+
};
21+
22+
export const Web3SelectSolanaWalletScreen = ({ onConnect }: Web3SelectWalletProps) => {
23+
const card = useCardState();
24+
const { close } = useActionContext();
25+
26+
const onClick = async ({ walletName }: { walletName: string }) => {
27+
card.setLoading(walletName);
28+
try {
29+
await onConnect({ strategy: 'web3_solana_signature', walletName });
30+
card.setIdle();
31+
} catch (err) {
32+
card.setIdle();
33+
console.error(err);
34+
} finally {
35+
close();
36+
}
37+
};
38+
39+
return (
40+
<FormContainer
41+
headerTitle={localizationKeys('userProfile.start.web3WalletsSection.web3SelectSolanaWalletScreen.title')}
42+
headerSubtitle={localizationKeys('userProfile.start.web3WalletsSection.web3SelectSolanaWalletScreen.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+
})}
55+
>
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>
78+
);
79+
};

packages/clerk-js/src/ui/customizables/elementDescriptors.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -516,13 +516,13 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([
516516
'enterpriseConnectionButton',
517517
'enterpriseConnectionButtonText',
518518

519-
'web3WalletButtonsRoot',
520-
'web3WalletButtons',
521-
'web3WalletButtonsIconButton',
522-
'web3WalletButtonsBlockButton',
523-
'web3WalletButtonsBlockButtonText',
524-
'web3WalletButtonsWalletIcon',
525-
'web3WalletButtonsWalletInitialIcon',
519+
'web3SolanaWalletButtonsRoot',
520+
'web3SolanaWalletButtons',
521+
'web3SolanaWalletButtonsIconButton',
522+
'web3SolanaWalletButtonsBlockButton',
523+
'web3SolanaWalletButtonsBlockButtonText',
524+
'web3SolanaWalletButtonsWalletIcon',
525+
'web3SolanaWalletButtonsWalletInitialIcon',
526526

527527
'walletIcon',
528528
'walletInitialIcon',

0 commit comments

Comments
 (0)