Skip to content

Commit

Permalink
Add MultiStat component, add feature flag for apr and roi switching (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
DannyDelott authored May 24, 2024
1 parent 24965f6 commit 91d13a1
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export function FeatureFlagPicker(): ReactElement {
className="daisy-menu daisy-dropdown-content z-[1] mt-4 w-52 rounded-box bg-base-100 p-2 shadow"
>
<li className="daisy-menu-title">Feature flags</li>
{/* Place your feature flag menu items here, eg:
<FeatureFlagMenuItem flagName="my-feature">
My new feature
</FeatureFlagMenuItem> */}
{/* Place your feature flag menu items here, eg: */}
<FeatureFlagMenuItem flagName="roi-apr">
ROI and APR
</FeatureFlagMenuItem>
</ul>
</div>
);
Expand Down
98 changes: 98 additions & 0 deletions apps/hyperdrive-trading/src/ui/base/components/MultiStat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import classNames from "classnames";
import { ReactElement } from "react";
import { Stat, StatProps } from "src/ui/base/components/Stat";
import { useActiveItem } from "src/ui/base/hooks/useActiveItem";

export interface MultiStatProps extends StatProps {
id: string;
}

export function MultiStat({
stats,
activeStatId,
onTabChange,
}: {
stats: MultiStatProps[];
onTabChange: (stat: MultiStatProps) => void;
activeStatId: string;
}): ReactElement {
const { activeItem, setActiveItemId } = useActiveItem({
items: stats,
idField: "id",
defaultActiveItemId: stats.find((stat) => activeStatId === stat.id)
?.id as string,
});

return (
<button
className="flex flex-col gap-2"
onClick={() => {
// if the active item isn't the last one, go to the next item
const currentStatIndex = stats.findIndex((i) => i.id === activeItem.id);
let nextStat = stats[0];
if (currentStatIndex !== stats.length - 1) {
nextStat = stats[currentStatIndex + 1];
}
setActiveItemId(nextStat.id);
onTabChange(nextStat);
}}
>
<Stat
label={
<div className="flex flex-col gap-1">
{/* Implements the same label as the Stat component, but adds the
tab buttons below the label. This is done so that the tabs can be
centered under the label, not the whole Stat element */}
{activeItem.description ? (
<p
data-tip={activeItem.description}
className={classNames(
`group daisy-tooltip cursor-help text-start text-sm text-neutral-content before:z-40 before:max-w-56 before:p-2 before:text-start`,
{
"daisy-tooltip-top": activeItem.tooltipPosition === "top",
"daisy-tooltip-bottom":
activeItem.tooltipPosition === "bottom",
"daisy-tooltip-left": activeItem.tooltipPosition === "left",
"daisy-tooltip-right":
activeItem.tooltipPosition === "right",
},
)}
>
{activeItem.label}
<InformationCircleIcon className="group-hover:text-gray-500 ml-1 hidden w-4 text-neutral-content opacity-0 transition duration-150 ease-in-out group-hover:opacity-100 lg:inline-block" />
</p>
) : (
<p className="self-start text-sm text-neutral-content">
{activeItem.label}
</p>
)}

<div
className={classNames("flex items-center justify-center gap-2", {
"pr-4": !!activeItem.description,
})}
>
{stats.map(({ id }) => {
return (
<div
key={id}
className={classNames(
"transition-all hover:opacity-90",
id === activeItem.id
? "scale-150 text-base-content"
: "text-neutral-content hover:scale-125",
)}
>
</div>
);
})}
</div>
</div>
}
value={activeItem.value}
/>
</button>
);
}
4 changes: 2 additions & 2 deletions apps/hyperdrive-trading/src/ui/base/components/Stat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from "classnames";
import { ReactElement, ReactNode } from "react";

export interface StatProps {
label: string;
label: ReactNode;
value: ReactNode;
description?: string;
tooltipPosition?: "top" | "bottom" | "left" | "right";
Expand Down Expand Up @@ -37,7 +37,7 @@ export function Stat({
<InformationCircleIcon className="group-hover:text-gray-500 ml-1 hidden w-4 text-neutral-content opacity-0 transition duration-150 ease-in-out group-hover:opacity-100 lg:inline-block" />
</p>
) : (
<p className="text-sm text-neutral-content">{label}</p>
<div className="text-sm text-neutral-content">{label}</div>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function MarketDetailsBody({
</div>

{/* Stats section */}
<div className="flex flex-wrap gap-16 ">
<div className="flex flex-wrap gap-16">
<YieldStats hyperdrive={hyperdrive} />
<LiquidityStats hyperdrive={hyperdrive} />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { HyperdriveConfig } from "@hyperdrive/appconfig";
import classNames from "classnames";
import { ReactElement } from "react";
import Skeleton from "react-loading-skeleton";
import { useLocalStorage } from "react-use";
import { MultiStat, MultiStatProps } from "src/ui/base/components/MultiStat";
import { useIsTailwindSmallScreen } from "src/ui/base/mediaBreakpoints";
import { useCurrentFixedAPR } from "src/ui/hyperdrive/hooks/useCurrentFixedAPR";

export function FixedRateStat({
hyperdrive,
}: {
hyperdrive: HyperdriveConfig;
}): ReactElement {
const isTailwindSmallScreen = useIsTailwindSmallScreen();
const { fixedAPR, fixedAPRStatus } = useCurrentFixedAPR(hyperdrive.address);
const [rateType, setRateType] = useLocalStorage<"fixedApr" | "fixedRoi">(
"yield-stats-long-rate-type",
"fixedApr",
);
return (
<MultiStat
activeStatId={
rateType! /* Stripping off the undefined because we set a default value
in useLocalStorage */
}
stats={[
{
id: "fixedApr",
label: "Fixed APR",
value:
fixedAPRStatus === "loading" && fixedAPR === undefined ? (
<Skeleton className="w-20" />
) : (
<span className={classNames("flex items-center gap-1.5")}>
{fixedAPR?.formatted || "0"}%
</span>
),

description:
"Annualized fixed rate earned from opening longs, before fees and slippage are applied.",
tooltipPosition: isTailwindSmallScreen ? "right" : "bottom",
},
{
id: "fixedRoi",
label: "Fixed ROI",
value:
fixedAPRStatus === "loading" && fixedAPR === undefined ? (
<Skeleton className="w-20" />
) : (
<span className={classNames("flex items-center gap-1.5")}>
{fixedAPR?.formatted || "0"}%
</span>
),

description:
"Holding period return for the duration of the term, before fees and slippage are applied.",
tooltipPosition: isTailwindSmallScreen ? "right" : "bottom",
},
]}
onTabChange={(stat: MultiStatProps) => {
setRateType(stat.id as any);
}}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { HyperdriveConfig } from "@hyperdrive/appconfig";
import classNames from "classnames";
import { ReactElement } from "react";
import Skeleton from "react-loading-skeleton";
import { useLocalStorage } from "react-use";
import { formatRate } from "src/base/formatRate";
import { parseUnits } from "src/base/parseUnits";
import { MultiStat, MultiStatProps } from "src/ui/base/components/MultiStat";
import { useImpliedRate } from "src/ui/hyperdrive/shorts/hooks/useImpliedRate";
import { useYieldSourceRate } from "src/ui/vaults/useYieldSourceRate";

export function ShortRateStat({
hyperdrive,
}: {
hyperdrive: HyperdriveConfig;
}): ReactElement {
const [rateType, setRateType] = useLocalStorage<"shortApr" | "shortRoi">(
"yield-stats-short-rate-type",
"shortApr",
);
const { vaultRate } = useYieldSourceRate({
hyperdriveAddress: hyperdrive.address,
});

const { impliedRate, impliedRateStatus, impliedRateFetchStatus } =
useImpliedRate({
bondAmount: parseUnits("1", 18),
hyperdriveAddress: hyperdrive.address,
variableApy: vaultRate?.vaultRate ? vaultRate.vaultRate : undefined,
timestamp: BigInt(Math.floor(Date.now() / 1000)),
});
const isLoadingShortRoi =
impliedRateStatus === "loading" &&
impliedRateFetchStatus === "fetching" &&
impliedRate === undefined;

const formattedRate = impliedRate ? `${formatRate(impliedRate)}%` : "-";

return (
<MultiStat
activeStatId={
rateType! /* Stripping off the undefined because we set a default value
in useLocalStorage */
}
stats={[
{
id: "shortApr",
label: "Short APR",
description:
"Annualized return on shorts assuming the current variable rate stays the same for 1 year.",
tooltipPosition: "bottom",
value: isLoadingShortRoi ? (
<Skeleton className="w-20" />
) : (
<span className={classNames("flex items-center gap-1.5")}>
{formattedRate}
</span>
),
},
{
id: "shortRoi",
label: "Short ROI",
description:
"Holding period return on shorts assuming the current variable rate stays the same until maturity.",
tooltipPosition: "bottom",
value: isLoadingShortRoi ? (
<Skeleton className="w-20" />
) : (
<span className={classNames("flex items-center gap-1.5")}>
{formattedRate}
</span>
),
},
]}
onTabChange={(stat: MultiStatProps) => {
setRateType(stat.id as any);
}}
/>
);
}
Loading

0 comments on commit 91d13a1

Please sign in to comment.