Skip to content

Commit

Permalink
feat: more refined ui flow for extension wallets (#3039)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnthecat authored Jan 24, 2025
1 parent cc16ee0 commit 4a1ab16
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useUnit } from 'effector-react';
import { type ReactNode } from 'react';

import { WalletType } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { nonNullable, nullable } from '@/shared/lib/utils';
import { Icon } from '@/shared/ui';
import { Dropdown, Label } from '@/shared/ui-kit';
import { walletIcon } from '../constants';
import { wallets } from '../model/wallets';

import { PairingModal } from './PairingModal';

export const DropdownOptions = () => {
const { t } = useI18n();
const polkadotjsExtension = useUnit(wallets.$polkadotJsExtensionWallet);
const talismanExtension = useUnit(wallets.$talismanExtensionWallet);
const subWalletExtension = useUnit(wallets.$subWalletExtensionWallet);

const installed: ReactNode[] = [];
const notInstalled: ReactNode[] = [];

const polkadotOption = (
<PairingModal extension="polkadot-js" title={t('onboarding.extension.polkadotJsTitle')}>
<Dropdown.Item disabled={nullable(polkadotjsExtension)}>
<Icon name={walletIcon[WalletType.POLKADOT_EXTENSION].icon} size={20} />
{t('wallets.addPolkadotExtension')}
{nonNullable(polkadotjsExtension) && <Label variant="blue">{t('onboarding.extension.beta')}</Label>}
</Dropdown.Item>
</PairingModal>
);

const talismanOption = (
<PairingModal extension="talisman" title={t('onboarding.extension.talismanTitle')}>
<Dropdown.Item disabled={nullable(talismanExtension)}>
<Icon name={walletIcon[WalletType.TALISMAN_EXTENSION].icon} size={20} />
{t('wallets.addTalismanExtension')}
{nonNullable(talismanExtension) && <Label variant="blue">{t('onboarding.extension.beta')}</Label>}
</Dropdown.Item>
</PairingModal>
);

const subwallet = (
<PairingModal extension="subwallet-js" title={t('onboarding.extension.subWalletTitle')}>
<Dropdown.Item disabled={nullable(subWalletExtension)}>
<Icon name={walletIcon[WalletType.SUBWALLET_EXTENSION].icon} size={20} />
{t('wallets.addSubWalletExtension')}
{nonNullable(subWalletExtension) && <Label variant="blue">{t('onboarding.extension.beta')}</Label>}
</Dropdown.Item>
</PairingModal>
);

if (nonNullable(polkadotjsExtension)) {
installed.push(polkadotOption);
} else {
notInstalled.push(polkadotOption);
}

if (nonNullable(talismanExtension)) {
installed.push(talismanOption);
} else {
notInstalled.push(talismanOption);
}

if (nonNullable(subWalletExtension)) {
installed.push(subwallet);
} else {
notInstalled.push(subwallet);
}

return (
<>
{installed}
{notInstalled.length > 0 && (
<Dropdown.Group label={t('onboarding.extensionNotInstalled')}>{notInstalled}</Dropdown.Group>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useUnit } from 'effector-react';

import { TEST_IDS } from '@/shared/constants';
import { WalletType } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { nonNullable, nullable } from '@/shared/lib/utils';
import { WalletOnboardingCard } from '@/shared/ui-entities';
import { walletIcon } from '../constants';
import { wallets } from '../model/wallets';

import { PairingModal } from './PairingModal';

export const OnboardingCards = () => {
const { t } = useI18n();
const polkadotjsExtension = useUnit(wallets.$polkadotJsExtensionWallet);
const talismanExtension = useUnit(wallets.$talismanExtensionWallet);
const subWalletExtension = useUnit(wallets.$subWalletExtensionWallet);

return (
<>
<PairingModal extension="polkadot-js" title={t('onboarding.extension.polkadotJsTitle')}>
<WalletOnboardingCard
beta={nonNullable(polkadotjsExtension)}
disabled={nullable(polkadotjsExtension)}
notInstalled={nullable(polkadotjsExtension)}
title={t('onboarding.welcome.polkadotExtensionTitle')}
description={t('onboarding.welcome.polkadotExtensionDescription')}
iconName={walletIcon[WalletType.POLKADOT_EXTENSION].onboarding}
testId={TEST_IDS.ONBOARDING.POLKADOT_EXTENSION_BUTTON}
/>
</PairingModal>
<PairingModal extension="talisman" title={t('onboarding.extension.talismanTitle')}>
<WalletOnboardingCard
beta={nonNullable(talismanExtension)}
disabled={nullable(talismanExtension)}
notInstalled={nullable(talismanExtension)}
title={t('onboarding.welcome.talismanTitle')}
description={t('onboarding.welcome.talismanDescription')}
iconName={walletIcon[WalletType.TALISMAN_EXTENSION].onboarding}
testId={TEST_IDS.ONBOARDING.TALISMAN_BUTTON}
/>
</PairingModal>
<PairingModal extension="subwallet-js" title={t('onboarding.extension.subWalletTitle')}>
<WalletOnboardingCard
beta={nonNullable(subWalletExtension)}
disabled={nullable(subWalletExtension)}
notInstalled={nullable(subWalletExtension)}
title={t('onboarding.welcome.subWalletTitle')}
description={t('onboarding.welcome.subWalletDescription')}
iconName={walletIcon[WalletType.SUBWALLET_EXTENSION].onboarding}
testId={TEST_IDS.ONBOARDING.SUBWALLET_BUTTON}
/>
</PairingModal>
</>
);
};
83 changes: 5 additions & 78 deletions src/renderer/features/extension-wallet/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { useUnit } from 'effector-react';

import { TEST_IDS } from '@/shared/constants';
import { WalletType } from '@/shared/core';
import { useI18n } from '@/shared/i18n';
import { nullable } from '@/shared/lib/utils';
import { Icon } from '@/shared/ui';
import { WalletOnboardingCard } from '@/shared/ui-entities';
import { Dropdown, Label } from '@/shared/ui-kit';
import { accountsService } from '@/domains/network';
import { walletPairingDropdownOptionsSlot } from '@/features/wallet-pairing';
import { walletGroupSlot, walletIconSlot } from '@/features/wallet-select';
import { onboardingActionsSlot } from '@/pages/Onboarding';

import { PairingModal } from './components/PairingModal';
import { DropdownOptions } from './components/DropdownOptions';
import { OnboardingCards } from './components/OnboardingCards';
import { WalletGroup, walletActionsSlot } from './components/WalletGroup';
import { walletIcon } from './constants';
import { extensionWalletFeature } from './model/feature';
Expand All @@ -38,83 +35,13 @@ extensionWalletFeature.inject(walletIconSlot, ({ wallet, size }) => {
});

extensionWalletFeature.inject(walletPairingDropdownOptionsSlot, {
order: 3,
render({ t }) {
const polkadotjsExtension = useUnit(wallets.$polkadotJsExtensionWallet);
const talismanExtension = useUnit(wallets.$talismanExtensionWallet);
const subWalletExtension = useUnit(wallets.$subWalletExtensionWallet);

return (
<>
<PairingModal extension="polkadot-js" title={t('onboarding.extension.polkadotJsTitle')}>
<Dropdown.Item disabled={nullable(polkadotjsExtension)}>
<Icon name={walletIcon[WalletType.POLKADOT_EXTENSION].icon} size={20} />
{t('wallets.addPolkadotExtension')}
<Label variant="blue">{t('onboarding.extension.beta')}</Label>
</Dropdown.Item>
</PairingModal>
<PairingModal extension="talisman" title={t('onboarding.extension.talismanTitle')}>
<Dropdown.Item disabled={nullable(talismanExtension)}>
<Icon name={walletIcon[WalletType.TALISMAN_EXTENSION].icon} size={20} />
{t('wallets.addTalismanExtension')}
<Label variant="blue">{t('onboarding.extension.beta')}</Label>
</Dropdown.Item>
</PairingModal>
<PairingModal extension="subwallet-js" title={t('onboarding.extension.subWalletTitle')}>
<Dropdown.Item disabled={nullable(subWalletExtension)}>
<Icon name={walletIcon[WalletType.SUBWALLET_EXTENSION].icon} size={20} />
{t('wallets.addSubWalletExtension')}
<Label variant={nullable(subWalletExtension) ? 'darkGray' : 'blue'}>{t('onboarding.extension.beta')}</Label>
</Dropdown.Item>
</PairingModal>
</>
);
},
order: 4,
render: () => <DropdownOptions />,
});

extensionWalletFeature.inject(onboardingActionsSlot, {
order: 5,
render() {
const { t } = useI18n();
const polkadotjsExtension = useUnit(wallets.$polkadotJsExtensionWallet);
const talismanExtension = useUnit(wallets.$talismanExtensionWallet);
const subWalletExtension = useUnit(wallets.$subWalletExtensionWallet);

return (
<>
<PairingModal extension="polkadot-js" title={t('onboarding.extension.polkadotJsTitle')}>
<WalletOnboardingCard
beta
disabled={nullable(polkadotjsExtension)}
title={t('onboarding.welcome.polkadotExtensionTitle')}
description={t('onboarding.welcome.polkadotExtensionDescription')}
iconName={walletIcon[WalletType.POLKADOT_EXTENSION].onboarding}
testId={TEST_IDS.ONBOARDING.POLKADOT_EXTENSION_BUTTON}
/>
</PairingModal>
<PairingModal extension="talisman" title={t('onboarding.extension.talismanTitle')}>
<WalletOnboardingCard
beta
disabled={nullable(talismanExtension)}
title={t('onboarding.welcome.talismanTitle')}
description={t('onboarding.welcome.talismanDescription')}
iconName={walletIcon[WalletType.TALISMAN_EXTENSION].onboarding}
testId={TEST_IDS.ONBOARDING.TALISMAN_BUTTON}
/>
</PairingModal>
<PairingModal extension="subwallet-js" title={t('onboarding.extension.subWalletTitle')}>
<WalletOnboardingCard
beta
disabled={nullable(subWalletExtension)}
title={t('onboarding.welcome.subWalletTitle')}
description={t('onboarding.welcome.subWalletDescription')}
iconName={walletIcon[WalletType.SUBWALLET_EXTENSION].onboarding}
testId={TEST_IDS.ONBOARDING.SUBWALLET_BUTTON}
/>
</PairingModal>
</>
);
},
render: () => <OnboardingCards />,
});

extensionWalletFeature.inject(walletGroupSlot, {
Expand Down
7 changes: 4 additions & 3 deletions src/renderer/shared/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -954,11 +954,11 @@
"ledgerDescription": "Ledger's the smartest way to secure, buy, exchange and grow your crypto assets",
"ledgerTitle": "Ledger",
"polkadotExtensionTitle": "Polkadot js extension",
"polkadotExtensionDescription": "Simple, secure access to Polkadot and Substrate chains via a browser extension",
"polkadotExtensionDescription": "Multi-chain browser extension",
"talismanTitle": "Talisman",
"talismanDescription": "Simple, secure access to Polkadot and Substrate chains via a browser extension",
"talismanDescription": "Multi-chain browser extension",
"subWalletTitle": "SubWallet extension",
"subWalletDescription": "Simple, secure access to Polkadot and Substrate chains via a browser extension",
"subWalletDescription": "Multi-chain browser extension",
"novaWalletDescription": "Seamless and secure transaction signing with Nova Wallet, leading mobile application for Polkadot eco",
"novaWalletTitle": "Nova Wallet",
"polkadotVaultDescription": "Use dedicated hardware wallet for Polkadot, Kusama, and other Substrate-based chains",
Expand All @@ -971,6 +971,7 @@
"watchOnlyDescription": "Track the activity of any wallet without injecting your private key to Nova Spektr",
"watchOnlyTitle": "Watch-only"
},
"extensionNotInstalled": "Not Installed",
"yourAccountsLabel": "Here are your accounts"
},
"operation": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,34 @@ type Props = {
description: string;
iconName: IconNames;
beta?: boolean;
notInstalled?: boolean;
soon?: boolean;
disabled?: boolean;
testId?: string;
onClick?: VoidFunction;
};

export const WalletOnboardingCard = forwardRef<HTMLButtonElement, Props>(
({ title, beta, soon, description, iconName, disabled, onClick, testId = 'WelcomeCard' }, ref) => {
({ title, beta, soon, notInstalled, description, iconName, disabled, onClick, testId = 'WelcomeCard' }, ref) => {
const { t } = useI18n();

let farEndLabel;

if (soon) {
farEndLabel = <Label variant="darkGray">{t('onboarding.welcome.soonBadge')}</Label>;
} else if (notInstalled) {
farEndLabel = <Label variant="darkGray">{t('onboarding.extensionNotInstalled')}</Label>;
} else {
farEndLabel = <Icon name="arrowRight" size={24} />;
}

return (
<button
ref={ref}
data-testid={testId}
disabled={disabled}
className={cnTw(
'flex gap-4 rounded-lg border border-filter-border px-4 py-2 shadow-none transition-shadow duration-200',
'flex items-center gap-4 rounded-lg border border-filter-border px-4 py-2 shadow-none transition-shadow duration-200',
{
'bg-block-background-default text-text-primary shadow-card-shadow hover:shadow-card-shadow-level2':
!disabled,
Expand All @@ -35,7 +46,7 @@ export const WalletOnboardingCard = forwardRef<HTMLButtonElement, Props>(
)}
onClick={onClick}
>
<div className="py-1">
<div className={cnTw('py-1', { 'opacity-60': disabled })}>
<Icon size={56} name={iconName} />
</div>

Expand All @@ -46,11 +57,7 @@ export const WalletOnboardingCard = forwardRef<HTMLButtonElement, Props>(
{beta ? <Label variant={disabled ? 'darkGray' : 'blue'}>{t('onboarding.extension.beta')}</Label> : null}
</div>

{soon ? (
<Label variant="darkGray">{t('onboarding.welcome.soonBadge')}</Label>
) : (
<Icon name="arrowRight" size={24} />
)}
{farEndLabel}
</div>
<FootnoteText className="text-text-tertiary">{description}</FootnoteText>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/shared/ui-kit/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ const Item = ({ onSelect, onClick, disabled, children }: ItemProps) => {
'cursor-default bg-block-background-default',
{
'cursor-pointer hover:bg-block-background-hover': !disabled,
'text-text-tertiary': disabled,
},
)}
disabled={disabled}
Expand Down

0 comments on commit 4a1ab16

Please sign in to comment.