Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/user-profile-web3-wallets-section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
1 change: 1 addition & 0 deletions packages/swingset/src/components/DocsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const docModules: Record<string, Record<string, React.ComponentType>> = {
'user-profile-connected-accounts-section': dynamic(
() => import('../stories/user-profile-connected-accounts-section.mdx'),
),
'user-profile-web3wallets-section': dynamic(() => import('../stories/user-profile-web3-wallets-section.mdx')),
},
organization: {
'organization-profile': dynamic(() => import('../stories/organization-profile.mdx')),
Expand Down
9 changes: 9 additions & 0 deletions packages/swingset/src/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ import {
Default as UserProfileConnectedAccountsSectionDefault,
meta as userProfileConnectedAccountsSectionMeta,
} from '../stories/user-profile-connected-accounts-section.stories';
import {
Default as UserProfileWeb3WalletsSectionDefault,
meta as userProfileWeb3WalletsSectionMeta,
} from '../stories/user-profile-web3-wallets-section.stories';
import { toSlug } from './slug';
import type { StoryModule } from './types';

Expand Down Expand Up @@ -293,12 +297,17 @@ const userProfileConnectedAccountsSectionModule: StoryModule = {
meta: userProfileConnectedAccountsSectionMeta,
Default: UserProfileConnectedAccountsSectionDefault,
};
const userProfileWeb3WalletsSectionModule: StoryModule = {
meta: userProfileWeb3WalletsSectionMeta,
Default: UserProfileWeb3WalletsSectionDefault,
};

export const registry: StoryModule[] = [
// User
userButtonModule,
userProfileAccountSectionModule,
userProfileConnectedAccountsSectionModule,
userProfileWeb3WalletsSectionModule,
// Organization
organizationProfileModule,
organizationProfileGeneralPanelModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Stories from './user-profile-web3-wallets-section.stories';

# UserProfileWeb3WalletsSection

Web3 wallet identities, verification state, primary status, and wallet actions.

<Story name='Default' storyModule={Stories} composition={[{ name: 'SettingsGroup', href: '/blocks/settings-group', layer: 'Blocks' }]} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/** @jsxImportSource @emotion/react */
import { UserProfileWeb3WalletsSectionView } from '@clerk/ui/mosaic/user-profile/user-profile-web3-wallets-section.view';

import type { StoryMeta } from '@/lib/types';

export { default as __source } from './user-profile-web3-wallets-section.stories?raw';

export const meta: StoryMeta = {
group: 'User',
title: 'UserProfileWeb3WalletsSection',
source: 'packages/ui/src/mosaic/user-profile/user-profile-web3-wallets-section.view.tsx',
styleEngine: 'stylex',
};

export function Default() {
return (
<UserProfileWeb3WalletsSectionView
wallets={[
{
id: 'metamask',
address: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
provider: 'MetaMask',
iconUrl: 'https://img.clerk.com/static/metamask.svg',
isPrimary: true,
isVerified: true,
},
]}
onAdd={() => undefined}
onManage={() => undefined}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export const styles = stylex.create({
height: space['6'],
width: space['6'],
},
sectionHeader: {
alignItems: 'flex-end',
columnGap: space['4'],
display: 'flex',
justifyContent: 'space-between',
minWidth: 0,
},
sectionTitle: {
flexGrow: 1,
minWidth: 0,
},
root: {
gap: space['6'],
display: 'flex',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import * as stylex from '@stylexjs/stylex';

import { SettingsGroup } from '../block/settings-group';
import { Badge } from '../components/badge';
import { Button } from '../components/button';
import type { UserProfileMenuAction } from './user-profile-action-menu';
import { UserProfileActionMenu } from './user-profile-action-menu';
import { styles } from './user-profile-profile-panel.styles';

export interface UserProfileWeb3Wallet {
id: string;
address: string;
provider?: string;
iconUrl?: string;
isPrimary?: boolean;
isVerified?: boolean;
canRemove?: boolean;
}

export interface UserProfileWeb3WalletsSectionViewProps {
wallets: UserProfileWeb3Wallet[];
onAdd?: () => void;
onManage?: (id: string) => void;
onSetPrimary?: (id: string) => void;
onRemove?: (id: string) => void;
}

const shortenWeb3Address = (address: string) => {
if (address.length <= 10) {
return address;
}

return `${address.slice(0, 6)}...${address.slice(-4)}`;
};

export function UserProfileWeb3WalletsSectionView({
wallets,
onAdd,
onManage,
onSetPrimary,
onRemove,
}: UserProfileWeb3WalletsSectionViewProps) {
return (
<SettingsGroup.Root>
<div {...stylex.props(styles.sectionHeader)}>
<SettingsGroup.Title {...stylex.props(styles.sectionTitle)}>Web3 wallets</SettingsGroup.Title>
{onAdd ? (
<Button
color='neutral'
size='sm'
variant='outline'
onClick={onAdd}
>
Add
</Button>
) : null}
</div>
<SettingsGroup.List>
{wallets.map(wallet => {
const actions: UserProfileMenuAction[] = [];
const hasExplicitActions = Boolean(onSetPrimary || onRemove);

if (!wallet.isPrimary && wallet.isVerified !== false && onSetPrimary) {
actions.push({ label: 'Set as primary', onClick: () => onSetPrimary(wallet.id) });
}

if (onRemove && wallet.canRemove !== false) {
actions.push({ label: 'Remove wallet', color: 'negative', onClick: () => onRemove(wallet.id) });
}

if (!hasExplicitActions && onManage) {
actions.push({ label: 'Manage', onClick: () => onManage(wallet.id) });
}

const address = shortenWeb3Address(wallet.address);
const badges = (
<>
{wallet.isPrimary ? (
<Badge
color='neutral'
style={{ backgroundColor: 'rgba(23, 23, 23, 0.06)', color: 'inherit' }}
>
Primary
</Badge>
) : null}
{wallet.isVerified === false ? <Badge color='warning'>Unverified</Badge> : null}
</>
);

return (
<SettingsGroup.Row key={wallet.id}>
{wallet.iconUrl ? (
<SettingsGroup.Media>
<img
alt=''
src={wallet.iconUrl}
{...stylex.props(styles.providerIcon)}
/>
</SettingsGroup.Media>
) : null}
<SettingsGroup.Label description={wallet.provider ? address : undefined}>
<span {...stylex.props(styles.contactValue)}>
{wallet.provider ?? address}
{badges}
</span>
</SettingsGroup.Label>
<SettingsGroup.Control>
<UserProfileActionMenu
actions={actions}
label={`Manage ${wallet.provider ?? address}`}
/>
</SettingsGroup.Control>
</SettingsGroup.Row>
);
})}
</SettingsGroup.List>
</SettingsGroup.Root>
);
}
Loading