Skip to content

Commit 1158132

Browse files
authored
Merge pull request #3238 from pyth-network/bduran/pyth-pro-demo
feat(insights): ported the Pyth pro feed perf demo to be on an unlisted page in Insights Hub
2 parents f42bbad + ca707cc commit 1158132

File tree

68 files changed

+3709
-31
lines changed

Some content is hidden

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

68 files changed

+3709
-31
lines changed

apps/insights/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
"@pythnetwork/component-library": "workspace:*",
2727
"@pythnetwork/hermes-client": "workspace:*",
2828
"@pythnetwork/known-publishers": "workspace:*",
29+
"@pythnetwork/shared-lib": "workspace:*",
2930
"@react-hookz/web": "catalog:",
3031
"@solana/web3.js": "catalog:",
3132
"async-cache-dedupe": "catalog:",
3233
"bs58": "catalog:",
34+
"change-case": "catalog:",
3335
"clsx": "catalog:",
3436
"date-fns": "catalog:",
3537
"csv-stringify": "catalog:",
@@ -43,8 +45,10 @@
4345
"@pythnetwork/react-hooks": "workspace:",
4446
"react": "catalog:",
4547
"react-aria": "catalog:",
48+
"react-aria-components": "catalog:",
4649
"react-dom": "catalog:",
4750
"recharts": "catalog:",
51+
"sockette": "catalog:",
4852
"superjson": "catalog:",
4953
"swr": "catalog:",
5054
"zod": "catalog:",
@@ -73,4 +77,4 @@
7377
"stylelint-config-standard-scss": "catalog:",
7478
"vercel": "catalog:"
7579
}
76-
}
80+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Metadata } from "next";
2+
export { PythFeedsDemoPage as default } from "../../components/PythFeedsDemoPage";
3+
4+
export const metadata: Metadata = {
5+
title: {
6+
default: "Pyth Realtime Feed Comparison Tool",
7+
template: "%s | Realtime Feed Comparison | Pyth Network Insights",
8+
},
9+
description:
10+
"A real-time price monitoring demo that fetches live price data from multiple exchanges and displays it in an interactive chart.",
11+
robots: {
12+
index: false,
13+
follow: false,
14+
},
15+
};

apps/insights/src/app/robots.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ import type { MetadataRoute } from "next";
33
import { IS_PRODUCTION_SERVER } from "../config/server";
44

