Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Publishers as default } from "../../../../components/PriceFeed/publishers";
2 changes: 1 addition & 1 deletion apps/insights/src/app/price-feeds/layout.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { PriceFeedsLayout as default } from "../../components/PriceFeeds/layout";
export { ZoomLayoutTransition as default } from "../../components/ZoomLayoutTransition";
3 changes: 3 additions & 0 deletions apps/insights/src/app/publishers/[key]/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use client";

export { Error as default } from "../../../components/Error";
13 changes: 13 additions & 0 deletions apps/insights/src/app/publishers/[key]/layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Metadata } from "next";

export { PublishersLayout as default } from "../../../components/Publisher/layout";
import { getPublishers } from "../../../services/clickhouse";

export const metadata: Metadata = {
title: "Publishers",
};

export const generateStaticParams = async () => {
const publishers = await getPublishers();
return publishers.map(({ key }) => ({ key }));
};
1 change: 1 addition & 0 deletions apps/insights/src/app/publishers/[key]/page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Performance as default } from "../../../components/Publisher/performance";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PriceFeeds as default } from "../../../../components/Publisher/price-feeds";
1 change: 1 addition & 0 deletions apps/insights/src/app/publishers/layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ZoomLayoutTransition as default } from "../../components/ZoomLayoutTransition";
18 changes: 2 additions & 16 deletions apps/insights/src/app/yesterdays-prices/route.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import type { NextRequest } from "next/server";
import { z } from "zod";

import { client } from "../../services/clickhouse";
import { getYesterdaysPrices } from "../../services/clickhouse";

export async function GET(req: NextRequest) {
const symbols = req.nextUrl.searchParams.getAll("symbols");
const rows = await client.query({
query:
"select symbol, price from insights_yesterdays_prices(symbols={symbols: Array(String)})",
query_params: { symbols },
});
const result = await rows.json();
const data = schema.parse(result.data);
const data = await getYesterdaysPrices(symbols);
return Response.json(
Object.fromEntries(data.map(({ symbol, price }) => [symbol, price])),
);
}

const schema = z.array(
z.object({
symbol: z.string(),
price: z.number(),
}),
);
25 changes: 7 additions & 18 deletions apps/insights/src/components/ChangePercent/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
"use client";

import { CaretUp } from "@phosphor-icons/react/dist/ssr/CaretUp";
import { Skeleton } from "@pythnetwork/component-library/Skeleton";
import clsx from "clsx";
import { type ComponentProps, createContext, use } from "react";
import { useNumberFormatter } from "react-aria";
import { z } from "zod";

import styles from "./index.module.scss";
import { StateType, useData } from "../../use-data";
import { ChangeValue } from "../ChangeValue";
import { useLivePrice } from "../LivePrices";

const ONE_SECOND_IN_MS = 1000;
const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS;
const ONE_HOUR_IN_MS = 60 * ONE_MINUTE_IN_MS;
const REFRESH_YESTERDAYS_PRICES_INTERVAL = ONE_HOUR_IN_MS;

const CHANGE_PERCENT_SKELETON_WIDTH = 15;

