header.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. import { ListDashes } from "@phosphor-icons/react/dist/ssr/ListDashes";
  2. import { Breadcrumbs } from "@pythnetwork/component-library/Breadcrumbs";
  3. import { Button } from "@pythnetwork/component-library/Button";
  4. import { Skeleton } from "@pythnetwork/component-library/Skeleton";
  5. import { StatCard } from "@pythnetwork/component-library/StatCard";
  6. import { SymbolPairTag } from "@pythnetwork/component-library/SymbolPairTag";
  7. import { Suspense } from "react";
  8. import { Cluster } from "../../services/pyth";
  9. import { AssetClassBadge } from "../AssetClassBadge";
  10. import { Cards } from "../Cards";
  11. import { Explain } from "../Explain";
  12. import { FeedKey } from "../FeedKey";
  13. import { LiveConfidence, LiveLastUpdated, LivePrice } from "../LivePrices";
  14. import {
  15. PriceFeedChangePercent,
  16. YesterdaysPricesProvider,
  17. } from "../PriceFeedChangePercent";
  18. import { PriceFeedIcon } from "../PriceFeedIcon";
  19. import { PriceName } from "../PriceName";
  20. import { getFeed } from "./get-feed";
  21. import styles from "./header.module.scss";
  22. import { PriceFeedSelect } from "./price-feed-select";
  23. import { ReferenceData } from "./reference-data";
  24. type Props = {
  25. params: Promise<{
  26. slug: string;
  27. }>;
  28. };
  29. export const PriceFeedHeader = ({ params }: Props) => (
  30. <Suspense fallback={<PriceFeedHeaderImpl isLoading />}>
  31. <ResolvedPriceFeedHeader params={params} />
  32. </Suspense>
  33. );
  34. const ResolvedPriceFeedHeader = async ({ params }: Props) => (
  35. <PriceFeedHeaderImpl {...await getFeed(params)} />
  36. );
  37. type PriceFeedHeaderImplProps =
  38. | { isLoading: true }
  39. | ({
  40. isLoading?: false | undefined;
  41. } & Awaited<ReturnType<typeof getFeed>>);
  42. const PriceFeedHeaderImpl = (props: PriceFeedHeaderImplProps) => (
  43. <section className={styles.header}>
  44. <div className={styles.headerRow}>
  45. <Breadcrumbs
  46. label="Breadcrumbs"
  47. items={[
  48. { href: "/", label: "Home" },
  49. { href: "/price-feeds", label: "Price Feeds" },
  50. {
  51. label: props.isLoading ? (
  52. <Skeleton width={30} />
  53. ) : (
  54. props.feed.product.display_symbol
  55. ),
  56. },
  57. ]}
  58. />
  59. {props.isLoading ? (
  60. <Skeleton width={15} />
  61. ) : (
  62. <AssetClassBadge className={styles.assetClassBadge}>
  63. {props.feed.product.asset_type}
  64. </AssetClassBadge>
  65. )}
  66. </div>
  67. <div className={styles.headerRow}>
  68. <PriceFeedSelect
  69. className={styles.priceFeedSelect}
  70. {...(props.isLoading
  71. ? { isLoading: true }
  72. : {
  73. feeds: props.feeds
  74. .filter((item) => item.symbol !== props.symbol)
  75. .map((item) => ({
  76. symbol: item.symbol,
  77. assetClass: item.product.asset_type,
  78. description: item.product.description,
  79. displaySymbol: item.product.display_symbol,
  80. key: item.product.price_account,
  81. icon: <PriceFeedIcon assetClass={item.product.asset_type} />,
  82. })),
  83. })}
  84. >
  85. <SymbolPairTag
  86. {...(props.isLoading
  87. ? { isLoading: true }
  88. : {
  89. description: props.feed.product.description,
  90. displaySymbol: props.feed.product.display_symbol,
  91. icon: (
  92. <PriceFeedIcon assetClass={props.feed.product.asset_type} />
  93. ),
  94. })}
  95. />
  96. </PriceFeedSelect>
  97. <SymbolPairTag
  98. className={styles.priceFeedTag}
  99. {...(props.isLoading
  100. ? { isLoading: true }
  101. : {
  102. description: props.feed.product.description,
  103. displaySymbol: props.feed.product.display_symbol,
  104. icon: (
  105. <PriceFeedIcon assetClass={props.feed.product.asset_type} />
  106. ),
  107. })}
  108. />
  109. <div className={styles.rightGroup}>
  110. {props.isLoading ? (
  111. <Skeleton width={30} />
  112. ) : (
  113. <FeedKey
  114. className={styles.feedKey ?? ""}
  115. feedKey={props.feed.product.price_account}
  116. />
  117. )}
  118. <Button
  119. variant="outline"
  120. size="sm"
  121. beforeIcon={<ListDashes />}
  122. isPending={props.isLoading}
  123. {...(!props.isLoading && {
  124. drawer: {
  125. fill: true,
  126. title: "Reference Data",
  127. contents: (
  128. <ReferenceData
  129. feed={{
  130. symbol: props.feed.symbol,
  131. feedKey: props.feed.product.price_account,
  132. assetClass: props.feed.product.asset_type,
  133. base: props.feed.product.base,
  134. description: props.feed.product.description,
  135. country: props.feed.product.country,
  136. quoteCurrency: props.feed.product.quote_currency,
  137. tenor: props.feed.product.tenor,
  138. cmsSymbol: props.feed.product.cms_symbol,
  139. cqsSymbol: props.feed.product.cqs_symbol,
  140. nasdaqSymbol: props.feed.product.nasdaq_symbol,
  141. genericSymbol: props.feed.product.generic_symbol,
  142. weeklySchedule: props.feed.product.weekly_schedule,
  143. schedule: props.feed.product.schedule,
  144. contractId: props.feed.product.contract_id,
  145. displaySymbol: props.feed.product.display_symbol,
  146. exponent: props.feed.price.exponent,
  147. numComponentPrices: props.feed.price.numComponentPrices,
  148. numQuoters: props.feed.price.numQuoters,
  149. minPublishers: props.feed.price.minPublishers,
  150. lastSlot: props.feed.price.lastSlot,
  151. validSlot: props.feed.price.validSlot,
  152. }}
  153. />
  154. ),
  155. },
  156. })}
  157. >
  158. Reference Data
  159. </Button>
  160. </div>
  161. </div>
  162. <Cards>
  163. <StatCard
  164. variant="primary"
  165. header={
  166. props.isLoading ? (
  167. <Skeleton width={30} />
  168. ) : (
  169. <>
  170. Aggregated{" "}
  171. <PriceName assetClass={props.feed.product.asset_type} />
  172. </>
  173. )
  174. }
  175. stat={
  176. props.isLoading ? (
  177. <Skeleton width={20} />
  178. ) : (
  179. <LivePrice
  180. feedKey={props.feed.product.price_account}
  181. cluster={Cluster.Pythnet}
  182. />
  183. )
  184. }
  185. />
  186. <StatCard
  187. header="Confidence"
  188. stat={
  189. props.isLoading ? (
  190. <Skeleton width={20} />
  191. ) : (
  192. <LiveConfidence
  193. feedKey={props.feed.product.price_account}
  194. cluster={Cluster.Pythnet}
  195. />
  196. )
  197. }
  198. corner={
  199. <Explain size="xs" title="Confidence">
  200. <p>
  201. <b>Confidence</b> is how far from the aggregate price Pyth
  202. believes the true price might be. It reflects a combination of the
  203. confidence of individual quoters and how well individual quoters
  204. agree with each other.
  205. </p>
  206. <Button
  207. size="xs"
  208. variant="solid"
  209. href="https://docs.pyth.network/price-feeds/best-practices#confidence-intervals"
  210. target="_blank"
  211. >
  212. Learn more
  213. </Button>
  214. </Explain>
  215. }
  216. />
  217. <StatCard
  218. header={
  219. props.isLoading ? (
  220. <Skeleton width={30} />
  221. ) : (
  222. <>
  223. 1-Day <PriceName assetClass={props.feed.product.asset_type} />{" "}
  224. Change
  225. </>
  226. )
  227. }
  228. stat={
  229. props.isLoading ? (
  230. <Skeleton width={20} />
  231. ) : (
  232. <YesterdaysPricesProvider
  233. feeds={{ [props.feed.symbol]: props.feed.product.price_account }}
  234. >
  235. <PriceFeedChangePercent
  236. feedKey={props.feed.product.price_account}
  237. />
  238. </YesterdaysPricesProvider>
  239. )
  240. }
  241. />
  242. <StatCard
  243. header="Last Updated"
  244. stat={
  245. props.isLoading ? (
  246. <Skeleton width={20} />
  247. ) : (
  248. <LiveLastUpdated
  249. feedKey={props.feed.product.price_account}
  250. cluster={Cluster.Pythnet}
  251. />
  252. )
  253. }
  254. />
  255. </Cards>
  256. </section>
  257. );