55
const robots = (): MetadataRoute.Robots => ({
6-
rules: {
7-
userAgent: "*",
8-
...(IS_PRODUCTION_SERVER ? { allow: "/" } : { disallow: "/" }),
9-
},
6+
rules: IS_PRODUCTION_SERVER
7+
? {
8+
userAgent: "*",
9+
allow: "/",
10+
disallow: ["/pyth-feeds-demo"],
11+
}
12+
: {
13+
userAgent: "*",
14+
disallow: "/",
15+
},
1016
});
1117
export default robots;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.body {
4+
padding-top: theme.spacing(4);
5+
}
6+
7+
.subheader {
8+
border-bottom: 1px solid theme.color("border");
9+
display: flex;
10+
flex-flow: column;
11+
gap: theme.spacing(4);
12+
padding-bottom: theme.spacing(4);
13+
14+
@include theme.breakpoint("sm") {
15+
align-items: flex-end;
16+
flex-flow: initial;
17+
justify-content: space-between;
18+
}
19+
20+
& > div {
21+
&:first-child {
22+
display: flex;
23+
flex-flow: column;
24+
gap: theme.spacing(4);
25+
26+
& > h3,
27+
& > h4 {
28+
margin: 0;
29+
}
30+
31+
& > h4 {
32+
color: theme.color("muted");
33+
}
34+
}
35+
36+
&:last-child {
37+
align-items: center;
38+
display: flex;
39+
gap: theme.spacing(2);
40+
}
41+
}
42+
}
43+
44+
.pythFeedsDemoPageRoot {
45+
@include theme.max-width;
46+
47+
// this holds the chart
48+
& > aside {
49+
margin-top: theme.spacing(2);
50+
}
51+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import classes from "./index.module.scss";
4+
import {
5+
PythProApiTokensProvider,
6+
PythProAppStateProvider,
7+
WebSocketsProvider,
8+
} from "../../context/pyth-pro-demo";
9+
import { PythProApiTokensMenu } from "../PythProApiTokensMenu";
10+
import { PythProDemoCards } from "../PythProDemoCards";
11+
import { PythProDemoPriceChart } from "../PythProDemoPriceChart";
12+
import { PythProDemoSourceSelector } from "../PythProDemoSourceSelector";
13+
14+
export function PythFeedsDemoPage() {
15+
return (
16+
<PythProApiTokensProvider>
17+
<PythProAppStateProvider>
18+
<WebSocketsProvider>
19+
<article className={classes.pythFeedsDemoPageRoot}>
20+
<section>
21+
<div className={classes.subheader}>
22+
<div>
23+
<h3>Pyth Pro</h3>
24+
<h4>Real-time feed comparison tool</h4>
25+
</div>
26+
<div>
27+
<PythProApiTokensMenu />
28+
<PythProDemoSourceSelector />
29+
</div>
30+
</div>
31+
<div className={classes.body}>
32+
<PythProDemoCards />
33+
</div>
34+
</section>
35+
<aside>
36+
<PythProDemoPriceChart />
37+
</aside>
38+
</article>
39+
</WebSocketsProvider>
40+
</PythProAppStateProvider>
41+
</PythProApiTokensProvider>
42+
);
43+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.apiTokensModalBody {
4+
overflow-y: auto;
5+
padding: theme.spacing(6);
6+
padding-top: 0;
7+
}
8+
9+
.apiTokensForm {
10+
border-top: 1px solid theme.color("border");
11+
display: flex;
12+
flex-flow: column;
13+
gap: theme.spacing(2);
14+
padding-top: theme.spacing(4);
15+
margin-top: theme.spacing(4);
16+
}
17+
18+
.apiTokensMenu {
19+
@include theme.breakpoint("sm") {
20+
width: 600px;
21+
}
22+
}
23+
24+
.apiTokensMenuHeader {
25+
align-items: center;
26+
display: flex;
27+
font-size: 1.4rem;
28+
justify-content: space-between;
29+
padding: theme.spacing(6);
30+
padding-right: theme.spacing(2);
31+
}
32+
33+
.fyi {
34+
margin-bottom: theme.spacing(2);
35+
}
36+
37+
.tooltip {
38+
@include theme.elevation("default", 1);
39+
40+
background-color: theme.color("background", "tooltip");
41+
border-radius: theme.border-radius("md");
42+
color: theme.color("tooltip");
43+
min-height: 0;
44+
overflow-y: auto;
45+
padding: theme.spacing(4);
46+
margin-top: theme.spacing(2);
47+
}
48+
49+
.modal {
50+
@include theme.elevation("default", 1);
51+
52+
background-color: theme.color("background", "secondary");
53+
border-radius: theme.border-radius("md");
54+
left: 50%;
55+
max-height: 80vh;
56+
overflow-y: auto;
57+
position: fixed;
58+
transform: translate3d(-50%, -50%, 0);
59+
top: 50%;
60+
width: 90vw;
61+
z-index: 10;
62+
63+
@include theme.breakpoint("sm") {
64+
width: initial;
65+
}
66+
}
67+
68+
.modalOverlay {
69+
background-color: theme.color("background", "nav-blur");
70+
inset: 0;
71+
position: fixed;
72+
z-index: 1;
73+
}
74+
75+
.tokenInputWrapper {
76+
display: flex;
77+
flex-flow: column;
78+
gap: theme.spacing(1);
79+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { GearSix, X } from "@phosphor-icons/react/dist/ssr";
2+
import { Button } from "@pythnetwork/component-library/Button";
3+
import { Input } from "@pythnetwork/component-library/Input";
4+
import { ModalDialog } from "@pythnetwork/component-library/ModalDialog";
5+
import { sentenceCase } from "change-case";
6+
import { useState } from "react";
7+
import { Label, Tooltip, TooltipTrigger } from "react-aria-components";
8+
9+
import classes from "./index.module.scss";
10+
import { usePythProApiTokensContext } from "../../context/pyth-pro-demo";
11+
import { DATA_SOURCES_REQUIRING_API_TOKENS } from "../../schemas/pyth/pyth-pro-demo-schema";
12+
13+
export function PythProApiTokensMenu() {
14+
/** context */
15+
const { tokens, updateApiToken } = usePythProApiTokensContext();
16+
17+
/** state */
18+
const [open, setOpen] = useState(false);
19+
20+
/** local variables */
21+
const tooltip = "Configure your API tokens";
22+
const closeTooltip = "Close API tokens config";
23+
24+
return (
25+
<>
26+
<TooltipTrigger delay={0}>
27+
<Button
28+
aria-label={tooltip}
29+
onClick={() => {
30+
setOpen(true);
31+
}}
32+
variant="outline"
33+
>
34+
<GearSix />
35+
</Button>
36+
<Tooltip className={classes.tooltip ?? ""} placement="bottom">
37+
{tooltip}
38+
</Tooltip>
39+
</TooltipTrigger>
40+
<ModalDialog
41+
className={classes.modal ?? ""}
42+
isOpen={open}
43+
onOpenChange={setOpen}
44+
overlayClassName={classes.modalOverlay ?? ""}
45+
>
46+
<div className={classes.apiTokensMenu}>
47+
<div className={classes.apiTokensMenuHeader}>
48+
<span>{tooltip}</span>
49+
<Button
50+
aria-label={closeTooltip}
51+
onClick={() => {
52+
setOpen(false);
53+
}}
54+
size="md"
55+
variant="ghost"
56+
>
57+
<X />
58+
</Button>
59+
</div>
60+
<div className={classes.apiTokensModalBody}>
61+
<div className={classes.fyi}>
62+
In order to provide a quality demo of Pyth Pro real-time
63+
performance relative to other sources, you will need to provide
64+
API tokens to interact with these APIs.
65+
</div>
66+
<div className={classes.fyi}>
67+
They will be saved securely to your browser for future use here.
68+
</div>
69+
<div className={classes.apiTokensForm}>
70+
{Object.values(DATA_SOURCES_REQUIRING_API_TOKENS.Values).map(
71+
(dataSource) => {
72+
const inputId = `input-${dataSource}`;
73+
const tokenVal = tokens[dataSource] ?? "";
74+
75+
return (
76+
<div className={classes.tokenInputWrapper} key={dataSource}>
77+
<Label htmlFor={inputId}>
78+
{sentenceCase(dataSource)}
79+
</Label>
80+
<Input
81+
autoComplete="off"
82+
fullWidth
83+
id={inputId}
84+
onChange={(e) => {
85+
const {
86+
currentTarget: { value },
87+
} = e;
88+
updateApiToken(dataSource, value);
89+
}}
90+
placeholder="Enter an API token"
91+
type="password"
92+
value={tokenVal}
93+
/>
94+
</div>
95+
);
96+
},
97+
)}
98+
</div>
99+
</div>
100+
</div>
101+
</ModalDialog>
102+
</>
103+
);
104+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.root {
4+
font-size: 2rem;
5+
gap: theme.spacing(2);
6+
margin-top: theme.spacing(4);
7+
text-align: center;
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { HandPointing } from "@phosphor-icons/react/dist/ssr";
2+
3+
import classes from "./empty-state.module.scss";
4+
5+
export function EmptyState() {
6+
return (
7+
<div className={classes.root}>
8+
To get started, select a source from the dropdown, above <HandPointing />
9+
</div>
10+
);
11+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@use "@pythnetwork/component-library/theme";
2+
3+
.root {
4+
display: flex;
5+
gap: theme.spacing(2);
6+
flex-wrap: wrap;
7+
}

0 commit comments

Comments
 (0)