"use client";
import { ChartLine } from "@phosphor-icons/react/dist/ssr/ChartLine";
import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import { Card } from "@pythnetwork/component-library/Card";
import { Paginator } from "@pythnetwork/component-library/Paginator";
import { SearchInput } from "@pythnetwork/component-library/SearchInput";
import { Select } from "@pythnetwork/component-library/Select";
import {
type RowConfig,
type SortDescriptor,
Table,
} from "@pythnetwork/component-library/Table";
import { useQueryState, parseAsString } from "nuqs";
import { Suspense, useCallback, useMemo } from "react";
import { useFilter, useCollator } from "react-aria";
import { usePriceFeeds } from "../../hooks/use-price-feeds";
import { useQueryParamFilterPagination } from "../../hooks/use-query-param-filter-pagination";
import { AssetClassTag } from "../AssetClassTag";
import { FeedKey } from "../FeedKey";
import {
SKELETON_WIDTH,
LivePrice,
LiveConfidence,
LiveValue,
} from "../LivePrices";
import { NoResults } from "../NoResults";
import { PriceFeedTag } from "../PriceFeedTag";
import rootStyles from "../Root/index.module.scss";
type Props = {
id: string;
priceFeeds: PriceFeed[];
};
type PriceFeed = {
symbol: string;
exponent: number;
numQuoters: number;
};
export const PriceFeedsCard = ({ priceFeeds, ...props }: Props) => (
}>
);
const ResolvedPriceFeedsCard = ({ priceFeeds, ...props }: Props) => {
const logger = useLogger();
const collator = useCollator();
const filter = useFilter({ sensitivity: "base", usage: "search" });
const [assetClass, setAssetClass] = useQueryState(
"assetClass",
parseAsString.withDefault(""),
);
const feeds = usePriceFeeds();
const priceFeedsWithContextInfo = useMemo(
() =>
priceFeeds.map((feed) => {
const contextFeed = feeds.get(feed.symbol);
if (contextFeed) {
return {
...feed,
assetClass: contextFeed.assetClass,
displaySymbol: contextFeed.displaySymbol,
key: contextFeed.key,
};
} else {
throw new NoSuchFeedError(feed.symbol);
}
}),
[feeds, priceFeeds],
);
const feedsFilteredByAssetClass = useMemo(
() =>
assetClass
? priceFeedsWithContextInfo.filter(
(feed) => feed.assetClass === assetClass,
)
: priceFeedsWithContextInfo,
[assetClass, priceFeedsWithContextInfo],
);
const {
search,
sortDescriptor,
page,
pageSize,
updateSearch,
updateSortDescriptor,
updatePage,
updatePageSize,
paginatedItems,
numResults,
numPages,
mkPageLink,
} = useQueryParamFilterPagination(
feedsFilteredByAssetClass,
(priceFeed, search) => {
const searchTokens = search
.split(" ")
.flatMap((item) => item.split(","))
.filter(Boolean);
return searchTokens.some((token) =>
filter.contains(priceFeed.displaySymbol, token),
);
},
(a, b, { column, direction }) => {
const field = column === "assetClass" ? "assetClass" : "displaySymbol";
return (
(direction === "descending" ? -1 : 1) *
collator.compare(a[field], b[field])
);
},
{ defaultSort: "priceFeedName" },
);
const rows = useMemo(
() =>
paginatedItems.map(({ symbol, exponent, numQuoters, key }) => ({
id: symbol,
href: `/price-feeds/${encodeURIComponent(symbol)}`,
data: {
exponent: (
),
numPublishers: (
),
price: ,
confidenceInterval: ,
priceFeedName: ,
assetClass: ,
priceFeedId: ,
},
})),
[paginatedItems],
);
const updateAssetClass = useCallback(
(newAssetClass: string) => {
updatePage(1);
setAssetClass(newAssetClass).catch((error: unknown) => {
logger.error("Failed to update asset class", error);
});
},
[updatePage, setAssetClass, logger],
);
const assetClasses = useMemo(
() =>
[
...new Set(priceFeedsWithContextInfo.map((feed) => feed.assetClass)),
].sort((a, b) => collator.compare(a, b)),
[priceFeedsWithContextInfo, collator],
);
return (
);
};
type PriceFeedsCardContents = Pick &
(
| { isLoading: true }
| {
isLoading?: false;
numResults: number;
search: string;
sortDescriptor: SortDescriptor;
onSortChange: (newSort: SortDescriptor) => void;
assetClass: string;
assetClasses: string[];
numPages: number;
page: number;
pageSize: number;
onSearchChange: (newSearch: string) => void;
onAssetClassChange: (newAssetClass: string) => void;
onPageSizeChange: (newPageSize: number) => void;
onPageChange: (newPage: number) => void;
mkPageLink: (page: number) => string;
rows: RowConfig<
| "priceFeedName"
| "assetClass"
| "priceFeedId"
| "price"
| "confidenceInterval"
| "exponent"
| "numPublishers"
>[];
}
);
const PriceFeedsCardContents = ({ id, ...props }: PriceFeedsCardContents) => (
}
title={
<>
Price Feeds
{!props.isLoading && (
{props.numResults}
)}
>
}
toolbar={
<>