Skip to content

Change home screen styling #1499

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

Merged
merged 1 commit into from
May 31, 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
2 changes: 1 addition & 1 deletion apps/next/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const Page: NextPageWithLayout<InferGetServerSidePropsType<typeof getServ
<title>Send</title>
</Head>
{session ? (
<HomeLayout TopNav={<TopNav header="Home" showLogo={true} backFunction="router" />}>
<HomeLayout TopNav={<TopNav header="Home" showLogo={true} backFunction="home" />}>
<HomeScreen />
</HomeLayout>
) : (
Expand Down
14 changes: 13 additions & 1 deletion packages/app/components/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function TopNav({
)
case media.gtLg && !showOnGtLg:
return null
case Boolean(queryParams.token):
case queryParams.token !== undefined:
return (
<XStack ai="center" f={1}>
<Button onPress={handleBack}>
Expand All @@ -174,6 +174,18 @@ export function TopNav({
/>
</ButtonOg.Icon>
</Button>
<Paragraph size={'$8'} col={'$color10'}>
{(() => {
switch (queryParams.token) {
case 'investments':
return 'Invest'
case 'stables':
return 'Cash'
default:
return 'Balance'
}
})()}
</Paragraph>
</XStack>
)
case !isSubRoute:
Expand Down
2 changes: 1 addition & 1 deletion packages/app/data/coins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export const allCoinsDict = allCoins.reduce((acc, coin) => {

export type allCoinsDict = typeof allCoinsDict

export type CoinWithBalance = allCoins[number] & {
export type CoinWithBalance = coin & {
balance: bigint | undefined
}

Expand Down
2 changes: 2 additions & 0 deletions packages/app/features/auth/loginWithPhone/screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const LoginWithPhoneScreen = () => {
$theme-dark={{
variant: canSubmit ? undefined : 'outlined',
}}
elevation={canSubmit ? '$0.75' : undefined}
>
<ButtonText
ff={'$mono'}
Expand Down Expand Up @@ -154,6 +155,7 @@ export const LoginWithPhoneScreen = () => {
>
{({ countryCode, phone }) => (
<FadeCard
elevation={'$0.75'}
w={'100%'}
mt={'$5'}
borderColor={validationError ? '$error' : 'transparent'}
Expand Down
34 changes: 34 additions & 0 deletions packages/app/features/home/FriendsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Card, type CardProps, Paragraph, XStack } from '@my/ui'

import { ChevronRight } from '@tamagui/lucide-icons'
import { type LinkProps, useLink } from 'solito/link'

export const FriendsCard = ({ href, ...props }: Omit<CardProps & LinkProps, 'children'>) => {
const linkProps = useLink({ href })

return (
<Card
elevate
hoverStyle={{ scale: 0.925 }}
pressStyle={{ scale: 0.875 }}
animation="bouncy"
size={'$5'}
br="$7"
{...linkProps}
{...props}
>
<Card.Header padded fd="row" ai="center" jc="space-between">
<Paragraph fontSize={'$7'} fontWeight="400">
Friends
</Paragraph>
<XStack flex={1} />
<ChevronRight
size={'$1.5'}
color={'$lightGrayTextField'}
$theme-light={{ color: '$darkGrayTextField' }}
/>
</Card.Header>
<Card.Footer padded />
</Card>
)
}
40 changes: 10 additions & 30 deletions packages/app/features/home/InvestmentBalanceList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { Link, LinkableButton, type LinkProps, Paragraph, XStack, YStack } from '@my/ui'

import { Link, type LinkProps, Paragraph, XStack, YStack } from '@my/ui'
import { IconCoin } from 'app/components/icons/IconCoin'
import type { CoinWithBalance } from 'app/data/coins'

import { useCoins } from 'app/provider/coins'

import formatAmount from 'app/utils/formatAmount'
import { Fragment } from 'react'
import { useHoverStyles } from 'app/utils/useHoverStyles'
Expand All @@ -13,17 +10,17 @@ import { useTokenPrices } from 'app/utils/useTokenPrices'
import { type MarketData, useMultipleTokensMarketData } from 'app/utils/coin-gecko'
import { useThemeSetting } from '@tamagui/next-theme'
import { useIsPriceHidden } from 'app/features/home/utils/useIsPriceHidden'
import { IconPlus } from 'app/components/icons'

export const InvestmentsBalanceList = () => {
const { investmentCoins, isLoading } = useCoins()
export const InvestmentsBalanceList = ({
coins,
}: {
coins: CoinWithBalance[]
}) => {
const hoverStyles = useHoverStyles()
const { data: tokensMarketData, isLoading: isLoadingTokensMarketData } =
useMultipleTokensMarketData(investmentCoins?.map((c) => c.coingeckoTokenId) || [])

if (isLoading) return null
useMultipleTokensMarketData(coins?.map((c) => c.coingeckoTokenId) || [])

return investmentCoins.map((coin) => (
return coins.map((coin) => (
<Fragment key={`token-balance-list-${coin.label}`}>
<TokenBalanceItem
testID={`token-balance-list-${coin.label}`}
Expand Down Expand Up @@ -75,7 +72,7 @@ const TokenBalanceItem = ({
<YStack f={1} jc={'space-between'}>
<XStack jc={'space-between'} ai={'center'}>
<Paragraph fontSize={'$6'} fontWeight={'500'} color={'$color12'}>
{coin.shortLabel || coin.label}
{coin.label}
</Paragraph>
<TokenBalance coin={coin} />
</XStack>
Expand Down Expand Up @@ -128,27 +125,10 @@ const TokenBalance = ({

if (balance === undefined) return <></>
return (
<Paragraph
fontSize={'$6'}
fontWeight={'500'}
col="$color12"
$gtSm={{ fontSize: '$8', fontWeight: '600' }}
>
<Paragraph fontSize={'$8'} fontWeight={'500'} col="$color12">
{isPriceHidden
? '//////'
: formatAmount((Number(balance) / 10 ** decimals).toString(), 10, formatDecimals ?? 5)}
</Paragraph>
)
}

export const AddInvestmentLink = () => {
const hoverStyles = useHoverStyles()

return (
<LinkableButton circular href="/trade" p="$2" size="$5" hoverStyle={hoverStyles}>
<LinkableButton.Icon>
<IconPlus size="$5" color="$color10" />
</LinkableButton.Icon>
</LinkableButton>
)
}
172 changes: 115 additions & 57 deletions packages/app/features/home/InvestmentsBalanceCard.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import { Button, Card, Paragraph, Spinner, Theme, XStack, YStack } from '@my/ui'
import {
Card,
type CardProps,
H1,
Paragraph,
Spinner,
Theme,
ThemeableStack,
useMedia,
XStack,
type XStackProps,
} from '@my/ui'
import formatAmount from 'app/utils/formatAmount'

import { ChevronLeft, ChevronRight } from '@tamagui/lucide-icons'

import { useIsPriceHidden } from './utils/useIsPriceHidden'

import { useSendAccountBalances } from 'app/utils/useSendAccountBalances'
import { investmentCoins } from 'app/data/coins'
import { type CoinWithBalance, investmentCoins } from 'app/data/coins'

import { useRootScreenParams } from 'app/routers/params'
import { useMultipleTokensMarketData } from 'app/utils/coin-gecko'
import { useMemo } from 'react'
import { IconError } from 'app/components/icons'
import { IconCoin, IconError } from 'app/components/icons'
import { useCoins } from 'app/provider/coins'
import { investmentCoins as investmentCoinsList } from 'app/data/coins'

export const InvestmentsBalanceCard = () => {
export const InvestmentsBalanceCard = (props: CardProps) => {
const media = useMedia()
const [queryParams, setParams] = useRootScreenParams()
const isInvestmentCoin = investmentCoins.some(
(coin) => coin.token.toLowerCase() === queryParams.token
(coin) => coin.token.toLowerCase() === queryParams.token?.toLowerCase()
)
const isInvestmentsScreen = queryParams.token === 'investments'

Expand All @@ -39,62 +52,107 @@ export const InvestmentsBalanceCard = () => {
const formattedBalance = formatAmount(dollarTotal, 9, 0)

return (
<Card py="$5" px="$4" w={'100%'} jc="space-between" onPress={toggleSubScreen}>
<YStack jc={'center'} gap={'$5'} w={'100%'}>
<YStack w={'100%'} gap={'$2.5'} jc="space-between">
<XStack ai={'center'} jc={'space-between'} gap="$2.5" width={'100%'}>
<Paragraph fontSize={'$7'} fontWeight="400">
Invest
</Paragraph>
<Button
chromeless
backgroundColor="transparent"
hoverStyle={{ backgroundColor: 'transparent' }}
pressStyle={{
backgroundColor: 'transparent',
borderColor: 'transparent',
}}
focusStyle={{ backgroundColor: 'transparent' }}
p={0}
height={'auto'}
>
<Button.Icon>
{isInvestmentCoin || isInvestmentsScreen ? (
<ChevronLeft
size={'$1.5'}
color={'$lightGrayTextField'}
$theme-light={{ color: '$darkGrayTextField' }}
$lg={{ display: 'none' }}
/>
) : (
<ChevronRight
size={'$1.5'}
color={'$primary'}
$theme-light={{ color: '$color12' }}
/>
)}
</Button.Icon>
</Button>
</XStack>
</YStack>
<Paragraph fontSize={'$10'} fontWeight={'600'} color={'$color12'}>
{(() => {
switch (true) {
case isPriceHidden:
return '///////'
case isLoading || !dollarBalances:
return <Spinner size={'large'} />
default:
return `$${formattedBalance}`
}
})()}
<Card
elevate
hoverStyle={{ scale: 0.925 }}
pressStyle={{ scale: 0.875 }}
animation="bouncy"
onPress={toggleSubScreen}
size={'$5'}
br="$7"
{...props}
>
<Card.Header padded pb={0} fd="row" ai="center" jc="space-between">
<Paragraph fontSize={'$5'} fontWeight="400">
Invest
</Paragraph>
<InvestmentsAggregate />
</YStack>
{isInvestmentCoin || isInvestmentsScreen ? (
<ChevronLeft
size={'$1'}
color={'$primary'}
$theme-light={{ color: '$color12' }}
$lg={{ display: 'none' }}
/>
) : (
<ChevronRight
size={'$1'}
color={'$lightGrayTextField'}
$theme-light={{ color: '$darkGrayTextField' }}
/>
)}
</Card.Header>
<Card.Footer padded pt={0} fd="column">
{isInvestmentsScreen && !media.gtLg ? (
<>
<Paragraph color={'$color12'} fontWeight={500} size={'$10'}>
{(() => {
switch (true) {
case isPriceHidden:
return '///////'
case isLoading || !dollarBalances:
return <Spinner size={'large'} color={'$color12'} />
default:
return `$${formattedBalance}`
}
})()}
</Paragraph>
<InvestmentsAggregate />
</>
) : (
<>
<InvestmentsPreview />
<InvestmentsAggregate />
</>
)}
</Card.Footer>
</Card>
)
}

function InvestmentsPreview() {
const { investmentCoins, isLoading } = useCoins()

if (isLoading) return <Spinner size="small" />

const existingSymbols = new Set(investmentCoins.map((coin) => coin.symbol))
const coins = [
...investmentCoins,
...investmentCoinsList
.filter((coin) => !existingSymbols.has(coin.symbol))
.map((coin) => ({ ...coin, balance: 0n })),
]

const sortedByBalance = coins.toSorted((a, b) =>
(b?.balance ?? 0n) > (a?.balance ?? 0n) ? 1 : -1
)
return (
<XStack ai="center" jc="space-between">
<OverlappingCoinIcons coins={sortedByBalance} />
<Card circular ai="center" jc="center" bc="$color0" w={'$3.5'} h="$3.5" mih={0} miw={0}>
<Paragraph fontSize={'$4'} fontWeight="500">
{`+${investmentCoinsList.length - 3}`}
</Paragraph>
</Card>
</XStack>
)
}

function OverlappingCoinIcons({
coins,
length = 3,
...props
}: { coins: CoinWithBalance[]; length?: number } & XStackProps) {
return (
<XStack ai="center" {...props}>
{coins.slice(0, length).map(({ symbol }) => (
<ThemeableStack key={symbol} circular mr={'$-3.5'} bc="transparent" ai="center" jc="center">
<IconCoin size={'$3'} symbol={symbol} />
</ThemeableStack>
))}
</XStack>
)
}

function InvestmentsAggregate() {
const tokenIds = useCoins()
.investmentCoins.filter((c) => c?.balance && c.balance > 0n)
Expand All @@ -117,7 +175,7 @@ function InvestmentsAggregate() {
return (
<XStack gap="$2" ai="center">
<Paragraph color="$color10" $gtXs={{ fontSize: 14 }} fontSize={12}>
Add popular crypto assets to your portfolio
Diversify Your Crypto Portfolio
</Paragraph>
</XStack>
)
Expand Down
Loading
Loading