Skip to content
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

Mattupham/fe 944 portfolio add chart expand minimize #3703

Merged
merged 36 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e99083b
preserveAspectRatio
mattupham Aug 8, 2024
f8472aa
Get animations working
mattupham Aug 8, 2024
435ba01
Clean up with ref
mattupham Aug 8, 2024
b4a8b71
Handle show scales
mattupham Aug 8, 2024
1b1d0da
add button to minimize
mattupham Aug 8, 2024
5a19a1e
Add transition
mattupham Aug 8, 2024
a1f7bc0
Add hover state
mattupham Aug 8, 2024
05be302
Add card
mattupham Aug 8, 2024
f89adaf
Clean up
mattupham Aug 8, 2024
18d6c2b
Merge branch 'stage' into mattupham/fe-944-portfolio-add-chart-expand…
mattupham Aug 8, 2024
d57825b
Add optional skeleton
mattupham Aug 8, 2024
249bc64
Update chart style
mattupham Aug 8, 2024
abc30ee
Merge with stage
mattupham Aug 8, 2024
040eb53
Update transitions
mattupham Aug 8, 2024
f045ff2
Clean up
mattupham Aug 8, 2024
70020ba
udpates
mattupham Aug 8, 2024
4dcc033
migrate portfolio performance
mattupham Aug 8, 2024
8d6405c
Clean up globals
mattupham Aug 8, 2024
3d927b5
Add transitions
mattupham Aug 8, 2024
9d486ee
Update expand area
mattupham Aug 8, 2024
007783a
add transitions
mattupham Aug 8, 2024
e2f04eb
Clean up
mattupham Aug 8, 2024
492f523
Make chart not selectable
mattupham Aug 8, 2024
0329da6
Fix fit
mattupham Aug 9, 2024
c499fb4
Clean up
mattupham Aug 9, 2024
e262910
Clean up chart minimization
mattupham Aug 9, 2024
fee8c56
Clean up scales
mattupham Aug 9, 2024
1256615
Clean up config
mattupham Aug 9, 2024
f2a4629
Cleanup
mattupham Aug 9, 2024
e42da54
Clean up ref
mattupham Aug 9, 2024
c807961
Clean up
mattupham Aug 9, 2024
c0c8d3b
Clean up i18n
mattupham Aug 9, 2024
73404cc
Fix coins
mattupham Aug 9, 2024
420a479
Merge branch 'stage' into mattupham/fe-944-portfolio-add-chart-expand…
mattupham Aug 9, 2024
23035fa
Fix import
mattupham Aug 9, 2024
042dda9
Remove extra state
mattupham Aug 9, 2024
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
1 change: 1 addition & 0 deletions packages/web/components/assets/highlights-categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ function highlightPrice24hChangeAsset(asset: PriceChange24hAsset) {
<PriceChange
priceChange={asset.priceChange24h}
overrideTextClasses="body2"
className="h-fit"
/>
) : null,
};
Expand Down
2 changes: 1 addition & 1 deletion packages/web/components/assets/price.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const PriceChange: FunctionComponent<
value !== undefined ? `(${priceChangeDisplay})` : priceChangeDisplay;

