diff --git a/apps/insights/src/components/PriceFeed/price-feed-select.tsx b/apps/insights/src/components/PriceFeed/price-feed-select.tsx index 6b4c4025a1..3234e06d37 100644 --- a/apps/insights/src/components/PriceFeed/price-feed-select.tsx +++ b/apps/insights/src/components/PriceFeed/price-feed-select.tsx @@ -36,15 +36,15 @@ export const PriceFeedSelect = ({ children }: Props) => { const filteredFeeds = useMemo( () => search === "" - ? feeds.entries() - : feeds - .entries() - .filter( - ([, { displaySymbol, assetClass, key }]) => - filter.contains(displaySymbol, search) || - filter.contains(assetClass, search) || - filter.contains(key[Cluster.Pythnet], search), - ), + ? // This is inefficient but Safari doesn't support `Iterator.filter`, see + // https://bugs.webkit.org/show_bug.cgi?id=248650 + [...feeds.entries()] + : [...feeds.entries()].filter( + ([, { displaySymbol, assetClass, key }]) => + filter.contains(displaySymbol, search) || + filter.contains(assetClass, search) || + filter.contains(key[Cluster.Pythnet], search), + ), [feeds, search, filter], ); const sortedFeeds = useMemo( diff --git a/apps/insights/src/components/Root/index.tsx b/apps/insights/src/components/Root/index.tsx index cc44846ed1..0bac83c8f5 100644 --- a/apps/insights/src/components/Root/index.tsx +++ b/apps/insights/src/components/Root/index.tsx @@ -59,7 +59,7 @@ const getPublishersForSearchDialog = async (cluster: Cluster) => { const knownPublisher = lookupPublisher(publisher.key); return { - id: publisher.key, + publisherKey: publisher.key, averageScore: publisher.averageScore, cluster, ...(knownPublisher && { diff --git a/apps/insights/src/components/Root/search-dialog.tsx b/apps/insights/src/components/Root/search-dialog.tsx index e9876df1b9..a707e8be87 100644 --- a/apps/insights/src/components/Root/search-dialog.tsx +++ b/apps/insights/src/components/Root/search-dialog.tsx @@ -14,6 +14,7 @@ import { ListBox, ListBoxItem, } from "@pythnetwork/component-library/unstyled/ListBox"; +import { useRouter } from "next/navigation"; import { type ReactNode, useState, @@ -23,7 +24,7 @@ import { use, useMemo, } from "react"; -import { useCollator, useFilter } from "react-aria"; +import { RouterProvider, useCollator, useFilter } from "react-aria"; import styles from "./search-dialog.module.scss"; import { usePriceFeeds } from "../../hooks/use-price-feeds"; @@ -46,7 +47,7 @@ const SearchDialogOpenContext = createContext< type Props = { children: ReactNode; publishers: ({ - id: string; + publisherKey: string; averageScore: number; cluster: Cluster; } & ( @@ -63,54 +64,85 @@ export const SearchDialogProvider = ({ children, publishers }: Props) => { const filter = useFilter({ sensitivity: "base", usage: "search" }); const feeds = usePriceFeeds(); - const close = useCallback(() => { - searchDialogState.close(); - setTimeout(() => { - setSearch(""); - setType(""); - }, CLOSE_DURATION_IN_MS); - }, [searchDialogState, setSearch, setType]); + const close = useCallback( + () => + new Promise((resolve) => { + searchDialogState.close(); + setTimeout(() => { + setSearch(""); + setType(""); + resolve(); + }, CLOSE_DURATION_IN_MS); + }), + [searchDialogState, setSearch, setType], + ); const handleOpenChange = useCallback( (isOpen: boolean) => { if (!isOpen) { - close(); + close().catch(() => { + /* no-op since this actually can't fail */ + }); } }, [close], ); + const router = useRouter(); + const handleOpenItem = useCallback( + (href: string) => { + close() + .then(() => { + router.push(href); + }) + .catch(() => { + /* no-op since this actually can't fail */ + }); + }, + [close, router], + ); + const results = useMemo( () => [ ...(type === ResultType.Publisher ? [] - : feeds - .entries() + : // This is inefficient but Safari doesn't support `Iterator.filter`, + // see https://bugs.webkit.org/show_bug.cgi?id=248650 + [...feeds.entries()] .filter(([, { displaySymbol }]) => filter.contains(displaySymbol, search), ) - .map(([symbol, feed]) => ({ + .map(([symbol, { assetClass, displaySymbol }]) => ({ type: ResultType.PriceFeed as const, id: symbol, - ...feed, + assetClass, + displaySymbol, }))), ...(type === ResultType.PriceFeed ? [] : publishers .filter( (publisher) => - filter.contains(publisher.id, search) || + filter.contains(publisher.publisherKey, search) || (publisher.name && filter.contains(publisher.name, search)), ) .map((publisher) => ({ type: ResultType.Publisher as const, + id: [ + ClusterToName[publisher.cluster], + publisher.publisherKey, + ].join(":"), ...publisher, }))), ].sort((a, b) => collator.compare( - a.type === ResultType.PriceFeed ? a.displaySymbol : (a.name ?? a.id), - b.type === ResultType.PriceFeed ? b.displaySymbol : (b.name ?? b.id), + a.type === ResultType.PriceFeed + ? a.displaySymbol + : (a.name ?? a.publisherKey), + b.type === ResultType.PriceFeed + ? b.displaySymbol + : (b.name ?? b.publisherKey), ), ), [feeds, publishers, collator, filter, search, type], @@ -182,80 +214,87 @@ export const SearchDialogProvider = ({ children, publishers }: Props) => {
- - { - setSearch(""); - }} - /> - } - > - {(result) => ( - -
- - {result.type === ResultType.PriceFeed - ? "PRICE FEED" - : "PUBLISHER"} - -
- {result.type === ResultType.PriceFeed ? ( - <> - - - - ) : ( - <> - - - - )} -
- )} -
-
+ + + { + setSearch(""); + }} + /> + } + > + {(result) => ( + +
+ + {result.type === ResultType.PriceFeed + ? "PRICE FEED" + : "PUBLISHER"} + +
+ {result.type === ResultType.PriceFeed ? ( + <> + + + + ) : ( + <> + + + + )} +
+ )} +
+
+