Skip to content

Commit 7e5e504

Browse files
committed
feat(insights): build header & tabs for price feed details page
1 parent 6b611c8 commit 7e5e504

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+3740
-3295
lines changed

apps/api-reference/jsx.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* This file only exists because in react 19, the JSX namespace was moved under
3+
* the React export. However, some libraries (e.g. react-markdown) still have
4+
* some things typed as `JSX.<Something>`. Until those libraries update to
5+
* import the namespace correctly, we'll need this declaration file in place to
6+
* expose JSX via the old global location.
7+
*/
8+
9+
import type { JSX as Jsx } from "react/jsx-runtime";
10+
11+
declare global {
12+
namespace JSX {
13+
type ElementClass = Jsx.ElementClass;
14+
type Element = Jsx.Element;
15+
type IntrinsicElements = Jsx.IntrinsicElements;
16+
}
17+
}

apps/api-reference/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
/// <reference types="next/image-types/global" />
33

44
// NOTE: This file should not be edited
5-
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
5+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

apps/api-reference/src/markdown-components.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ export const MARKDOWN_COMPONENTS = {
3131
</Code>
3232
);
3333
} else {
34-
// @ts-expect-error react-markdown doesn't officially support react 19
35-
// yet; there's no issues here in practice but the types don't currently
36-
// unify
3734
return <pre {...props} />;
3835
}
3936
},

apps/insights/next-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
/// <reference types="next/image-types/global" />
33

44
// NOTE: This file should not be edited
5-
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
5+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