return (
<div className={classNames("flex h-fit items-center gap-1", className)}>
<div className={classNames("flex items-center gap-1", className)}>
{isBullish && (
<Icon
id="triangle-down"
Expand Down
60 changes: 43 additions & 17 deletions packages/web/components/chart/historical-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ const getSeriesOpt = (config: Style): DeepPartial<AreaSeriesOptions> => {
break;
case "neutral":
default:
lineColor = theme.colors.wosmongton[300];
topColor = theme.colors.osmoverse[700];
bottomColor = theme.colors.osmoverse[850];
lineColor = theme.colors.wosmongton[400];
topColor = "rgba(70, 42, 223, 0.2)";
bottomColor = "rgba(165, 19, 153, 0.01)";
crosshairMarkerBorderColor = theme.colors.osmoverse[900];
break;
}
Expand Down Expand Up @@ -70,14 +70,36 @@ interface HistoricalChartProps {
onPointerHover?: (price: number, time: Time) => void;
onPointerOut?: () => void;
style?: Style;
hideScales?: boolean;
}

export const HistoricalChart = memo((props: HistoricalChartProps) => {
const { data = [], onPointerHover, onPointerOut, style = "neutral" } = props;
const {
data = [],
onPointerHover,
onPointerOut,
style = "neutral",
hideScales = false,
} = props;

const options = hideScales
? {
rightPriceScale: {
visible: false,
},
leftPriceScale: {
visible: false,
},
timeScale: {
visible: false,
},
}
: {};

return (
<Chart
Controller={AreaChartController}
options={options}
series={[
{
type: "Area",
Expand Down Expand Up @@ -155,7 +177,7 @@ export const HistoricalChartHeader: FunctionComponent<{
);
};

export const HistoricalChartSkeleton = () => {
export const HistoricalChartSkeleton = ({ showScales = true }) => {
return (
<div className="flex h-full w-full flex-1 flex-row gap-3">
<div className="flex flex-1 flex-col items-end justify-end">
Expand Down Expand Up @@ -202,7 +224,21 @@ export const HistoricalChartSkeleton = () => {
</defs>
</svg>

<div className="flex w-full flex-row justify-between py-3">
{showScales && (
<div className="flex w-full flex-row justify-between py-3">
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
</div>
)}
</div>

{showScales && (
<div className="flex flex-col justify-between pb-7 pt-2">
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
Expand All @@ -211,17 +247,7 @@ export const HistoricalChartSkeleton = () => {
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
</div>
</div>

<div className="flex flex-col justify-between pb-7 pt-2">
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
<Skeleton className="h-[14px] w-11 rounded-xl bg-osmoverse-825" />
</div>
)}
</div>
);
};
242 changes: 158 additions & 84 deletions packages/web/components/complex/portfolio/assets-overview.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Transition } from "@headlessui/react";
import { PricePretty } from "@keplr-wallet/unit";
import { Dec, RatePretty } from "@keplr-wallet/unit";
import { DEFAULT_VS_CURRENCY } from "@osmosis-labs/server";
Expand All @@ -9,12 +10,15 @@ import classNames from "classnames";
import dayjs from "dayjs";
import { AreaData, Time } from "lightweight-charts";
import { observer } from "mobx-react-lite";
import { FunctionComponent, useState } from "react";
import { Fragment, FunctionComponent, useState } from "react";

import { Icon } from "~/components/assets";
import { CreditCardIcon } from "~/components/assets/credit-card-icon";
import { GetStartedWithOsmosis } from "~/components/complex/portfolio/get-started-with-osmosis";
import { PortfolioHistoricalChart } from "~/components/complex/portfolio/historical-chart";
import {
PortfolioHistoricalChart,
PortfolioHistoricalChartMinimized,
} from "~/components/complex/portfolio/historical-chart";
import { PortfolioPerformance } from "~/components/complex/portfolio/performance";
import { DataPoint } from "~/components/complex/portfolio/types";
import { SkeletonLoader } from "~/components/loaders/skeleton-loader";
Expand All @@ -26,6 +30,8 @@ import { useBridge } from "~/hooks/bridge";
import { useStore } from "~/stores";
import { api } from "~/utils/trpc";

const CHART_CONTAINER_HEIGHT = 468;

const calculatePortfolioPerformance = (
data: ChartPortfolioOverTimeResponse[] | undefined,
dataPoint: DataPoint
Expand Down Expand Up @@ -117,96 +123,164 @@ export const AssetsOverview: FunctionComponent<
? formatDate(dayjs.unix(dataPoint.time as number).format("YYYY-MM-DD"))
: undefined;

if (isWalletLoading) return null;

const totalDisplayValue =
new PricePretty(DEFAULT_VS_CURRENCY, new Dec(dataPoint.value || 0)) ||
totalValue?.toString();

const [isHovering, setIsHovering] = useState(false);
const [isChartMinimized, setIsChartMinimized] = useState(true);

if (isWalletLoading) return null;

return (
<div className="flex w-full flex-col gap-4">
<div
className={classNames(
"relative flex w-full flex-col transition-all duration-[250ms]",
{ "delay-[250ms]": isChartMinimized }
)}
style={{
marginBottom: isChartMinimized ? "" : `${CHART_CONTAINER_HEIGHT}px`,
}}
>
{wallet && wallet.isWalletConnected && wallet.address ? (
mattupham marked this conversation as resolved.
Show resolved Hide resolved
<>
<div className="flex flex-col gap-2">
<span className="body1 md:caption text-osmoverse-300">
{t("assets.totalBalance")}
</span>

<SkeletonLoader
className={classNames(isTotalValueFetched ? null : "h-14 w-48")}
isLoaded={isTotalValueFetched}
>
{isMobile ? (
<h5>{totalDisplayValue?.toString()}</h5>
) : (
<h3>{totalDisplayValue?.toString()}</h3>
)}
</SkeletonLoader>
<SkeletonLoader
className={classNames(
isPortfolioOverTimeDataIsFetched ? null : "h-6 w-16"
)}
isLoaded={isPortfolioOverTimeDataIsFetched}
>
<PortfolioPerformance
selectedDifference={selectedDifferencePricePretty}
selectedPercentage={selectedPercentageRatePretty}
formattedDate={formattedDate}
showDate={showDate}
/>
</SkeletonLoader>
</div>
<div className="flex items-center gap-3 py-3">
<Button
className="flex items-center gap-2 !rounded-full"
onClick={() => startBridge({ direction: "deposit" })}
>
<Icon id="deposit" className=" h-4 w-4" height={16} width={16} />
<div className="subtitle1">{t("assets.table.depositButton")}</div>
</Button>
<Button
className="group flex items-center gap-2 !rounded-full !bg-osmoverse-825 text-wosmongton-200 hover:bg-gradient-positive hover:text-black hover:shadow-[0px_0px_30px_4px_rgba(57,255,219,0.2)]"
onClick={fiatRampSelection}
>
<CreditCardIcon
isAnimated
classes={{
backCard: "group-hover:stroke-[2]",
frontCard:
"group-hover:fill-[#71B5EB] group-hover:stroke-[2]",
}}
/>
<span className="subtitle1">{t("portfolio.buy")}</span>
</Button>
<Button
className="flex items-center gap-2 !rounded-full !bg-osmoverse-825 text-wosmongton-200"
onClick={() => startBridge({ direction: "withdraw" })}
disabled={totalValue && totalValue.toDec().isZero()}
>
<Icon id="withdraw" height={16} width={16} />
<div className="subtitle1">
{t("assets.table.withdrawButton")}
<header className="flex justify-between">
<div className="mr-6 flex flex-col">
<span className="body1 md:caption text-osmoverse-300">
{t("assets.totalBalance")}
</span>

<SkeletonLoader
className={classNames(
`mt-2 ${isTotalValueFetched ? "" : "h-14 w-48"}`
)}
isLoaded={isTotalValueFetched}
>
{isMobile ? (
<h5>{totalDisplayValue?.toString()}</h5>
) : (
<h3>{totalDisplayValue?.toString()}</h3>
)}
</SkeletonLoader>
<SkeletonLoader
className={classNames(
isPortfolioOverTimeDataIsFetched ? "mt-2" : "mt-2 h-6 w-16"
)}
isLoaded={isPortfolioOverTimeDataIsFetched}
>
<PortfolioPerformance
selectedDifference={selectedDifferencePricePretty}
selectedPercentage={selectedPercentageRatePretty}
formattedDate={formattedDate}
showDate={showDate}
/>
</SkeletonLoader>
<div className="flex items-center gap-3 pt-6">
<Button
className="flex items-center gap-2 !rounded-full"
onClick={() => startBridge({ direction: "deposit" })}
>
<Icon
id="deposit"
className=" h-4 w-4"
height={16}
width={16}
/>
<div className="subtitle1">
{t("assets.table.depositButton")}
</div>
</Button>
<Button
className="group flex items-center gap-2 !rounded-full !bg-osmoverse-825 text-wosmongton-200 hover:bg-gradient-positive hover:text-black hover:shadow-[0px_0px_30px_4px_rgba(57,255,219,0.2)]"
onClick={fiatRampSelection}
>
<CreditCardIcon
isAnimated
classes={{
backCard: "group-hover:stroke-[2]",
frontCard:
"group-hover:fill-[#71B5EB] group-hover:stroke-[2]",
}}
/>
<span className="subtitle1">{t("portfolio.buy")}</span>
</Button>
<Button
className="flex items-center gap-2 !rounded-full !bg-osmoverse-825 text-wosmongton-200 hover:!bg-osmoverse-800"
onClick={() => startBridge({ direction: "withdraw" })}
disabled={totalValue && totalValue.toDec().isZero()}
mattupham marked this conversation as resolved.
Show resolved Hide resolved
>
<Icon id="withdraw" height={16} width={16} />
<div className="subtitle1">
{t("assets.table.withdrawButton")}
</div>
</Button>
</div>
</Button>
</div>

<PortfolioHistoricalChart
data={portfolioOverTimeData as AreaData<Time>[]}
isFetched={isPortfolioOverTimeDataIsFetched}
setDataPoint={setDataPoint}
range={range}
setRange={setRange}
totalPriceChange={totalPriceChange}
error={error}
setShowDate={setShowDate}
resetDataPoint={() => {
setDataPoint({
time: dayjs().unix() as Time,
value: +totalValue.toDec().toString(),
});
setShowDate(false);
}}
/>
</div>
<Transition
show={isChartMinimized}
enter="transition ease-out duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-out duration-500"
leaveFrom="opacity-100"
leaveTo="opacity-0"
className="max-w-[20rem] grow"
as="div"
>
<button
className="group relative flex h-[12.5rem] w-full cursor-pointer flex-col overflow-hidden rounded-[1.25rem] bg-opacity-10 pt-3 hover:bg-osmoverse-900"
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
onClick={() => setIsChartMinimized(false)}
>
<PortfolioHistoricalChartMinimized
data={portfolioOverTimeData as AreaData<Time>[]}
isFetched={isPortfolioOverTimeDataIsFetched}
error={error}
/>
<div className="absolute z-50 h-full w-full">
<Icon
id="resize-expand"
className="absolute top-4 right-4 text-osmoverse-200 opacity-0 transition-opacity duration-100 group-hover:opacity-100"
height={16}
width={16}
/>
</div>
</button>
</Transition>
</header>

<Transition
show={!isChartMinimized}
enter="ease-out duration-500 transition-opacity delay-[250ms]"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-out duration-[250ms] transition-opacity"
leaveFrom="opacity-100"
leaveTo=" opacity-0"
as="div"
unmount={false}
appear={true}
className="absolute top-full w-full"
>
<PortfolioHistoricalChart
setIsChartMinimized={setIsChartMinimized}
data={portfolioOverTimeData as AreaData<Time>[]}
isFetched={isPortfolioOverTimeDataIsFetched}
setDataPoint={setDataPoint}
range={range}
setRange={setRange}
error={error}
setShowDate={setShowDate}
resetDataPoint={() => {
setDataPoint({
time: dayjs().unix() as Time,
value: +totalValue.toDec().toString(),
});
setShowDate(false);
}}
/>
</Transition>
</>
) : (
<GetStartedWithOsmosis />
Expand Down
Loading
Loading