11"use client" ;
22
33import { CaretUp } from "@phosphor-icons/react/dist/ssr/CaretUp" ;
4- import { Card } from "@pythnetwork/component-library/Card" ;
54import { Skeleton } from "@pythnetwork/component-library/Skeleton" ;
6- import { type ReactNode , useMemo } from "react" ;
5+ import { type ComponentProps , createContext , use } from "react" ;
76import { useNumberFormatter } from "react-aria" ;
87import { z } from "zod" ;
98
10- import styles from "./featured-recently-added .module.scss" ;
9+ import styles from "./change-percent .module.scss" ;
1110import { StateType , useData } from "../../use-data" ;
12- import { LivePrice , useLivePrice } from "../LivePrices" ;
11+ import { useLivePrice } from "../LivePrices" ;
1312
1413const ONE_SECOND_IN_MS = 1000 ;
1514const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS ;
@@ -18,87 +17,64 @@ const REFRESH_YESTERDAYS_PRICES_INTERVAL = ONE_HOUR_IN_MS;
1817
1918const CHANGE_PERCENT_SKELETON_WIDTH = 15 ;
2019
21- type Props = {
22- recentlyAdded : RecentlyAddedPriceFeed [ ] ;
20+ type Props = Omit < ComponentProps < typeof YesterdaysPricesContext > , "value" > & {
21+ symbolsToFeedKeys : Record < string , string > ;
2322} ;
2423
25- type RecentlyAddedPriceFeed = {
26- id : string ;
27- symbol : string ;
28- priceFeedName : ReactNode ;
29- } ;
24+ const YesterdaysPricesContext = createContext <
25+ undefined | ReturnType < typeof useData < Map < string , number > > >
26+ > ( undefined ) ;
3027
31- export const FeaturedRecentlyAdded = ( { recentlyAdded } : Props ) => {
32- const feedKeys = useMemo (
33- ( ) => recentlyAdded . map ( ( { id } ) => id ) ,
34- [ recentlyAdded ] ,
35- ) ;
36- const symbols = useMemo (
37- ( ) => recentlyAdded . map ( ( { symbol } ) => symbol ) ,
38- [ recentlyAdded ] ,
39- ) ;
28+ export const YesterdaysPricesProvider = ( {
29+ symbolsToFeedKeys,
30+ ...props
31+ } : Props ) => {
4032 const state = useData (
41- [ "yesterdaysPrices" , feedKeys ] ,
42- ( ) => getYesterdaysPrices ( symbols ) ,
33+ [ "yesterdaysPrices" , Object . values ( symbolsToFeedKeys ) ] ,
34+ ( ) => getYesterdaysPrices ( symbolsToFeedKeys ) ,
4335 {
4436 refreshInterval : REFRESH_YESTERDAYS_PRICES_INTERVAL ,
4537 } ,
4638 ) ;
4739
48- return (
49- < >
50- { recentlyAdded . map ( ( { priceFeedName, id, symbol } , i ) => (
51- < Card
52- key = { i }
53- href = "#"
54- title = { priceFeedName }
55- footer = {
56- < div className = { styles . footer } >
57- < LivePrice account = { id } />
58- < div className = { styles . changePercent } >
59- < ChangePercent
60- yesterdaysPriceState = { state }
61- feedKey = { id }
62- symbol = { symbol }
63- />
64- </ div >
65- </ div >
66- }
67- className = { styles . recentlyAddedFeed ?? "" }
68- variant = "tertiary"
69- />
70- ) ) }
71- </ >
72- ) ;
40+ return < YesterdaysPricesContext value = { state } { ...props } /> ;
7341} ;
7442
7543const getYesterdaysPrices = async (
76- symbols : string [ ] ,
77- ) : Promise < Record < string , number > > => {
44+ symbolsToFeedKeys : Record < string , string > ,
45+ ) : Promise < Map < string , number > > => {
7846 const url = new URL ( "/yesterdays-prices" , window . location . origin ) ;
79- for ( const symbol of symbols ) {
47+ for ( const symbol of Object . keys ( symbolsToFeedKeys ) ) {
8048 url . searchParams . append ( "symbols" , symbol ) ;
8149 }
8250 const response = await fetch ( url ) ;
8351 const data : unknown = await response . json ( ) ;
84- return yesterdaysPricesSchema . parse ( data ) ;
52+ return new Map (
53+ Object . entries ( yesterdaysPricesSchema . parse ( data ) ) . map (
54+ ( [ symbol , value ] ) => [ symbolsToFeedKeys [ symbol ] ?? "" , value ] ,
55+ ) ,
56+ ) ;
8557} ;
8658
8759const yesterdaysPricesSchema = z . record ( z . string ( ) , z . number ( ) ) ;
8860
61+ const useYesterdaysPrices = ( ) => {
62+ const state = use ( YesterdaysPricesContext ) ;
63+
64+ if ( state ) {
65+ return state ;
66+ } else {
67+ throw new YesterdaysPricesNotInitializedError ( ) ;
68+ }
69+ } ;
70+
8971type ChangePercentProps = {
90- yesterdaysPriceState : ReturnType <
91- typeof useData < Awaited < ReturnType < typeof getYesterdaysPrices > > >
92- > ;
9372 feedKey : string ;
94- symbol : string ;
9573} ;
9674
97- const ChangePercent = ( {
98- yesterdaysPriceState,
99- feedKey,
100- symbol,
101- } : ChangePercentProps ) => {
75+ export const ChangePercent = ( { feedKey } : ChangePercentProps ) => {
76+ const yesterdaysPriceState = useYesterdaysPrices ( ) ;
77+
10278 switch ( yesterdaysPriceState . type ) {
10379 case StateType . Error : {
10480 // eslint-disable-next-line unicorn/no-null
@@ -107,11 +83,16 @@ const ChangePercent = ({
10783
10884 case StateType . Loading :
10985 case StateType . NotLoaded : {
110- return < Skeleton width = { CHANGE_PERCENT_SKELETON_WIDTH } /> ;
86+ return (
87+ < Skeleton
88+ className = { styles . changePercent }
89+ width = { CHANGE_PERCENT_SKELETON_WIDTH }
90+ />
91+ ) ;
11192 }
11293
11394 case StateType . Loaded : {
114- const yesterdaysPrice = yesterdaysPriceState . data [ symbol ] ;
95+ const yesterdaysPrice = yesterdaysPriceState . data . get ( feedKey ) ;
11596 // eslint-disable-next-line unicorn/no-null
11697 return yesterdaysPrice === undefined ? null : (
11798 < ChangePercentLoaded priorPrice = { yesterdaysPrice } feedKey = { feedKey } />
@@ -132,7 +113,10 @@ const ChangePercentLoaded = ({
132113 const currentPrice = useLivePrice ( feedKey ) ;
133114
134115 return currentPrice === undefined ? (
135- < Skeleton width = { CHANGE_PERCENT_SKELETON_WIDTH } />
116+ < Skeleton
117+ className = { styles . changePercent }
118+ width = { CHANGE_PERCENT_SKELETON_WIDTH }
119+ />
136120 ) : (
137121 < PriceDifference
138122 currentPrice = { currentPrice . price }
@@ -154,7 +138,7 @@ const PriceDifference = ({
154138 const direction = getDirection ( currentPrice , priorPrice ) ;
155139
156140 return (
157- < span data-direction = { direction } className = { styles . price } >
141+ < span data-direction = { direction } className = { styles . changePercent } >
158142 < CaretUp weight = "fill" className = { styles . caret } />
159143 { numberFormatter . format (
160144 ( 100 * Math . abs ( currentPrice - priorPrice ) ) / currentPrice ,
@@ -173,3 +157,12 @@ const getDirection = (currentPrice: number, priorPrice: number) => {
173157 return "flat" ;
174158 }
175159} ;
160+
161+ class YesterdaysPricesNotInitializedError extends Error {
162+ constructor ( ) {
163+ super (
164+ "This component must be contained within a <YesterdaysPricesProvider>" ,
165+ ) ;
166+ this . name = "YesterdaysPricesNotInitializedError" ;
167+ }
168+ }
0 commit comments