| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- import { ListDashes } from "@phosphor-icons/react/dist/ssr/ListDashes";
- import { Breadcrumbs } from "@pythnetwork/component-library/Breadcrumbs";
- import { Button } from "@pythnetwork/component-library/Button";
- import { Skeleton } from "@pythnetwork/component-library/Skeleton";
- import { StatCard } from "@pythnetwork/component-library/StatCard";
- import { SymbolPairTag } from "@pythnetwork/component-library/SymbolPairTag";
- import { Suspense } from "react";
- import { Cluster } from "../../services/pyth";
- import { AssetClassBadge } from "../AssetClassBadge";
- import { Cards } from "../Cards";
- import { Explain } from "../Explain";
- import { FeedKey } from "../FeedKey";
- import { LiveConfidence, LiveLastUpdated, LivePrice } from "../LivePrices";
- import {
- PriceFeedChangePercent,
- YesterdaysPricesProvider,
- } from "../PriceFeedChangePercent";
- import { PriceFeedIcon } from "../PriceFeedIcon";
- import { PriceName } from "../PriceName";
- import { getFeed } from "./get-feed";
- import styles from "./header.module.scss";
- import { PriceFeedSelect } from "./price-feed-select";
- import { ReferenceData } from "./reference-data";
- type Props = {
- params: Promise<{
- slug: string;
- }>;
- };
- export const PriceFeedHeader = ({ params }: Props) => (
- <Suspense fallback={<PriceFeedHeaderImpl isLoading />}>
- <ResolvedPriceFeedHeader params={params} />
- </Suspense>
- );
- const ResolvedPriceFeedHeader = async ({ params }: Props) => (
- <PriceFeedHeaderImpl {...await getFeed(params)} />
- );
- type PriceFeedHeaderImplProps =
- | { isLoading: true }
- | ({
- isLoading?: false | undefined;
- } & Awaited<ReturnType<typeof getFeed>>);
- const PriceFeedHeaderImpl = (props: PriceFeedHeaderImplProps) => (
- <section className={styles.header}>
- <div className={styles.headerRow}>
- <Breadcrumbs
- label="Breadcrumbs"
- items={[
- { href: "/", label: "Home" },
- { href: "/price-feeds", label: "Price Feeds" },
- {
- label: props.isLoading ? (
- <Skeleton width={30} />
- ) : (
- props.feed.product.display_symbol
- ),
- },
- ]}
- />
- {props.isLoading ? (
- <Skeleton width={15} />
- ) : (
- <AssetClassBadge className={styles.assetClassBadge}>
- {props.feed.product.asset_type}
- </AssetClassBadge>
- )}
- </div>
- <div className={styles.headerRow}>
- <PriceFeedSelect
- className={styles.priceFeedSelect}
- {...(props.isLoading
- ? { isLoading: true }
- : {
- feeds: props.feeds
- .filter((item) => item.symbol !== props.symbol)
- .map((item) => ({
- symbol: item.symbol,
- assetClass: item.product.asset_type,
- description: item.product.description,
- displaySymbol: item.product.display_symbol,
- key: item.product.price_account,
- icon: <PriceFeedIcon assetClass={item.product.asset_type} />,
- })),
- })}
- >
- <SymbolPairTag
- {...(props.isLoading
- ? { isLoading: true }
- : {
- description: props.feed.product.description,
- displaySymbol: props.feed.product.display_symbol,
- icon: (
- <PriceFeedIcon assetClass={props.feed.product.asset_type} />
- ),
- })}
- />
- </PriceFeedSelect>
- <SymbolPairTag
- className={styles.priceFeedTag}
- {...(props.isLoading
- ? { isLoading: true }
- : {
- description: props.feed.product.description,
- displaySymbol: props.feed.product.display_symbol,
- icon: (
- <PriceFeedIcon assetClass={props.feed.product.asset_type} />
- ),
- })}
- />
- <div className={styles.rightGroup}>
- {props.isLoading ? (
- <Skeleton width={30} />
- ) : (
- <FeedKey
- className={styles.feedKey ?? ""}
- feedKey={props.feed.product.price_account}
- />
- )}
- <Button
- variant="outline"
- size="sm"
- beforeIcon={<ListDashes />}
- isPending={props.isLoading}
- {...(!props.isLoading && {
- drawer: {
- fill: true,
- title: "Reference Data",
- contents: (
- <ReferenceData
- feed={{
- symbol: props.feed.symbol,
- feedKey: props.feed.product.price_account,
- assetClass: props.feed.product.asset_type,
- base: props.feed.product.base,
- description: props.feed.product.description,
- country: props.feed.product.country,
- quoteCurrency: props.feed.product.quote_currency,
- tenor: props.feed.product.tenor,
- cmsSymbol: props.feed.product.cms_symbol,
- cqsSymbol: props.feed.product.cqs_symbol,
- nasdaqSymbol: props.feed.product.nasdaq_symbol,
- genericSymbol: props.feed.product.generic_symbol,
- weeklySchedule: props.feed.product.weekly_schedule,
- schedule: props.feed.product.schedule,
- contractId: props.feed.product.contract_id,
- displaySymbol: props.feed.product.display_symbol,
- exponent: props.feed.price.exponent,
- numComponentPrices: props.feed.price.numComponentPrices,
- numQuoters: props.feed.price.numQuoters,
- minPublishers: props.feed.price.minPublishers,
- lastSlot: props.feed.price.lastSlot,
- validSlot: props.feed.price.validSlot,
- }}
- />
- ),
- },
- })}
- >
- Reference Data
- </Button>
- </div>
- </div>
- <Cards>
- <StatCard
- variant="primary"
- header={
- props.isLoading ? (
- <Skeleton width={30} />
- ) : (
- <>
- Aggregated{" "}
- <PriceName assetClass={props.feed.product.asset_type} />
- </>
- )
- }
- stat={
- props.isLoading ? (
- <Skeleton width={20} />
- ) : (
- <LivePrice
- feedKey={props.feed.product.price_account}
- cluster={Cluster.Pythnet}
- />
- )
- }
- />
- <StatCard
- header="Confidence"
- stat={
- props.isLoading ? (
- <Skeleton width={20} />
- ) : (
- <LiveConfidence
- feedKey={props.feed.product.price_account}
- cluster={Cluster.Pythnet}
- />
- )
- }
- corner={
- <Explain size="xs" title="Confidence">
- <p>
- <b>Confidence</b> is how far from the aggregate price Pyth
- believes the true price might be. It reflects a combination of the
- confidence of individual quoters and how well individual quoters
- agree with each other.
- </p>
- <Button
- size="xs"
- variant="solid"
- href="https://docs.pyth.network/price-feeds/best-practices#confidence-intervals"
- target="_blank"
- >
- Learn more
- </Button>
- </Explain>
- }
- />
- <StatCard
- header={
- props.isLoading ? (
- <Skeleton width={30} />
- ) : (
- <>
- 1-Day <PriceName assetClass={props.feed.product.asset_type} />{" "}
- Change
- </>
- )
- }
- stat={
- props.isLoading ? (
- <Skeleton width={20} />
- ) : (
- <YesterdaysPricesProvider
- feeds={{ [props.feed.symbol]: props.feed.product.price_account }}
- >
- <PriceFeedChangePercent
- feedKey={props.feed.product.price_account}
- />
- </YesterdaysPricesProvider>
- )
- }
- />
- <StatCard
- header="Last Updated"
- stat={
- props.isLoading ? (
- <Skeleton width={20} />
- ) : (
- <LiveLastUpdated
- feedKey={props.feed.product.price_account}
- cluster={Cluster.Pythnet}
- />
- )
- }
- />
- </Cards>
- </section>
- );
|