apps/insights/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"clsx": "catalog:",
3636
"cryptocurrency-icons": "catalog:",
3737
"dnum": "catalog:",
38-
"framer-motion": "catalog:",
38+
"motion": "catalog:",
3939
"next": "catalog:",
4040
"next-themes": "catalog:",
4141
"nuqs": "catalog:",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Metadata } from "next";
2+
3+
import { client } from "../../../services/pyth";
4+
export { PriceFeedLayout as default } from "../../../components/PriceFeed/layout";
5+
6+
export const metadata: Metadata = {
7+
title: "Price Feeds",
8+
};
9+
10+
export const generateStaticParams = async () => {
11+
const data = await client.getData();
12+
return data.symbols.map((symbol) => ({ slug: encodeURIComponent(symbol) }));
13+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Chart as default } from "../../../components/PriceFeed/chart";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { PriceComponents as default } from "../../../../components/PriceFeed/price-components";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { PriceFeedsLayout as default } from "../../components/PriceFeeds/layout";
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
import type { Metadata } from "next";
2+
13
export { PriceFeeds as default } from "../../components/PriceFeeds";
4+
5+
export const metadata: Metadata = {
6+
title: "Price Feeds",
7+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
import type { Metadata } from "next";
2+
13
export { Publishers as default } from "../../components/Publishers";
4+
5+
export const metadata: Metadata = {
6+
title: "Publishers",
7+
};

apps/insights/src/components/AsyncValue/index.tsx

Lines changed: 0 additions & 21 deletions
This file was deleted.

apps/insights/src/components/PriceFeeds/change-percent.module.scss renamed to apps/insights/src/components/ChangePercent/index.module.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@use "@pythnetwork/component-library/theme";
22

33
.changePercent {
4-
font-size: theme.font-size("sm");
54
transition: color 100ms linear;
65
display: flex;
76
flex-flow: row nowrap;

apps/insights/src/components/PriceFeeds/change-percent.tsx renamed to apps/insights/src/components/ChangePercent/index.tsx

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
import { CaretUp } from "@phosphor-icons/react/dist/ssr/CaretUp";
44
import { Skeleton } from "@pythnetwork/component-library/Skeleton";
5+
import clsx from "clsx";
56
import { type ComponentProps, createContext, use } from "react";
67
import { useNumberFormatter } from "react-aria";
78
import { z } from "zod";
89

9-
import styles from "./change-percent.module.scss";
10+
import styles from "./index.module.scss";
1011
import { StateType, useData } from "../../use-data";
1112
import { useLivePrice } from "../LivePrices";
1213

@@ -18,20 +19,17 @@ const REFRESH_YESTERDAYS_PRICES_INTERVAL = ONE_HOUR_IN_MS;
1819
const CHANGE_PERCENT_SKELETON_WIDTH = 15;
1920

2021
type Props = Omit<ComponentProps<typeof YesterdaysPricesContext>, "value"> & {
21-
symbolsToFeedKeys: Record<string, string>;
22+
feeds: (Feed & { symbol: string })[];
2223
};
2324

2425
const YesterdaysPricesContext = createContext<
2526
undefined | ReturnType<typeof useData<Map<string, number>>>
2627
>(undefined);
2728

28-
export const YesterdaysPricesProvider = ({
29-
symbolsToFeedKeys,
30-
...props
31-
}: Props) => {
29+
export const YesterdaysPricesProvider = ({ feeds, ...props }: Props) => {
3230
const state = useData(
33-
["yesterdaysPrices", Object.values(symbolsToFeedKeys)],
34-
() => getYesterdaysPrices(symbolsToFeedKeys),
31+
["yesterdaysPrices", feeds.map((feed) => feed.symbol)],
32+
() => getYesterdaysPrices(feeds),
3533
{
3634
refreshInterval: REFRESH_YESTERDAYS_PRICES_INTERVAL,
3735
},
@@ -41,17 +39,21 @@ export const YesterdaysPricesProvider = ({
4139
};
4240

4341
const getYesterdaysPrices = async (
44-
symbolsToFeedKeys: Record<string, string>,
42+
feeds: (Feed & { symbol: string })[],
4543
): Promise<Map<string, number>> => {
4644
const url = new URL("/yesterdays-prices", window.location.origin);
47-
for (const symbol of Object.keys(symbolsToFeedKeys)) {
48-
url.searchParams.append("symbols", symbol);
45+
for (const feed of feeds) {
46+
url.searchParams.append("symbols", feed.symbol);
4947
}
5048
const response = await fetch(url);
5149
const data: unknown = await response.json();
5250
return new Map(
5351
Object.entries(yesterdaysPricesSchema.parse(data)).map(
54-
([symbol, value]) => [symbolsToFeedKeys[symbol] ?? "", value],
52+
([symbol, value]) => [
53+
feeds.find((feed) => feed.symbol === symbol)?.product.price_account ??
54+
"",
55+
value,
56+
],
5557
),
5658
);
5759
};
@@ -69,10 +71,17 @@ const useYesterdaysPrices = () => {
6971
};
7072

7173
type ChangePercentProps = {
72-
feedKey: string;
74+
className?: string | undefined;
75+
feed: Feed;
7376
};
7477

75-
export const ChangePercent = ({ feedKey }: ChangePercentProps) => {
78+
type Feed = {
79+
product: {
80+
price_account: string;
81+
};
82+
};
83+
84+
export const ChangePercent = ({ feed, className }: ChangePercentProps) => {
7685
const yesterdaysPriceState = useYesterdaysPrices();
7786

7887
switch (yesterdaysPriceState.type) {
@@ -85,60 +94,68 @@ export const ChangePercent = ({ feedKey }: ChangePercentProps) => {
8594
case StateType.NotLoaded: {
8695
return (
8796
<Skeleton
88-
className={styles.changePercent}
97+
className={clsx(styles.changePercent, className)}
8998
width={CHANGE_PERCENT_SKELETON_WIDTH}
9099
/>
91100
);
92101
}
93102

94103
case StateType.Loaded: {
95-
const yesterdaysPrice = yesterdaysPriceState.data.get(feedKey);
104+
const yesterdaysPrice = yesterdaysPriceState.data.get(
105+
feed.product.price_account,
106+
);
96107
// eslint-disable-next-line unicorn/no-null
97108
return yesterdaysPrice === undefined ? null : (
98-
<ChangePercentLoaded priorPrice={yesterdaysPrice} feedKey={feedKey} />
109+
<ChangePercentLoaded
110+
className={clsx(styles.changePercent, className)}
111+
priorPrice={yesterdaysPrice}
112+
feed={feed}
113+
/>
99114
);
100115
}
101116
}
102117
};
103118

104119
type ChangePercentLoadedProps = {
120+
className?: string | undefined;
105121
priorPrice: number;
106-
feedKey: string;
122+
feed: Feed;
107123
};
108124

109125
const ChangePercentLoaded = ({
126+
className,
110127
priorPrice,
111-
feedKey,
128+
feed,
112129
}: ChangePercentLoadedProps) => {
113-
const currentPrice = useLivePrice(feedKey);
130+
const currentPrice = useLivePrice(feed);
114131

115132
return currentPrice === undefined ? (
116-
<Skeleton
117-
className={styles.changePercent}
118-
width={CHANGE_PERCENT_SKELETON_WIDTH}
119-
/>
133+
<Skeleton className={className} width={CHANGE_PERCENT_SKELETON_WIDTH} />
120134
) : (
121135
<PriceDifference
122-
currentPrice={currentPrice.price}
136+
className={className}
137+
currentPrice={currentPrice.aggregate.price}
123138
priorPrice={priorPrice}
124139
/>
125140
);
126141
};
127142

128143
type PriceDifferenceProps = {
144+
className?: string | undefined;
129145
currentPrice: number;
130146
priorPrice: number;
131147
};
132148

133149
const PriceDifference = ({
150+
className,
134151
currentPrice,
135152
priorPrice,
136153
}: PriceDifferenceProps) => {
137154
const numberFormatter = useNumberFormatter({ maximumFractionDigits: 2 });
138155
const direction = getDirection(currentPrice, priorPrice);
139156

140157
return (
141-
<span data-direction={direction} className={styles.changePercent}>
158+
<span data-direction={direction} className={className}>
142159
<CaretUp weight="fill" className={styles.caret} />
143160
{numberFormatter.format(
144161
(100 * Math.abs(currentPrice - priorPrice)) / currentPrice,

apps/insights/src/components/CopyButton/index.module.scss

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,29 @@
11
@use "@pythnetwork/component-library/theme";
22

33
.copyButton {
4-
margin: -#{theme.spacing(0.5)} -#{theme.spacing(1)};
5-
display: inline-block;
6-
white-space: nowrap;
7-
border-radius: theme.border-radius("md");
8-
padding: theme.spacing(0.5) theme.spacing(1);
9-
background: none;
10-
cursor: pointer;
11-
transition-property: background-color, color, border-color, outline-color;
12-
transition-duration: 100ms;
13-
transition-timing-function: linear;
14-
border: 1px solid transparent;
15-
outline-offset: 0;
16-
outline: theme.spacing(1) solid transparent;
17-
184
.iconContainer {
195
position: relative;
20-
top: 0.125em;
21-
margin-left: theme.spacing(1);
22-
display: inline-block;
236

247
.copyIcon {
258
opacity: 0.5;
269
transition: opacity 100ms linear;
27-
width: 1em;
28-
height: 1em;
10+
position: absolute;
11+
inset: 0;
12+
width: 100%;
13+
height: 100%;
2914
}
3015

3116
.checkIcon {
3217
position: absolute;
3318
inset: 0;
19+
width: 100%;
20+
height: 100%;
3421
color: theme.color("states", "success", "normal");
3522
opacity: 0;
3623
transition: opacity 100ms linear;
3724
}
3825
}
3926

40-
&[data-hovered] {
41-
background-color: theme.color("button", "outline", "background", "hover");
42-
}
43-
44-
&[data-pressed] {
45-
background-color: theme.color("button", "outline", "background", "active");
46-
}
47-
48-
&[data-focus-visible] {
49-
border-color: theme.color("focus");
50-
outline-color: theme.color("focus-dim");
51-
}
52-
5327
&[data-is-copied] .iconContainer {
5428
.copyIcon {
5529
opacity: 0;

0 commit comments

Comments
 (0)