Skip to content

Commit

Permalink
feat: rework onboarding ledger flow, closes leather-io#4281
Browse files Browse the repository at this point in the history
  • Loading branch information
alter-eggo committed Oct 18, 2023
1 parent ceaf228 commit 47aa2e2
Show file tree
Hide file tree
Showing 74 changed files with 1,188 additions and 286 deletions.
Binary file removed public/assets/images/ledger/connect-ledger-error.png
Binary file not shown.
19 changes: 11 additions & 8 deletions src/app/common/hooks/account/use-account-names.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import { useMemo } from 'react';

import { isUndefined } from '@shared/utils';

import { parseIfValidPunycode } from '@app/common/utils';
import { getAutogeneratedAccountDisplayName } from '@app/common/utils/get-account-display-name';
import {
useCurrentAccountNames,
useGetAccountNamesByAddressQuery,
} from '@app/query/stacks/bns/bns.hooks';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models';

export function useCurrentAccountDisplayName() {
const account = useCurrentStacksAccount();
const { data: names = [] } = useCurrentAccountNames();
const index = useCurrentAccountIndex();

return useMemo(() => {
if (!account || typeof account?.index !== 'number') return 'Account';
if (isUndefined(index) && (!account || typeof account?.index !== 'number')) return 'Account';
if (names[0]) return parseIfValidPunycode(names[0]);
return getAutogeneratedAccountDisplayName(account.index);
}, [account, names]);
return getAutogeneratedAccountDisplayName(index);
}, [account, names, index]);
}

export function useAccountDisplayName(account: StacksAccount): string {
const { data: names = [] } = useGetAccountNamesByAddressQuery(account.address);
export function useAccountDisplayName({ address, index }: { index: number; address: string }) {
const { data: names = [] } = useGetAccountNamesByAddressQuery(address);
return useMemo(() => {
if (names[0]) return parseIfValidPunycode(names[0]);
return getAutogeneratedAccountDisplayName(account.index);
}, [account, names]);
return getAutogeneratedAccountDisplayName(index);
}, [names, index]);
}
2 changes: 1 addition & 1 deletion src/app/common/hooks/account/use-switch-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function useSwitchAccount(callback?: () => void) {
switchAccount(index);
if (callback) setTimeout(() => callback(), TIMEOUT);
if (!accounts) return;
void trackSwitchAccount(accounts[index].address, index);
void trackSwitchAccount(accounts[index]?.address, index);
},
[setHasSwitched, switchAccount, callback, accounts, trackSwitchAccount]
);
Expand Down
9 changes: 5 additions & 4 deletions src/app/common/hooks/balance/use-total-balance.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useMemo } from 'react';

import { createMoney } from '@shared/models/money.model';

import { baseCurrencyAmountInQuote } from '@app/common/money/calculate-money';
import { i18nFormatCurrency } from '@app/common/money/format-money';
import { useCryptoCurrencyMarketData } from '@app/query/common/market-data/market-data.hooks';
Expand All @@ -19,15 +21,14 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)

// get stx balance
const { data: balances, isLoading } = useAnchoredStacksAccountBalances(stxAddress);
const stxBalance = balances ? balances.stx.balance : createMoney(0, 'STX');

// get btc balance
const btcBalance = useBtcAssetBalance(btcAddress);

