Skip to content

Commit

Permalink
106 portfolio tab (#388)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: Thorianite <100335276+Thorian1te@users.noreply.github.com>
  • Loading branch information
cinnamoroll6130 and Thorian1te authored Oct 8, 2024
1 parent 39c4922 commit dfed819
Show file tree
Hide file tree
Showing 33 changed files with 832 additions and 187 deletions.
Binary file modified src/renderer/assets/png/asset-cacao.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified src/renderer/assets/png/asset-usk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/renderer/assets/svg/icon-cog.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/renderer/assets/svg/icon-portfolio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 31 additions & 7 deletions src/renderer/components/sidebar/SidebarComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ import { useMatch, useNavigate } from 'react-router-dom'
import { Dex } from '../../../shared/api/types'
import { ExternalUrl } from '../../../shared/const'
import { ReactComponent as DiscordIcon } from '../../assets/svg/discord.svg'
import { ReactComponent as SettingsIcon } from '../../assets/svg/icon-cog.svg'
import { ReactComponent as PortfolioIcon } from '../../assets/svg/icon-portfolio.svg'
import { ReactComponent as SwapIcon } from '../../assets/svg/icon-swap.svg'
import { ReactComponent as WalletIcon } from '../../assets/svg/icon-wallet.svg'
import { ReactComponent as ThorChainIcon } from '../../assets/svg/logo-thorchain.svg'
import * as appRoutes from '../../routes/app'
import * as playgroundRoutes from '../../routes/playground'
import * as poolsRoutes from '../../routes/pools'
import * as portfolioRoutes from '../../routes/portfolio'
import * as walletRoutes from '../../routes/wallet'
import { mayaIconT } from '../icons'
import * as Styled from './SidebarComponent.styles'
Expand All @@ -43,8 +47,10 @@ const FooterIcon: React.FC<IconProps> = (props: IconProps): JSX.Element => {
}

enum TabKey {
POOLS = 'POOLS',
WALLET = 'WALLET',
PORTFOLIO = 'PORTFOLIO',
POOLS = 'POOLS',
SETTINGS = 'SETTINGS',
UNKNOWN = 'UNKNOWN'
}

Expand All @@ -71,31 +77,49 @@ export const SidebarComponent: React.FC<Props> = (props): JSX.Element => {
const navigate = useNavigate()

const matchPoolsRoute = useMatch({ path: poolsRoutes.base.path(), end: false })
const matchPortfolioRoute = useMatch({ path: portfolioRoutes.base.path(), end: false })
const matchWalletRoute = useMatch({ path: walletRoutes.base.path(), end: false })
const matchSettingsRoute = useMatch({ path: appRoutes.settings.path(), end: false })

const activeKey: TabKey = useMemo(() => {
if (matchPoolsRoute) {
return TabKey.POOLS
} else if (matchPortfolioRoute) {
return TabKey.PORTFOLIO
} else if (matchWalletRoute) {
return TabKey.WALLET
} else if (matchSettingsRoute) {
return TabKey.SETTINGS
} else {
return TabKey.UNKNOWN
}
}, [matchPoolsRoute, matchWalletRoute])
}, [matchPoolsRoute, matchPortfolioRoute, matchWalletRoute, matchSettingsRoute])

