Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: more refined ui flow for extension wallets #3039

Merged
merged 1 commit into from
Jan 24, 2025
Merged
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
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
Loading