return useMemo(() => {
if (!balances) return null;

// calculate total balance
const stxUsdAmount = baseCurrencyAmountInQuote(balances.stx.balance, stxMarketData);
const stxUsdAmount = baseCurrencyAmountInQuote(stxBalance, stxMarketData);
const btcUsdAmount = baseCurrencyAmountInQuote(
btcBalance.btcAvailableAssetBalance.balance,
btcMarketData
Expand All @@ -39,5 +40,5 @@ export function useTotalBalance({ btcAddress, stxAddress }: UseTotalBalanceArgs)
totalUsdBalance: i18nFormatCurrency(totalBalance),
isLoading,
};
}, [btcBalance, balances, btcMarketData, stxMarketData, isLoading]);
}, [btcBalance, btcMarketData, stxMarketData, isLoading, stxBalance]);
}
2 changes: 1 addition & 1 deletion src/app/common/use-wallet-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function isSoftwareWallet(walletType: WalletType) {

type WalletTypeMap<T> = Record<WalletType, T>;

function whenWallet(walletType: WalletType) {
function whenWallet(walletType: WalletType = 'ledger') {
return <T extends WalletTypeMap<unknown>>(walletTypeMap: T) => {
if (isLedgerWallet(walletType)) return walletTypeMap.ledger as T['ledger'];
if (isSoftwareWallet(walletType)) return walletTypeMap.software as T['software'];
Expand Down
29 changes: 17 additions & 12 deletions src/app/components/account/account-list-item-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ import { Flex, HStack, Stack, StackProps, styled } from 'leather-styles/jsx';
import { token } from 'leather-styles/tokens';

import { useViewportMinWidth } from '@app/common/hooks/use-media-query';
import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query';

import { CaptionDotSeparator } from '../caption-dot-separator';
import { CheckmarkIcon } from '../icons/checkmark-icon';
import { Flag } from '../layout/flag';
import { StacksAccountLoader } from '../stacks-account-loader';
import { BitcoinNativeSegwitAccountLoader } from './bitcoin-account-loader';

interface AccountListItemLayoutProps extends StackProps {
isLoading: boolean;
isActive: boolean;
index: number;
stxAddress: string;
btcAddress: string;
accountName: React.ReactNode;
avatar: React.JSX.Element;
balanceLabel: React.ReactNode;
Expand All @@ -30,8 +29,6 @@ export function AccountListItemLayout(props: AccountListItemLayoutProps) {
index,
isLoading,
isActive,
stxAddress,
btcAddress,
accountName,
avatar,
balanceLabel,
Expand All @@ -44,7 +41,6 @@ export function AccountListItemLayout(props: AccountListItemLayoutProps) {
} = props;

const isBreakpointSm = useViewportMinWidth('sm');
const isBitcoinEnabled = useConfigBitcoinEnabled();

return (
<Flex
Expand Down Expand Up @@ -77,12 +73,21 @@ export function AccountListItemLayout(props: AccountListItemLayoutProps) {
</HStack>
<HStack alignItems="center" gap="space.02" whiteSpace="nowrap">
<CaptionDotSeparator>
<styled.span textStyle="caption.02">
{truncateMiddle(stxAddress, isBreakpointSm ? 4 : 3)}
</styled.span>
{isBitcoinEnabled && (
<styled.span textStyle="caption.02">{truncateMiddle(btcAddress, 5)}</styled.span>
)}
<StacksAccountLoader index={index}>
{account => (
<styled.span textStyle="caption.02">
{truncateMiddle(account.address, isBreakpointSm ? 4 : 3)}
</styled.span>
)}
</StacksAccountLoader>

<BitcoinNativeSegwitAccountLoader index={index}>
{signer => (
<styled.span textStyle="caption.02">
{truncateMiddle(signer.address, 5)}
</styled.span>
)}
</BitcoinNativeSegwitAccountLoader>
</CaptionDotSeparator>
</HStack>
</Stack>
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/account/account-name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import { BoxProps } from '@stacks/ui';
import { styled } from 'leather-styles/jsx';

import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names';
import { BtcAccount } from '@app/store/accounts/blockchain/bitcoin/bitcoin-accounts.hooks';
import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models';

interface AccountNameLayoutProps {
children: React.ReactNode;
}
const AccountNameLayout = memo(({ children }: AccountNameLayoutProps) => (
export const AccountNameLayout = memo(({ children }: AccountNameLayoutProps) => (
<styled.p textStyle="label.01">{children}</styled.p>
));

interface AccountNameProps extends BoxProps {
account: StacksAccount;
account: StacksAccount | BtcAccount;
}
export const AccountName = memo(({ account }: AccountNameProps) => {
const name = useAccountDisplayName(account);
Expand Down
31 changes: 31 additions & 0 deletions src/app/components/account/bitcoin-account-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { P2Ret } from '@scure/btc-signer';

import { useConfigBitcoinEnabled } from '@app/query/common/remote-config/remote-config.query';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import { Signer } from '@app/store/accounts/blockchain/bitcoin/bitcoin-signer';
import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks';

interface BitcoinAccountLoaderBaseProps {
children(account: Signer<P2Ret>): React.ReactNode;
}
interface BtcAccountLoaderCurrentProps extends BitcoinAccountLoaderBaseProps {
current: true;
}
interface BtcAccountLoaderIndexProps extends BitcoinAccountLoaderBaseProps {
index: number;
}

type BtcAccountLoaderProps = BtcAccountLoaderCurrentProps | BtcAccountLoaderIndexProps;

export function BitcoinNativeSegwitAccountLoader({ children, ...props }: BtcAccountLoaderProps) {
const isBitcoinEnabled = useConfigBitcoinEnabled();

const currentAccountIndex = useCurrentAccountIndex();

const properIndex = 'current' in props ? currentAccountIndex : props.index;

const signer = useNativeSegwitSigner(properIndex);

if (!signer || !isBitcoinEnabled) return null;
return children(signer(0));
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface CryptoCurrencyAssetItemLayoutProps extends StackProps {
currency?: CryptoCurrencies;
additionalBalanceInfo?: React.JSX.Element;
additionalUsdBalanceInfo?: React.JSX.Element;
connectBtn?: React.JSX.Element;
}
export const CryptoCurrencyAssetItemLayout = forwardRefWithAs(
(props: CryptoCurrencyAssetItemLayoutProps, ref) => {
Expand All @@ -44,6 +45,7 @@ export const CryptoCurrencyAssetItemLayout = forwardRefWithAs(
isHovered = false,
additionalBalanceInfo,
additionalUsdBalanceInfo,
connectBtn,
...rest
} = props;
const [component, bind] = usePressable(isPressable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface CryptoCurrencyAssetItemProps extends StackProps {
canCopy?: boolean;
additionalBalanceInfo?: React.JSX.Element;
additionalUsdBalanceInfo?: React.JSX.Element;
connectBtn?: React.JSX.Element;
}
export const CryptoCurrencyAssetItem = forwardRefWithAs(
(props: CryptoCurrencyAssetItemProps, ref) => {
Expand All @@ -33,6 +34,7 @@ export const CryptoCurrencyAssetItem = forwardRefWithAs(
usdBalance,
additionalBalanceInfo,
additionalUsdBalanceInfo,
connectBtn,
...rest
} = props;
const { balance, asset } = assetBalance;
Expand Down Expand Up @@ -80,6 +82,7 @@ export const CryptoCurrencyAssetItem = forwardRefWithAs(
onMouseOut={onBlur}
additionalBalanceInfo={additionalBalanceInfo}
additionalUsdBalanceInfo={additionalUsdBalanceInfo}
connectBtn={connectBtn}
{...rest}
/>
);
Expand Down
15 changes: 9 additions & 6 deletions src/app/components/external-link.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React from 'react';

import { BoxProps, Text, color } from '@stacks/ui';
import { styled } from 'leather-styles/jsx';

interface ExternalLinkProps extends BoxProps {
const StyledA = styled('a');

interface ExternalLinkProps extends React.ComponentProps<typeof StyledA> {
href: string;
children: React.ReactNode;
}
export function ExternalLink(props: ExternalLinkProps) {

export function ExternalLink({ href, children, ...rest }: ExternalLinkProps) {
return (
<Text as="a" color={color('accent')} target="_blank" {...props}>
{props.children}
</Text>
<styled.a target="_blank" cursor="pointer" href={href} {...rest}>
{children}
</styled.a>
);
}
10 changes: 10 additions & 0 deletions src/app/components/icons/btc-ledger-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function BtcLedgerIcon() {
return (
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20.2075 9.5429C20.5457 7.31003 18.8248 6.10969 16.4717 5.30895L17.235 2.28424L15.3714 1.8254L14.6283 4.77039C14.1383 4.64978 13.6351 4.53599 13.1351 4.42325L13.8835 1.45884L12.021 1L11.2571 4.02365C10.8516 3.9324 10.4535 3.84222 10.0671 3.7473L10.0692 3.73786L7.49906 3.10386L7.00329 5.07034C7.00329 5.07034 8.38603 5.3834 8.35683 5.40281C9.11164 5.58896 9.24804 6.08242 9.22522 6.47361L8.35576 9.91941C8.40778 9.93252 8.47521 9.9514 8.54952 9.98077C8.48741 9.96556 8.42106 9.94877 8.35259 9.93252L7.13386 14.7596C7.0415 14.9861 6.80742 15.3259 6.27981 15.1969C6.29838 15.2236 4.9252 14.8629 4.9252 14.8629L4 16.9704L6.42524 17.5677C6.87642 17.6794 7.31857 17.7963 7.75383 17.9064L6.98259 20.9658L8.84411 21.4246L9.60793 18.3978C10.1164 18.5341 10.6101 18.66 11.0931 18.7785L10.332 21.7912L12.1956 22.25L12.9669 19.1965C16.1448 19.7906 18.5344 19.5509 19.5403 16.7114C20.3509 14.425 19.5 13.1062 17.8279 12.2461C19.0456 11.9687 19.9628 11.1774 20.2075 9.5429ZM15.9494 15.4418C15.3735 17.7282 11.4769 16.4922 10.2136 16.1822L11.237 12.1292C12.5003 12.4407 16.5514 13.0574 15.9494 15.4418ZM16.5259 9.50986C16.0004 11.5896 12.7572 10.5329 11.7051 10.2739L12.633 6.59791C13.685 6.85696 17.0731 7.34044 16.5259 9.50986Z"
fill="currentColor"
/>
</svg>
);
}
17 changes: 17 additions & 0 deletions src/app/components/icons/ledger-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function LedgerIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M0 10V14.0612L6 14L6 13H1V10H0ZM14 10L14 13H9V14L15 14.061L15 10H14ZM6 4V10H10L10 9H7V4H6ZM0 0V4H1L1 1H6L6 0H0ZM9 0L9 1H14L14 4H15L15 0H9Z"
fill="currentColor"
/>
</svg>
);
}
14 changes: 14 additions & 0 deletions src/app/components/icons/stx-ledger-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function StxLedgerIcon() {
return (
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.2091 14.89L20.2452 21H17.2301L12.492 13.8212L7.75399 21H4.75483L8.79093 14.906H3V12.5928H22V14.89H16.2091Z"
fill="currentColor"
/>
<path
d="M22 8.03023V10.3434V10.3594H3V8.03023H8.67926L4.69102 2H7.70613L12.492 9.27456L17.2939 2H20.309L16.3207 8.03023H22Z"
fill="currentColor"
/>
</svg>
);
}
30 changes: 29 additions & 1 deletion src/app/components/stacks-account-loader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useCurrentStacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { useCurrentAccountIndex } from '@app/store/accounts/account';
import {
useCurrentStacksAccount,
useStacksAccounts,
} from '@app/store/accounts/blockchain/stacks/stacks-account.hooks';
import { StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models';

interface CurrentStacksAccountLoaderProps {
Expand All @@ -10,3 +14,27 @@ export function CurrentStacksAccountLoader({ children }: CurrentStacksAccountLoa
if (!currentAccount) return null;
return children(currentAccount);
}

interface StacksAccountBaseLoaderProps {
children(data: StacksAccount): React.ReactNode;
}

interface StacksAccountCurrentLoaderProps extends StacksAccountBaseLoaderProps {
current: true;
}

interface StacksAccountIndexLoaderProps extends StacksAccountBaseLoaderProps {
index: number;
}

type StacksAccountLoaderProps = StacksAccountCurrentLoaderProps | StacksAccountIndexLoaderProps;

export function StacksAccountLoader({ children, ...props }: StacksAccountLoaderProps) {
const stacksAccounts = useStacksAccounts();
const currentAccountIndex = useCurrentAccountIndex();
const properIndex = 'current' in props ? currentAccountIndex : props.index;

const account = stacksAccounts[properIndex];
if (!account) return null;
return children(account);
}
7 changes: 7 additions & 0 deletions src/app/components/typography.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Text as BaseText, BoxProps, color } from '@stacks/ui';
import { forwardRefWithAs } from '@stacks/ui-core';
import { BoxProps as LeatherBoxProps, styled } from 'leather-styles/jsx';

const interMetrics = {
capHeight: 2048,
Expand Down Expand Up @@ -70,6 +71,12 @@ export const Caption = forwardRefWithAs<{ variant?: 'c1' | 'c2' | 'c3' } & BoxPr
)
);

export const LCaption = forwardRefWithAs<LeatherBoxProps, 'span'>(({ children, ...props }, ref) => (
<styled.span ref={ref} textStyle="label.03" color="accent.text-subdued" {...props}>
{children}
</styled.span>
));

export function CaptionSeparatorDot(props: BoxProps) {
return (
<Text color={color('text-caption')} fontSize="10px" {...props}>
Expand Down
Loading

0 comments on commit 47aa2e2

Please sign in to comment.