type Props = Omit<ComponentProps<typeof YesterdaysPricesContext>, "value"> & {
feeds: (Feed & { symbol: string })[];
};
Expand Down Expand Up @@ -92,12 +87,7 @@ export const ChangePercent = ({ feed, className }: ChangePercentProps) => {

case StateType.Loading:
case StateType.NotLoaded: {
return (
<Skeleton
className={clsx(styles.changePercent, className)}
width={CHANGE_PERCENT_SKELETON_WIDTH}
/>
);
return <ChangeValue className={className} isLoading />;
}

case StateType.Loaded: {
Expand All @@ -107,7 +97,7 @@ export const ChangePercent = ({ feed, className }: ChangePercentProps) => {
// eslint-disable-next-line unicorn/no-null
return yesterdaysPrice === undefined ? null : (
<ChangePercentLoaded
className={clsx(styles.changePercent, className)}
className={className}
priorPrice={yesterdaysPrice}
feed={feed}
/>
Expand All @@ -130,7 +120,7 @@ const ChangePercentLoaded = ({
const currentPrice = useLivePrice(feed);

return currentPrice === undefined ? (
<Skeleton className={className} width={CHANGE_PERCENT_SKELETON_WIDTH} />
<ChangeValue className={className} isLoading />
) : (
<PriceDifference
className={className}
Expand All @@ -155,13 +145,12 @@ const PriceDifference = ({
const direction = getDirection(currentPrice, priorPrice);

return (
<span data-direction={direction} className={className}>
<CaretUp weight="fill" className={styles.caret} />
<ChangeValue direction={direction} className={className}>
{numberFormatter.format(
(100 * Math.abs(currentPrice - priorPrice)) / currentPrice,
(100 * Math.abs(currentPrice - priorPrice)) / priorPrice,
)}
%
</span>
</ChangeValue>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@use "@pythnetwork/component-library/theme";

.changePercent {
.changeValue {
transition: color 100ms linear;
display: flex;
flex-flow: row nowrap;
Expand Down
46 changes: 46 additions & 0 deletions apps/insights/src/components/ChangeValue/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { CaretUp } from "@phosphor-icons/react/dist/ssr/CaretUp";
import { Skeleton } from "@pythnetwork/component-library/Skeleton";
import clsx from "clsx";
import type { ComponentProps } from "react";

import styles from "./index.module.scss";

const SKELETON_WIDTH = 15;

type OwnProps =
| { isLoading: true; skeletonWidth?: number | undefined }
| {
isLoading?: false;
direction: "up" | "down" | "flat";
};

type Props = Omit<ComponentProps<"span">, keyof OwnProps> & OwnProps;

export const ChangeValue = ({ className, children, ...props }: Props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { isLoading, ...propsWithoutIsLoading } = props;
return (
<span
className={clsx(styles.changeValue, className)}
{...(!props.isLoading && { "data-direction": props.direction })}
{...propsWithoutIsLoading}
>
<Contents {...props}>{children}</Contents>
</span>
);
};

const Contents = (props: Props) => {
if (props.isLoading) {
return <Skeleton width={props.skeletonWidth ?? SKELETON_WIDTH} />;
} else if (props.direction === "flat") {
return "-";
} else {
return (
<>
<CaretUp weight="fill" className={styles.caret} />
{props.children}
</>
);
}
};
14 changes: 5 additions & 9 deletions apps/insights/src/components/CopyButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,23 @@ import {
type Props as ButtonProps,
Button,
} from "@pythnetwork/component-library/Button";
import type { Button as UnstyledButton } from "@pythnetwork/component-library/unstyled/Button";
import clsx from "clsx";
import { type ElementType, useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";

import styles from "./index.module.scss";

type OwnProps = {
text: string;
};

type Props<T extends ElementType> = Omit<
ButtonProps<T>,
type Props = Omit<
ButtonProps<typeof UnstyledButton>,
keyof OwnProps | "onPress" | "afterIcon"
> &
OwnProps;

export const CopyButton = <T extends ElementType>({
text,
children,
className,
...props
}: Props<T>) => {
export const CopyButton = ({ text, children, className, ...props }: Props) => {
const [isCopied, setIsCopied] = useState(false);
const logger = useLogger();
const copy = useCallback(() => {
Expand Down
13 changes: 13 additions & 0 deletions apps/insights/src/components/FormattedDate/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"use client";

import { useMemo } from "react";
import { useDateFormatter } from "react-aria";

type Props = Parameters<typeof useDateFormatter>[0] & {
value: Date;
};

export const FormattedDate = ({ value, ...args }: Props) => {
const numberFormatter = useDateFormatter(args);
return useMemo(() => numberFormatter.format(value), [numberFormatter, value]);
};
34 changes: 34 additions & 0 deletions apps/insights/src/components/Meter/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@use "@pythnetwork/component-library/theme";

.meter {
display: flex;
flex-flow: column nowrap;
gap: theme.spacing(2);

.labels {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;

@include theme.text("base", "medium");
}

.score {
height: theme.spacing(3);
width: 100%;
border-radius: theme.border-radius("full");
position: relative;
display: inline-block;
background-color: theme.color("button", "outline", "background", "hover");

.fill {
position: absolute;
top: 0;
bottom: 0;
left: 0;
border-radius: theme.border-radius("full");
background: theme.color("chart", "series", "primary");
}
}
}
35 changes: 35 additions & 0 deletions apps/insights/src/components/Meter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { Meter as MeterComponent } from "@pythnetwork/component-library/unstyled/Meter";
import type { ComponentProps, ReactNode } from "react";

import styles from "./index.module.scss";

type OwnProps = {
label: string;
startLabel?: ReactNode | undefined;
endLabel?: ReactNode | undefined;
};
type Props = Omit<ComponentProps<typeof MeterComponent>, keyof OwnProps> &
OwnProps;

export const Meter = ({ label, startLabel, endLabel, ...props }: Props) => (
<MeterComponent aria-label={label} {...props}>
{({ percentage }) => (
<div className={styles.meter}>
{(startLabel !== undefined || endLabel !== undefined) && (
<div className={styles.labels}>
{startLabel ?? <div />}
{endLabel ?? <div />}
</div>
)}
<div className={styles.score}>
<div
className={styles.fill}
style={{ width: `${percentage.toString()}%` }}
/>
</div>
</div>
)}
</MeterComponent>
);
Loading
Loading