-
Notifications
You must be signed in to change notification settings - Fork 0
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: Portfolio holdings details #278
Changes from all commits
5c4a902
6801a00
c6c2179
d79c068
daa3f2c
27ee7b4
7768292
2b37ff2
bba67cd
20239d5
5f36a43
e476f63
0eacc15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { FC, useMemo } from "react"; | ||
import { ApexDonutChart, themeColors } from "@alphaday/ui-kit"; | ||
import { makeRepeated } from "src/api/utils/itemUtils"; | ||
import { getAssetPrefix } from "src/api/utils/portfolioUtils"; | ||
import { ITEM_COLORS } from "src/components/item-colors"; | ||
import { TPortfolioDataForAddress } from "src/components/portfolio/types"; | ||
import CONFIG from "src/config"; | ||
import { portfolioData } from "./mockData"; | ||
|
||
const { DONUT_TOKENS_COUNT } = CONFIG.WIDGETS.PORTFOLIO; | ||
|
||
const PortfolioChart: FC = () => { | ||
const assets = useMemo<TPortfolioDataForAddress["assets"]>( | ||
() => | ||
portfolioData !== undefined | ||
? [...portfolioData.assets].sort( | ||
(a, b) => b.token.balanceUSD - a.token.balanceUSD | ||
) | ||
: [], | ||
[] | ||
); | ||
|
||
const labels: string[] = []; | ||
const series = assets.map((item) => { | ||
labels.push( | ||
`${item.token.symbol}${getAssetPrefix(item).toUpperCase()}` | ||
); | ||
return Number(item.token.balanceUSD); | ||
}); | ||
const othersBalance = | ||
series.length > DONUT_TOKENS_COUNT | ||
? series.splice(DONUT_TOKENS_COUNT).reduce((n, p) => n + p) | ||
: 0; | ||
const donutData = { | ||
options: { | ||
chart: { | ||
id: "portfolio-donut", | ||
sparkline: { | ||
enabled: false, | ||
}, | ||
background: "transparent", | ||
redrawOnWindowResize: true, | ||
height: "500px", | ||
}, | ||
labels: [ | ||
...labels.slice(0, DONUT_TOKENS_COUNT), | ||
...(othersBalance ? ["Others"] : []), | ||
], | ||
dataLabels: { | ||
enabled: false, | ||
}, | ||
tooltip: { | ||
y: { | ||
formatter(value: number) { | ||
return `$${new Intl.NumberFormat("en-US", { | ||
maximumFractionDigits: 2, | ||
}).format(value)}`; | ||
}, | ||
}, | ||
}, | ||
plotOptions: { | ||
pie: { | ||
donut: { | ||
customScale: 1.5, | ||
size: 170, | ||
background: "transparent", | ||
}, | ||
}, | ||
}, | ||
stroke: { | ||
colors: undefined, | ||
}, | ||
legend: { | ||
show: true, | ||
fontSize: "11px", | ||
position: "left", | ||
offsetX: 0, | ||
offsetY: 0, | ||
height: 500, | ||
labels: { | ||
colors: [themeColors.primaryVariant100], | ||
useSeriesColors: false, | ||
}, | ||
markers: { | ||
radius: 3, | ||
}, | ||
onItemHover() {}, | ||
formatter( | ||
label: string, | ||
opts: { | ||
w: { | ||
globals: { | ||
series: number[]; | ||
}; | ||
}; | ||
seriesIndex: number; | ||
} | ||
) { | ||
const percent = | ||
(100 * opts.w.globals.series[opts.seriesIndex]) / | ||
opts.w.globals.series.reduce((a, b) => a + b); | ||
return ` | ||
<span style="display: inline-flex; justify-content: space-between; width: 70%"> | ||
<div>${label}</div> | ||
<div> ${percent.toFixed(0)}%</div> | ||
</span>`; | ||
}, | ||
}, | ||
colors: makeRepeated(ITEM_COLORS, assets.length), | ||
}, | ||
series: [ | ||
...series.slice(0, DONUT_TOKENS_COUNT), | ||
...(othersBalance ? [othersBalance] : []), | ||
], | ||
}; | ||
|
||
return ( | ||
<div className="flex justify-between donut-chart ml-2 [&_.apexcharts-inner]:!translate-x-[200px] [&_.apexcharts-inner]:!translate-y-10 [&_.apexcharts-svg]:!w-80 "> | ||
{donutData.series && ( | ||
<ApexDonutChart | ||
options={donutData?.options} | ||
series={donutData?.series} | ||
width="260px" | ||
height="400px" | ||
/> | ||
)} | ||
</div> | ||
); | ||
}; | ||
export default PortfolioChart; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { FC } from "react"; | ||
import { OutlineButton, twMerge } from "@alphaday/ui-kit"; | ||
import { ReactComponent as CopySVG } from "src/assets/icons/copy.svg"; | ||
import { ReactComponent as HandSVG } from "src/assets/icons/hand.svg"; | ||
import { ReactComponent as WalletSVG } from "src/assets/icons/wallet.svg"; | ||
|
||
const WalletConnectionOptions: FC<{ | ||
isAuthenticated: boolean; | ||
onClick: (path: string) => void; | ||
className?: string; | ||
}> = ({ isAuthenticated, onClick, className }) => { | ||
return ( | ||
<div className={twMerge("flex flex-col items-center mt-4", className)}> | ||
<OutlineButton | ||
title="Add Wallet" | ||
subtext="Add your wallet manually to get started" | ||
icon={<WalletSVG className="w-[24px] mr-1" />} | ||
onClick={() => onClick("/portfolio/add-wallet")} | ||
isAuthenticated={isAuthenticated} | ||
/> | ||
<OutlineButton | ||
title="Connect Wallet" | ||
subtext="Connect your wallet to get started" | ||
icon={<CopySVG className="w-[22px] mr-1" />} | ||
onClick={() => onClick("/portfolio/connect-wallet")} | ||
isAuthenticated={isAuthenticated} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: we've been told today that this option was not going to be included. But it's fine to keep it for now |
||
/> | ||
<OutlineButton | ||
title="Add Holdings Manually" | ||
subtext="Add your holdings manually" | ||
icon={<HandSVG className="w-[20px] mr-1" />} | ||
onClick={() => onClick("/portfolio/add-holding")} | ||
isAuthenticated={isAuthenticated} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
export default WalletConnectionOptions; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
import { TPortfolioDataForAddress } from "src/components/portfolio/types"; | ||
|
||
export const portfolioData: TPortfolioDataForAddress = { | ||
assets: [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ideally we need to define a portoflio component API that is decoupled from the data API provider, but this is work for a separate PR. |
||
{ | ||
key: "2216096905", | ||
address: "0x2874c4e9cb5f183ef3d8adfa6d6bbd584dc67842", | ||
network: "avalanche", | ||
updatedAt: "2024-03-03T09:08:45.405Z", | ||
token: { | ||
id: "47", | ||
address: "0x0000000000000000000000000000000000000000", | ||
name: "AVAX", | ||
symbol: "AVAX", | ||
decimals: 18, | ||
coingeckoId: "avalanche-2", | ||
hide: false, | ||
canExchange: true, | ||
updatedAt: "2024-03-04T07:10:32.643Z", | ||
createdAt: "2022-05-18T12:54:47.542Z", | ||
price: 42.63, | ||
networkId: 3, | ||
status: "approved", | ||
totalSupply: "435942308.442695", | ||
dailyVolume: 621105842.2333122, | ||
verified: true, | ||
holdersEnabled: true, | ||
marketCap: 16035840022.326565, | ||
priceUpdatedAt: "2024-03-04T07:10:32.643Z", | ||
externallyVerified: false, | ||
label: "avax", | ||
balance: 0.0021, | ||
balanceUSD: 0.089523, | ||
balanceRaw: "2100000000000000", | ||
tokenImage: | ||
"https://storage.googleapis.com/zapper-fi-assets/tokens/avalanche/0x0000000000000000000000000000000000000000.png", | ||
}, | ||
}, | ||
{ | ||
key: "921602654", | ||
address: "0x2874c4e9cb5f183ef3d8adfa6d6bbd584dc67842", | ||
network: "optimism", | ||
updatedAt: "2024-03-03T09:08:45.244Z", | ||
token: { | ||
id: "78", | ||
address: "0x0000000000000000000000000000000000000000", | ||
name: "Ether", | ||
symbol: "ETH", | ||
decimals: 18, | ||
coingeckoId: "ethereum", | ||
hide: false, | ||
canExchange: true, | ||
updatedAt: "2024-03-04T07:10:32.643Z", | ||
createdAt: "2022-05-18T12:54:47.542Z", | ||
price: 3463.77, | ||
networkId: 11, | ||
status: "approved", | ||
totalSupply: "36811.197214188697856699", | ||
dailyVolume: 17641941138.93259, | ||
verified: true, | ||
holdersEnabled: true, | ||
marketCap: 416110233226.25, | ||
priceUpdatedAt: "2024-03-04T07:10:32.643Z", | ||
externallyVerified: false, | ||
label: "eth", | ||
balance: 0.008459516477201893, | ||
balanceUSD: 29.3018193882376, | ||
balanceRaw: "8459516477201892", | ||
tokenImage: | ||
"https://storage.googleapis.com/zapper-fi-assets/tokens/optimism/0x0000000000000000000000000000000000000000.png", | ||
}, | ||
}, | ||
{ | ||
key: "3612627573", | ||
address: "0x2874c4e9cb5f183ef3d8adfa6d6bbd584dc67842", | ||
network: "ethereum", | ||
updatedAt: "2024-03-03T09:08:45.213Z", | ||
token: { | ||
id: "3067", | ||
address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", | ||
name: "Wrapped Ether", | ||
symbol: "WETH", | ||
decimals: 18, | ||
coingeckoId: "weth", | ||
hide: false, | ||
canExchange: true, | ||
updatedAt: "2024-03-04T07:10:32.643Z", | ||
createdAt: "2022-05-18T12:54:47.695Z", | ||
price: 3463.77, | ||
networkId: 1, | ||
status: "approved", | ||
totalSupply: "3081838.685889420235408401", | ||
dailyVolume: 553589763.6055393, | ||
verified: false, | ||
holdersEnabled: true, | ||
marketCap: 0, | ||
priceUpdatedAt: "2024-03-04T07:10:32.643Z", | ||
externallyVerified: true, | ||
label: "WETH", | ||
balance: 30.56325, | ||
balanceUSD: 105864.0684525, | ||
balanceRaw: "30563250000000000000", | ||
tokenImage: | ||
"https://storage.googleapis.com/zapper-fi-assets/tokens/ethereum/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.png", | ||
}, | ||
}, | ||
{ | ||
key: "80289008", | ||
address: "0x2874c4e9cb5f183ef3d8adfa6d6bbd584dc67842", | ||
network: "ethereum", | ||
updatedAt: "2024-03-03T09:08:45.213Z", | ||
token: { | ||
id: "1496", | ||
address: "0x0000000000000000000000000000000000000000", | ||
name: "Ether", | ||
symbol: "ETH", | ||
decimals: 18, | ||
coingeckoId: "ethereum", | ||
hide: false, | ||
canExchange: true, | ||
updatedAt: "2024-03-04T07:10:32.643Z", | ||
createdAt: "2022-05-18T12:54:47.695Z", | ||
price: 3463.77, | ||
networkId: 1, | ||
status: "approved", | ||
totalSupply: "122373866.2178", | ||
dailyVolume: 17641941138.93259, | ||
verified: true, | ||
holdersEnabled: true, | ||
marketCap: 416110233226.25, | ||
priceUpdatedAt: "2024-03-04T07:10:32.643Z", | ||
externallyVerified: false, | ||
label: "eth", | ||
balance: 37.68335669707697, | ||
balanceUSD: 130526.48042663428, | ||
balanceRaw: "37683356697076965333", | ||
tokenImage: | ||
"https://storage.googleapis.com/zapper-fi-assets/tokens/ethereum/0x0000000000000000000000000000000000000000.png", | ||
}, | ||
}, | ||
{ | ||
key: "51396930", | ||
address: "0x2874c4e9cb5f183ef3d8adfa6d6bbd584dc67842", | ||
network: "ethereum", | ||
updatedAt: "2024-03-02T23:39:41.598Z", | ||
token: { | ||
id: "2626", | ||
address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", | ||
name: "SHIBA INU", | ||
symbol: "SHIB", | ||
decimals: 18, | ||
coingeckoId: "shiba-inu", | ||
hide: false, | ||
canExchange: true, | ||
updatedAt: "2024-03-04T07:10:32.643Z", | ||
createdAt: "2022-05-18T12:54:47.695Z", | ||
price: 0.00002693, | ||
networkId: 1, | ||
status: "approved", | ||
totalSupply: "999982367742850.863791106162366397", | ||
dailyVolume: 4359038452.289908, | ||
verified: false, | ||
holdersEnabled: true, | ||
marketCap: 15957709021.025589, | ||
priceUpdatedAt: "2024-03-04T07:10:32.643Z", | ||
externallyVerified: true, | ||
label: "Shiba Inu", | ||
balance: 434877.1472059143, | ||
balanceUSD: 11.711241574255274, | ||
balanceRaw: "434877147205914300000000", | ||
tokenImage: | ||
"https://storage.googleapis.com/zapper-fi-assets/tokens/ethereum/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce.png", | ||
}, | ||
}, | ||
], | ||
totalValue: 236431.6514630968, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be defined as a prop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah but that's for later