1
1
"use client" ;
2
2
3
3
import { CaretUp } from "@phosphor-icons/react/dist/ssr/CaretUp" ;
4
- import { Card } from "@pythnetwork/component-library/Card" ;
5
4
import { Skeleton } from "@pythnetwork/component-library/Skeleton" ;
6
- import { type ReactNode , useMemo } from "react" ;
5
+ import { type ComponentProps , createContext , use } from "react" ;
7
6
import { useNumberFormatter } from "react-aria" ;
8
7
import { z } from "zod" ;
9
8
10
- import styles from "./featured-recently-added .module.scss" ;
9
+ import styles from "./change-percent .module.scss" ;
11
10
import { StateType , useData } from "../../use-data" ;
12
- import { LivePrice , useLivePrice } from "../LivePrices" ;
11
+ import { useLivePrice } from "../LivePrices" ;
13
12
14
13
const ONE_SECOND_IN_MS = 1000 ;
15
14
const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS ;
@@ -18,87 +17,64 @@ const REFRESH_YESTERDAYS_PRICES_INTERVAL = ONE_HOUR_IN_MS;
18
17
19
18
const CHANGE_PERCENT_SKELETON_WIDTH = 15 ;
20
19
21
- type Props = {
22
- recentlyAdded : RecentlyAddedPriceFeed [ ] ;
20
+ type Props = Omit < ComponentProps < typeof YesterdaysPricesContext > , "value" > & {
21
+ symbolsToFeedKeys : Record < string , string > ;
23
22
} ;
24
23
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 ) ;
30
27
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 ) => {
40
32
const state = useData (
41
- [ "yesterdaysPrices" , feedKeys ] ,
42
- ( ) => getYesterdaysPrices ( symbols ) ,
33
+ [ "yesterdaysPrices" , Object . values ( symbolsToFeedKeys ) ] ,
34
+ ( ) => getYesterdaysPrices ( symbolsToFeedKeys ) ,
43
35
{
44
36
refreshInterval : REFRESH_YESTERDAYS_PRICES_INTERVAL ,
45
37
} ,
46
38
) ;
47
39
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 } /> ;
73
41
} ;
74
42
75
43
const getYesterdaysPrices = async (
76
- symbols : string [ ] ,
77
- ) : Promise < Record < string , number > > => {
44
+ symbolsToFeedKeys : Record < string , string > ,
45
+ ) : Promise < Map < string , number > > => {
78
46
const url = new URL ( "/yesterdays-prices" , window . location . origin ) ;
79
- for ( const symbol of symbols ) {
47
+ for ( const symbol of Object . keys ( symbolsToFeedKeys ) ) {
80
48
url . searchParams . append ( "symbols" , symbol ) ;
81
49
}
82
50
const response = await fetch ( url ) ;
83
51
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
+ ) ;
85
57
} ;
86
58
87
59
const yesterdaysPricesSchema = z . record ( z . string ( ) , z . number ( ) ) ;
88
60
61
+ const useYesterdaysPrices = ( ) => {
62
+ const state = use ( YesterdaysPricesContext ) ;
63
+
64
+ if ( state ) {
65
+ return state ;
66
+ } else {
67
+ throw new YesterdaysPricesNotInitializedError ( ) ;
68
+ }
69
+ } ;
70
+
89
71
type ChangePercentProps = {
90
- yesterdaysPriceState : ReturnType <
91
- typeof useData < Awaited < ReturnType < typeof getYesterdaysPrices > > >
92
- > ;
93
72
feedKey : string ;
94
- symbol : string ;
95
73
} ;
96
74
97
- const ChangePercent = ( {
98
- yesterdaysPriceState,
99
- feedKey,
100
- symbol,
101
- } : ChangePercentProps ) => {
75
+ export const ChangePercent = ( { feedKey } : ChangePercentProps ) => {
76
+ const yesterdaysPriceState = useYesterdaysPrices ( ) ;
77
+
102
78
switch ( yesterdaysPriceState . type ) {
103
79
case StateType . Error : {
104
80
// eslint-disable-next-line unicorn/no-null
@@ -107,11 +83,16 @@ const ChangePercent = ({
107
83
108
84
case StateType . Loading :
109
85
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
+ ) ;
111
92
}
112
93
113
94
case StateType . Loaded : {
114
- const yesterdaysPrice = yesterdaysPriceState . data [ symbol ] ;
95
+ const yesterdaysPrice = yesterdaysPriceState . data . get ( feedKey ) ;
115
96
// eslint-disable-next-line unicorn/no-null
116
97
return yesterdaysPrice === undefined ? null : (
117
98
< ChangePercentLoaded priorPrice = { yesterdaysPrice } feedKey = { feedKey } />
@@ -132,7 +113,10 @@ const ChangePercentLoaded = ({
132
113
const currentPrice = useLivePrice ( feedKey ) ;
133
114
134
115
return currentPrice === undefined ? (
135
- < Skeleton width = { CHANGE_PERCENT_SKELETON_WIDTH } />
116
+ < Skeleton
117
+ className = { styles . changePercent }
118
+ width = { CHANGE_PERCENT_SKELETON_WIDTH }
119
+ />
136
120
) : (
137
121
< PriceDifference
138
122
currentPrice = { currentPrice . price }
@@ -154,7 +138,7 @@ const PriceDifference = ({
154
138
const direction = getDirection ( currentPrice , priorPrice ) ;
155
139
156
140
return (
157
- < span data-direction = { direction } className = { styles . price } >
141
+ < span data-direction = { direction } className = { styles . changePercent } >
158
142
< CaretUp weight = "fill" className = { styles . caret } />
159
143
{ numberFormatter . format (
160
144
( 100 * Math . abs ( currentPrice - priorPrice ) ) / currentPrice ,
@@ -173,3 +157,12 @@ const getDirection = (currentPrice: number, priorPrice: number) => {
173
157
return "flat" ;
174
158
}
175
159
} ;
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