Skip to content

Commit

Permalink
feat: add tokens widget, ref leather-io/issues#221
Browse files Browse the repository at this point in the history
  • Loading branch information
pete-watters committed Sep 18, 2024
1 parent 714fff0 commit 69e4377
Show file tree
Hide file tree
Showing 46 changed files with 933 additions and 1,057 deletions.
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@stacks/transactions": "6.15.0",
"@stacks/wallet-sdk": "6.15.0",
"@tanstack/react-query": "5.51.23",
"bignumber.js": "9.1.2",
"buffer": "6.0.3",
"dayjs": "1.11.13",
"expo": "51.0.26",
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/src/app/wallet/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RefObject, createContext } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { ActionBarMethods } from '@/components/action-bar';
import { AccountHeader } from '@/components/headers/account';
import { AccountsHeader } from '@/components/headers/account';
import { BackButtonHeader } from '@/components/headers/back-button';
import { BlurredHeader } from '@/components/headers/containers/blurred-header';
import { SimpleHeader } from '@/components/headers/containers/simple-header';
Expand Down Expand Up @@ -30,7 +30,7 @@ export default function StackLayout() {
<BlurredHeader
insets={insets}
left={<BackButtonHeader onPress={() => router.back()} />}
center={<AccountHeader />}
center={<AccountsHeader />}
right={<MenuHeader />}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/components/headers/account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { t } from '@lingui/macro';

import { Box, EmojiSmileIcon, Text, TouchableOpacity } from '@leather.io/ui/native';

export function AccountHeader({ onPress }: { onPress?(): void }) {
export function AccountsHeader({ onPress }: { onPress?(): void }) {
return (
<TouchableOpacity onPress={onPress} p="3" flexDirection="row" alignItems="center" gap="2">
<Box borderRadius="round" p="1" bg="blue.background-secondary">
Expand Down
9 changes: 5 additions & 4 deletions apps/mobile/src/components/home/home.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AccountWidget } from '@/components/widgets/account/account-widget';
import { AccountsWidget } from '@/components/widgets/accounts/accounts-widget';
import { TokensWidget } from '@/components/widgets/tokens/tokens-widget';
import { getMockTokens, mockTotalBalance } from '@/components/widgets/tokens/tokens.mocks';
import { useAccounts } from '@/store/accounts/accounts.read';
import { useWallets } from '@/store/wallets/wallets.read';
import { useLingui } from '@lingui/react';

import { Earn } from './earn';
import { HomeLayout } from './home.layout';

export function Home() {
Expand All @@ -13,8 +14,8 @@ export function Home() {

return (
<HomeLayout>
<AccountWidget accounts={accounts.list} wallets={wallets.list} />
<Earn />
<AccountsWidget accounts={accounts.list} wallets={wallets.list} />
<TokensWidget tokens={getMockTokens()} totalBalance={mockTotalBalance.totalUsdBalance} />
</HomeLayout>
);
}
15 changes: 0 additions & 15 deletions apps/mobile/src/components/widgets/account/account-balance.tsx

This file was deleted.

This file was deleted.

11 changes: 0 additions & 11 deletions apps/mobile/src/components/widgets/account/utils/format-balance.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { t } from '@lingui/macro';

import { ChevronRightIcon, SheetRef, Text, TouchableOpacity } from '@leather.io/ui/native';

interface AccountHeaderProps {
interface AccountsHeaderProps {
hasAccounts: boolean;
sheetRef: React.RefObject<SheetRef>;
}

function AccountHeaderText() {
function AccountsHeaderText() {
return <Text variant="heading05">{t`My accounts`}</Text>;
}

export function AccountHeader({ hasAccounts, sheetRef }: AccountHeaderProps) {
if (!hasAccounts) return <AccountHeaderText />;
export function AccountsHeader({ hasAccounts, sheetRef }: AccountsHeaderProps) {
if (!hasAccounts) return <AccountsHeaderText />;
return (
<>
<TouchableOpacity
Expand All @@ -24,7 +24,7 @@ export function AccountHeader({ hasAccounts, sheetRef }: AccountHeaderProps) {
gap="1"
alignItems="center"
>
<AccountHeaderText />
<AccountsHeaderText />
<ChevronRightIcon variant="small" />
</TouchableOpacity>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { Box, SheetRef, Theme } from '@leather.io/ui/native';

import { Widget } from '../widget';

interface AccountWidgetProps {
interface AccountsWidgetProps {
balance?: React.ReactNode;
children: React.ReactNode;
header?: React.ReactNode;
sheetRef?: React.RefObject<SheetRef>;
}

export function AccountWidgetLayout({ balance, children, header }: AccountWidgetProps) {
export function AccountsWidgetLayout({ balance, children, header }: AccountsWidgetProps) {
const theme = useTheme<Theme>();
return (
<Widget>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { useRouter } from 'expo-router';

import { SheetRef } from '@leather.io/ui/native';

import { AccountBalance } from './account-balance';
import { AccountHeader } from './account-header';
import { AccountWidgetLayout } from './account-widget.layout';
import { FiatBalance } from '../components/balance/fiat-balance';
import { AccountsHeader } from './accounts-header';
import { AccountsWidgetLayout } from './accounts-widget.layout';
import { AccountCard } from './cards/account-card';
import { AccountOverview } from './cards/account-overview-card';
import { CreateWalletCard } from './cards/create-wallet-card';
Expand All @@ -25,12 +25,12 @@ interface MockedAccount extends AccountStore {
balance: number;
}

interface AccountWidgetProps {
interface AccountsWidgetProps {
accounts: AccountStore[];
wallets: WalletStore[];
}

export function AccountWidget({ accounts, wallets }: AccountWidgetProps) {
export function AccountsWidget({ accounts, wallets }: AccountsWidgetProps) {
const sheetRef = useRef<SheetRef>(null);
const addWalletSheetRef = useRef<SheetRef>(null);
const [selectedAccount, setSelectedAccount] = useState<MockedAccount | null>(null);
Expand Down Expand Up @@ -68,25 +68,25 @@ export function AccountWidget({ accounts, wallets }: AccountWidgetProps) {
/>
<AccountOverview
Icon={getAvatarIcon(selectedAccount.icon)}
heading={<AccountBalance balance={selectedAccount.balance} variant="heading02" />}
heading={<FiatBalance balance={selectedAccount.balance} variant="heading02" />}
caption={selectedAccount.name}
/>
</>
);
}
return (
<>
<AccountWidgetLayout
<AccountsWidgetLayout
sheetRef={sheetRef}
header={<AccountHeader hasAccounts={hasAccounts} sheetRef={sheetRef} />}
balance={hasWallets && <AccountBalance balance={mockedWalletBalance} variant="heading03" />}
header={<AccountsHeader hasAccounts={hasAccounts} sheetRef={sheetRef} />}
balance={hasWallets && <FiatBalance balance={mockedWalletBalance} variant="heading03" />}
>
{mockedAccounts.map(account => (
<AccountCard
type={account.type as WalletStore['type']}
Icon={getAvatarIcon(account.icon)}
key={account.id}
label={<AccountBalance balance={account.balance} />}
label={<FiatBalance balance={account.balance} />}
caption={account.name || ''}
onPress={() => setSelectedAccount(account as MockedAccount)}
/>
Expand All @@ -97,7 +97,7 @@ export function AccountWidget({ accounts, wallets }: AccountWidgetProps) {
) : (
<CreateWalletCard onPress={() => addWalletSheetRef.current?.present()} />
)}
</AccountWidgetLayout>
</AccountsWidgetLayout>

<AddWalletSheet addWalletSheetRef={addWalletSheetRef} />
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useMemo } from 'react';

import { t } from '@lingui/macro';

import { BulletSeparator, Text, TextProps } from '@leather.io/ui/native';
import { formatBalance } from '@leather.io/utils';

interface FiatBalanceProps {
balance: number | string;
lockedBalance?: string;
variant?: TextProps['variant'];
color?: TextProps['color'];
}

export function FiatBalance({
balance,
lockedBalance,
variant = 'label01',
color = 'ink.text-primary',
}: FiatBalanceProps) {
// FIXME: currencyLabel should be dynamic based on the user's currency
const currencyLabel = '$';
const formattedBalance = useMemo(
() =>
typeof balance === 'number'
? `${currencyLabel}${formatBalance(balance.toString()).value}`
: balance,
[balance]
);

if (!lockedBalance) {
return (
<Text variant={variant} color={color}>
{formattedBalance}
</Text>
);
}

return (
<BulletSeparator color={color}>
<Text variant={variant} color={color}>
{formattedBalance}
</Text>
<Text variant={variant} color={color}>
{lockedBalance} {t`locked`}
</Text>
</BulletSeparator>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import BigNumber from 'bignumber.js';

import { Money } from '@leather.io/models';

export function formatLockedBalance(balance: Money) {
if (!balance || !balance.amount || !balance.decimals) return undefined;

const amount = new BigNumber(balance.amount).dividedBy(new BigNumber(10).pow(balance.decimals));
return amount.toFixed().replace(/\.?0+$/, '');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import { t } from '@lingui/macro';

import { BulletSeparator, Text } from '@leather.io/ui/native';

interface TokenBalanceProps {
balance: string;
lockedBalance?: string;
}

export function TokenBalance({ balance, lockedBalance }: TokenBalanceProps) {
if (!lockedBalance) {
return <Text variant="label01">{balance}</Text>;
}

return (
<BulletSeparator>
<Text variant="label01">{balance}</Text>
<Text variant="label01">
{lockedBalance} {t`locked`}
</Text>
</BulletSeparator>
);
}
35 changes: 35 additions & 0 deletions apps/mobile/src/components/widgets/tokens/tokens-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';

import { t } from '@lingui/macro';

import { Box, ChevronRightIcon, Chip, SheetRef, Text } from '@leather.io/ui/native';

import { FiatBalance } from '../components/balance/fiat-balance';

type Token = Record<string, unknown>;

interface TokensHeaderProps {
tokens: Token[];
sheetRef: React.RefObject<SheetRef>;
totalBalance: string;
}

function TokensHeaderText() {
return <Text variant="heading05">{t`My tokens`}</Text>;
}

export function TokensHeader({ tokens, totalBalance }: TokensHeaderProps) {
const tokenCount = tokens.length;
const hasTokens = tokenCount > 0;
if (!hasTokens) return <TokensHeaderText />;
return (
<Box flexDirection="row" gap="1" alignItems="center" marginHorizontal="5">
<TokensHeaderText />
<Chip label={tokenCount} />
<ChevronRightIcon variant="small" />
<Box flex={1} justifyContent="flex-end" alignItems="flex-end">
<FiatBalance balance={totalBalance} color="ink.text-subdued" />
</Box>
</Box>
);
}
29 changes: 29 additions & 0 deletions apps/mobile/src/components/widgets/tokens/tokens-widget.layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { ScrollView } from 'react-native-gesture-handler';

import { useTheme } from '@shopify/restyle';

import { Box, SheetRef, Theme } from '@leather.io/ui/native';

import { Widget } from '../widget';

interface TokensWidgetLayoutProps {
balance?: React.ReactNode;
children: React.ReactNode;
header?: React.ReactNode;
sheetRef?: React.RefObject<SheetRef>;
}

export function TokensWidgetLayout({ children, header }: TokensWidgetLayoutProps) {
const theme = useTheme<Theme>();
return (
<Widget>
<Box>{header}</Box>
<ScrollView
contentContainerStyle={{ gap: theme.spacing['3'], marginHorizontal: theme.spacing['5'] }}
>
{children}
</ScrollView>
</Widget>
);
}
Loading

0 comments on commit 69e4377

Please sign in to comment.