const items: Tab[] = useMemo(
() => [
{
key: TabKey.WALLET,
label: intl.formatMessage({ id: 'common.wallet' }),
path: walletRoutes.base.path(),
icon: WalletIcon
},
{
key: TabKey.PORTFOLIO,
label: intl.formatMessage({ id: 'wallet.nav.portfolio' }),
path: portfolioRoutes.base.path(),
icon: PortfolioIcon
},
{
key: TabKey.POOLS,
label: intl.formatMessage({ id: 'common.pools' }),
path: poolsRoutes.base.path(),
icon: SwapIcon
},
{
key: TabKey.WALLET,
label: intl.formatMessage({ id: 'common.wallet' }),
path: walletRoutes.base.path(),
icon: WalletIcon
key: TabKey.SETTINGS,
label: intl.formatMessage({ id: 'common.settings' }),
path: appRoutes.settings.path(),
icon: SettingsIcon
}
],
[intl]
Expand Down Expand Up @@ -123,7 +147,7 @@ export const SidebarComponent: React.FC<Props> = (props): JSX.Element => {
`}
onClick={() => navigate(path)}>
<div className="flex flex-row items-center py-3 pl-8">
<Icon className="pr-5px" />
<Icon className="w-8 pr-5px" />
<span>{label}</span>
</div>
</div>
Expand Down
33 changes: 33 additions & 0 deletions src/renderer/components/uielements/radioGroup/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import clsx from 'clsx'

export const RadioGroup = ({
options,
activeIndex = 0,
onChange
}: {
options: { label: React.ReactNode; value: string | number }[]
activeIndex?: number
onChange: (index: number) => void
}) => {
return (
<div className="h-fit">
<div className="flex rounded-lg border border-solid border-gray0 dark:border-gray0d">
{options.map((option, index) => (
<>
{index !== 0 && <div className="h-10 w-[1px] bg-gray0 dark:bg-gray0d" />}
<div
key={option.value}
className={clsx(
'cursor-pointer p-2 hover:bg-gray0 hover:dark:bg-gray0d',
'first:rounded-l-md last:rounded-r-md',
activeIndex === index ? 'bg-gray0 dark:bg-gray0d' : 'bg-transparent'
)}
onClick={() => onChange(index)}>
{option.label}
</div>
</>
))}
</div>
</div>
)
}
1 change: 1 addition & 0 deletions src/renderer/components/uielements/radioGroup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './RadioGroup'
2 changes: 2 additions & 0 deletions src/renderer/components/wallet/assets/AssetsNav.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const Menu = styled(MenuUI)`
align-items: center;
border-bottom: 1px solid ${palette('gray', 1)};
border-radius: 8px 8px 0 0;
&.ant-menu-horizontal .ant-menu-item,
&.ant-menu-horizontal > .ant-menu-item::after {
transition: none;
Expand Down
94 changes: 94 additions & 0 deletions src/renderer/hooks/useAllSaverProviders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { useEffect, useState } from 'react'

import { AnyAsset } from '@xchainjs/xchain-util'
import * as A from 'fp-ts/lib/Array'
import * as FP from 'fp-ts/lib/function'
import * as O from 'fp-ts/lib/Option'
import * as Rx from 'rxjs'
import * as RxOp from 'rxjs/operators'

import { WalletType } from '../../shared/wallet/types'
import { useChainContext } from '../contexts/ChainContext'
import { useThorchainContext } from '../contexts/ThorchainContext'
import { useWalletContext } from '../contexts/WalletContext'
import { isMayaChain, isThorChain } from '../helpers/chainHelper'
import { userChains$ } from '../services/storage/userChains'
import { SaverProviderRD } from '../services/thorchain/types'
import { ledgerAddressToWalletAddress } from '../services/wallet/util'

export const useAllSaverProviders = (poolAsset: AnyAsset[]) => {
const { getSaverProvider$ } = useThorchainContext()
const { addressByChain$ } = useChainContext()
const { getLedgerAddress$ } = useWalletContext()

const [allSaverProviders, setAllSaverProviders] = useState<Record<string, SaverProviderRD>>({})

useEffect(() => {
if (poolAsset) {
const userChainsSubscription = userChains$.subscribe((enabledChains) => {
const keystoreAddresses$ = FP.pipe(
enabledChains,
A.filter((chain) => !isThorChain(chain)),
A.map(addressByChain$)
)

const ledgerAddresses$ = (): Rx.Observable<O.Option<{ chain: string; address: string; type: WalletType }>>[] =>
FP.pipe(
enabledChains,
A.filter((chain) => !isThorChain(chain) || isMayaChain(chain)),
A.map((chain) => getLedgerAddress$(chain)),
A.map(RxOp.map(FP.flow(O.map(ledgerAddressToWalletAddress))))
)

const combinedAddresses$ = Rx.combineLatest([...keystoreAddresses$, ...ledgerAddresses$()]).pipe(
RxOp.map((addressOptionsArray) => FP.pipe(addressOptionsArray, A.filterMap(FP.identity))),
RxOp.map((walletAddresses) =>
walletAddresses.map((walletAddress) => ({
chain: walletAddress.chain,
address: walletAddress.address,
type: walletAddress.type
}))
)
)

const subscriptions = poolAsset.map((asset) => {
return combinedAddresses$
.pipe(
RxOp.switchMap((walletAddresses) => {
const addressesForAssetChain = walletAddresses.filter((wa) => wa.chain === asset.chain)
if (addressesForAssetChain.length > 0) {
return Rx.combineLatest(
addressesForAssetChain.map((walletAddress) =>
getSaverProvider$(asset, walletAddress.address, walletAddress.type)
)
)
}
return Rx.of(null)
})
)
.subscribe((saverProviders) => {
if (saverProviders !== null) {
saverProviders.forEach((saverProvider) => {
if (
saverProvider !== null &&
saverProvider._tag === 'RemoteSuccess' &&
saverProvider.value.depositValue.amount().gt(0)
) {
const key = `${asset.chain}.${asset.symbol}`
setAllSaverProviders((prev) => ({ ...prev, [key]: saverProvider }))
}
})
}
})
})

return () => {
subscriptions.forEach((sub) => sub.unsubscribe())
userChainsSubscription.unsubscribe()
}
})
}
}, [addressByChain$, getLedgerAddress$, getSaverProvider$, poolAsset])

return { allSaverProviders, setAllSaverProviders }
}
98 changes: 98 additions & 0 deletions src/renderer/hooks/usePoolShares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useCallback, useEffect, useMemo, useState } from 'react'

import * as RD from '@devexperts/remote-data-ts'
import { THORChain } from '@xchainjs/xchain-thorchain'
import * as A from 'fp-ts/Array'
import * as FP from 'fp-ts/function'
import * as O from 'fp-ts/Option'
import * as Rx from 'rxjs'
import * as RxOp from 'rxjs/operators'

import { getChainsForDex } from '../../shared/utils/chain'
import { useChainContext } from '../contexts/ChainContext'
import { useMidgardContext } from '../contexts/MidgardContext'
import { useMidgardMayaContext } from '../contexts/MidgardMayaContext'
import { useWalletContext } from '../contexts/WalletContext'
import { addressFromWalletAddress } from '../helpers/walletHelper'
import { WalletAddress$ } from '../services/clients'
import { PoolShares } from '../services/midgard/types'
import { userChains$ } from '../services/storage/userChains'
import { ledgerAddressToWalletAddress } from '../services/wallet/util'
import { useDex } from './useDex'

export const usePoolShares = () => {
const { dex } = useDex()

const {
service: {
pools: { reloadAllPools },
shares: { allSharesByAddresses$, reloadAllSharesByAddresses }
}
} = useMidgardContext()

const {
service: {
pools: { reloadAllPools: reloadAllMayaPools },
shares: {
allSharesByAddresses$: allSharesByAddressesMaya$,
reloadAllSharesByAddresses: reloadAllSharesByAddressesMaya
}
}
} = useMidgardMayaContext()

const { addressByChain$ } = useChainContext()
const { getLedgerAddress$ } = useWalletContext()

const INCLUDED_CHAINS = useMemo(() => getChainsForDex(dex.chain), [dex.chain])

const [allSharesRD, setAllSharesRD] = useState<RD.RemoteData<Error, PoolShares>>(RD.initial)

useEffect(() => {
const subscription = userChains$
.pipe(
RxOp.switchMap((enabledChains) => {
const addresses$: WalletAddress$[] = FP.pipe(
enabledChains,
A.filter((chain) => INCLUDED_CHAINS.includes(chain)),
A.map(addressByChain$)
)
const ledgerAddresses$ = (): WalletAddress$[] =>
FP.pipe(
enabledChains,
A.filter((chain) => INCLUDED_CHAINS.includes(chain)),
A.map((chain) => getLedgerAddress$(chain)),
A.map(RxOp.map(FP.flow(O.map(ledgerAddressToWalletAddress))))
)
const combinedAddresses$ = Rx.combineLatest([...addresses$, ...ledgerAddresses$()])

return FP.pipe(
combinedAddresses$,
RxOp.switchMap(
FP.flow(A.filterMap(FP.identity), A.map(addressFromWalletAddress), (addresses) =>
dex.chain === THORChain ? allSharesByAddresses$(addresses) : allSharesByAddressesMaya$(addresses)
)
),
RxOp.startWith(RD.pending)
)
})
)
.subscribe(setAllSharesRD)

return () => subscription.unsubscribe()
}, [dex, allSharesByAddresses$, allSharesByAddressesMaya$, addressByChain$, INCLUDED_CHAINS, getLedgerAddress$])

const reload = useCallback(() => {
if (dex.chain === THORChain) {
reloadAllPools()
reloadAllSharesByAddresses()
} else {
reloadAllMayaPools()
reloadAllSharesByAddressesMaya()
}
}, [dex, reloadAllMayaPools, reloadAllPools, reloadAllSharesByAddresses, reloadAllSharesByAddressesMaya])

return {
allSharesRD,
reload
}
}
4 changes: 4 additions & 0 deletions src/renderer/i18n/de/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const common: CommonMessages = {
'common.test': 'Test',
'common.change': 'Ändern',
'common.wallet': 'Wallet',
'common.wallets': 'Brieftaschen',
'common.history': 'Verlauf',
'common.settings': 'Einstellungen',
'common.assets': 'Assets',
Expand Down Expand Up @@ -120,10 +121,13 @@ const common: CommonMessages = {
'common.collateral': 'Sicherheit',
'common.debt': 'Schuld',
'common.earn': 'Verdienen',
'common.earnings': 'Einnahmen',
'common.liquidity': 'Liquidität',
'common.withdraw': 'Auszahlen',
'common.approve': 'Erlauben',
'common.accept': 'Akzeptieren',
'common.allocationByType': 'Zuweisung nach Typ',
'common.allocationByChain': 'Zuweisung nach Chain',
'common.approve.checking': 'Überprüfe Erlaubnis für {asset}',
'common.approve.error': 'Fehler beim Überprüfen der Erlaubnis für {asset}: {error}',
'common.step': 'Schritt {current}/{total}',
Expand Down
1 change: 1 addition & 0 deletions src/renderer/i18n/de/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const wallet: WalletMessages = {
'wallet.name.error.empty': 'Bitte gib einen Namen für Deine Wallet ein',
'wallet.name.error.duplicated': 'Dieser Name existiert bereits, bitte einen anderen verwenden.',
'wallet.name.error.rename': 'Fehler beim Umbenennen der Wallet',
'wallet.nav.portfolio': 'Portfolio',
'wallet.nav.deposits': 'Einzahlungen',
'wallet.nav.bonds': 'Bonds',
'wallet.nav.poolshares': 'Anteile',
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/i18n/en/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const common: CommonMessages = {
'common.test': 'Test',
'common.change': 'Change',
'common.wallet': 'Wallet',
'common.wallets': 'Wallets',
'common.history': 'History',
'common.settings': 'Settings',
'common.asset': 'Asset',
Expand Down Expand Up @@ -119,10 +120,13 @@ const common: CommonMessages = {
'common.borrow': 'Borrow',
'common.repay': 'Repay',
'common.earn': 'Earn',
'common.earnings': 'Earnings',
'common.withdraw': 'Withdraw',
'common.liquidity': 'Liquidity',
'common.approve': 'Approve',
'common.accept': 'Accept',
'common.allocationByType': 'Allocation By Type',
'common.allocationByChain': 'Allocation BY Chain',
'common.approve.checking': 'Checking allowance for {asset}',
'common.approve.error': 'Error while checking allowance for {asset}: {error}',
'common.step': 'Step {current}/{total}',
Expand Down
1 change: 1 addition & 0 deletions src/renderer/i18n/en/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const wallet: WalletMessages = {
'wallet.name.error.empty': 'Please enter a name for your wallet',
'wallet.name.error.duplicated': 'Name already exists, please use another name.',
'wallet.name.error.rename': 'Error while renaming the wallet',
'wallet.nav.portfolio': 'Portfolio',
'wallet.nav.deposits': 'Deposits',
'wallet.nav.bonds': 'Bonds',
'wallet.nav.poolshares': 'LP Shares',
Expand Down
Loading

0 comments on commit dfed819

Please sign in to comment.