From 597defb3614937fd68be8fb6df8c1e8a6b002434 Mon Sep 17 00:00:00 2001 From: devin distefano Date: Wed, 28 Oct 2026 12:48:08 -0500 Subject: [PATCH 1/2] 24/7 streams - table integration --- src/features/feeds/components/FeedList.tsx | 262 ++++++++++++------ src/features/feeds/components/Tables.tsx | 38 ++- src/features/feeds/types.ts | 5 + .../feeds/utils/feedVisibility.test.ts | 147 ++++++++++ src/features/feeds/utils/feedVisibility.ts | 15 +- src/features/feeds/utils/tableFilters.ts | 13 +- src/features/feeds/utils/twentyFourSeven.ts | 57 ++++ 7 files changed, 438 insertions(+), 99 deletions(-) create mode 100644 src/features/feeds/utils/feedVisibility.test.ts create mode 100644 src/features/feeds/utils/twentyFourSeven.ts diff --git a/src/features/feeds/components/FeedList.tsx b/src/features/feeds/components/FeedList.tsx index 540af4cf768..72aef22cbde 100644 --- a/src/features/feeds/components/FeedList.tsx +++ b/src/features/feeds/components/FeedList.tsx @@ -36,9 +36,11 @@ import { import { getAddrPerPage, SMART_DATA_CATEGORY_OPTIONS } from "../constants.ts" import { type DataFeedType, + type MarketHoursFilterValue, type SchemaFilterValue, type StreamsRwaFeedTypeValue, type TradingHoursFilterValue, + type TwentyFourSevenFilterValue, getFeedTypeFlags, } from "../types.ts" import { updateUrlClean } from "./urlStateHelpers.ts" @@ -83,6 +85,18 @@ const feedTypeFilterOptions: FilterOption[] = [ { label: "Futures Streams", value: "futures" }, ] +const marketHoursFilterOptions: FilterOption[] = [ + { label: "All", value: "all" }, + { label: "24/5", value: "24x5" }, + { label: "24/7", value: "24x7" }, +] + +const twentyFourSevenFilterOptions: FilterOption[] = [ + { label: "All 24/7 Streams", value: "all" }, + { label: "24/7 TWAP", value: "twap" }, + { label: "24/7 Perpetual Markets", value: "perps" }, +] + const tradingHoursFilterOptions: FilterOption[] = [ { label: "All Time Segments", value: "all" }, { label: "Regular Hours", value: "regular" }, @@ -102,6 +116,10 @@ const isStreamsRwaFeedTypeValue = (value: unknown): value is StreamsRwaFeedTypeV value === "all" || value === "datalink" || value === "equities" || value === "forex" || value === "futures" const isTradingHoursFilterValue = (value: unknown): value is TradingHoursFilterValue => value === "all" || value === "regular" || value === "extended" || value === "overnight" +const isMarketHoursFilterValue = (value: unknown): value is MarketHoursFilterValue => + value === "all" || value === "24x5" || value === "24x7" +const isTwentyFourSevenFilterValue = (value: unknown): value is TwentyFourSevenFilterValue => + value === "all" || value === "twap" || value === "perps" const FilterDropdown = ({ label, @@ -368,15 +386,30 @@ export const FeedList = ({ const [svrTypeFilters, setSvrTypeFilters] = useState>(new Set()) const [showOnlyDatalinkFeeds, setShowOnlyDatalinkFeeds] = useState(false) const [showOnlyDatalinkFeedsTestnet, setShowOnlyDatalinkFeedsTestnet] = useState(false) + // `show24x5` is the legacy URL param kept for existing links; new selections use `marketHours`. const [show24x5FeedsParam, setShow24x5FeedsParam] = useQueryString("show24x5") - const show24x5Feeds = force24x5Only || show24x5FeedsParam === "true" + const [marketHoursFilterParam, setMarketHoursFilterParam] = useQueryString("marketHours") + const marketHoursFilter: MarketHoursFilterValue = force24x5Only + ? "24x5" + : isMarketHoursFilterValue(marketHoursFilterParam) + ? marketHoursFilterParam + : show24x5FeedsParam === "true" + ? "24x5" + : "all" + const show24x5Feeds = marketHoursFilter === "24x5" + const show247Streams = marketHoursFilter === "24x7" const showApacEquitiesFeeds = forceApacEquitiesOnly const showFuturesFeeds = forceFuturesOnly - const setShow24x5Feeds = (value: boolean) => { - if (!force24x5Only) { - setShow24x5FeedsParam(value ? "true" : []) - } + + const [twentyFourSevenFilterParam, setTwentyFourSevenFilterParam] = useQueryString("247Category") + const twentyFourSevenFilter: TwentyFourSevenFilterValue = + typeof twentyFourSevenFilterParam === "string" && isTwentyFourSevenFilterValue(twentyFourSevenFilterParam) + ? twentyFourSevenFilterParam + : "all" + const setTwentyFourSevenFilter = (next: TwentyFourSevenFilterValue) => { + setTwentyFourSevenFilterParam(next === "all" ? [] : next) } + const [tradingHoursFilterParam, setTradingHoursFilterParam] = useQueryString("tradingHours") const tradingHoursFilter = typeof tradingHoursFilterParam === "string" && isTradingHoursFilterValue(tradingHoursFilterParam) @@ -385,6 +418,14 @@ export const FeedList = ({ const setTradingHoursFilter = (next: TradingHoursFilterValue) => { setTradingHoursFilterParam(next === "all" ? [] : next) } + + const setMarketHoursFilter = (next: MarketHoursFilterValue) => { + if (force24x5Only) return + setMarketHoursFilterParam(next === "all" ? [] : next) + setShow24x5FeedsParam([]) + if (next !== "24x5") setTradingHoursFilter("all") + if (next !== "24x7") setTwentyFourSevenFilter("all") + } const [rwaSchemaFilterParam, setRwaSchemaFilterParam] = useQueryString("schema") const rwaSchemaFilter = typeof rwaSchemaFilterParam === "string" && isSchemaFilterValue(rwaSchemaFilterParam) ? rwaSchemaFilterParam : "all" @@ -415,15 +456,31 @@ export const FeedList = ({ const setTestnetCryptoSchemaFilter = (next: SchemaFilterValue) => { setTestnetCryptoSchemaFilterParam(next === "all" ? [] : next) } + // `testnetShow24x5` is the legacy URL param kept for existing links. const [show24x5FeedsTestnetParam, setShow24x5FeedsTestnetParam] = useQueryString("testnetShow24x5") - const show24x5FeedsTestnet = force24x5Only || show24x5FeedsTestnetParam === "true" + const [testnetMarketHoursFilterParam, setTestnetMarketHoursFilterParam] = useQueryString("testnetMarketHours") + const testnetMarketHoursFilter: MarketHoursFilterValue = force24x5Only + ? "24x5" + : isMarketHoursFilterValue(testnetMarketHoursFilterParam) + ? testnetMarketHoursFilterParam + : show24x5FeedsTestnetParam === "true" + ? "24x5" + : "all" + const show24x5FeedsTestnet = testnetMarketHoursFilter === "24x5" + const show247StreamsTestnet = testnetMarketHoursFilter === "24x7" const showApacEquitiesFeedsTestnet = forceApacEquitiesOnly const showFuturesFeedsTestnet = forceFuturesOnly - const setShow24x5FeedsTestnet = (value: boolean) => { - if (!force24x5Only) { - setShow24x5FeedsTestnetParam(value ? "true" : []) - } + + const [testnetTwentyFourSevenFilterParam, setTestnetTwentyFourSevenFilterParam] = useQueryString("testnet247Category") + const testnetTwentyFourSevenFilter: TwentyFourSevenFilterValue = + typeof testnetTwentyFourSevenFilterParam === "string" && + isTwentyFourSevenFilterValue(testnetTwentyFourSevenFilterParam) + ? testnetTwentyFourSevenFilterParam + : "all" + const setTestnetTwentyFourSevenFilter = (next: TwentyFourSevenFilterValue) => { + setTestnetTwentyFourSevenFilterParam(next === "all" ? [] : next) } + const [testnetTradingHoursFilterParam, setTestnetTradingHoursFilterParam] = useQueryString("testnetTradingHours") const testnetTradingHoursFilter = typeof testnetTradingHoursFilterParam === "string" && isTradingHoursFilterValue(testnetTradingHoursFilterParam) @@ -432,6 +489,14 @@ export const FeedList = ({ const setTestnetTradingHoursFilter = (next: TradingHoursFilterValue) => { setTestnetTradingHoursFilterParam(next === "all" ? [] : next) } + + const setTestnetMarketHoursFilter = (next: MarketHoursFilterValue) => { + if (force24x5Only) return + setTestnetMarketHoursFilterParam(next === "all" ? [] : next) + setShow24x5FeedsTestnetParam([]) + if (next !== "24x5") setTestnetTradingHoursFilter("all") + if (next !== "24x7") setTestnetTwentyFourSevenFilter("all") + } const [openDropdownId, setOpenDropdownId] = useState(null) const handleDropdownToggle = (dropdownId: string, isOpen: boolean) => { setOpenDropdownId((current) => { @@ -1368,58 +1433,54 @@ export const FeedList = ({ )} {dataFeedType === "streamsRwa" && ( <> - {!forceStreamCategoryFilter && !show24x5Feeds && !forceApacEquitiesOnly && !forceFuturesOnly && ( - <> - handleDropdownToggle("main-schema", isOpen)} - onClose={closeAllDropdowns} - label="Filter schema" - options={schemaFilterOptions} - value={rwaSchemaFilter} - groupId="schema-main" - onSelect={(next) => { - setRwaSchemaFilter(next) - setCurrentPage("1") - }} - /> - handleDropdownToggle("main-feed-type", isOpen)} - onClose={closeAllDropdowns} - label="Filter category" - options={feedTypeFilterOptions} - value={streamCategoryFilter} - groupId="feed-type-main" - onSelect={(next) => { - setStreamCategoryFilter(next) - setCurrentPage("1") - }} - /> - - )} {!force24x5Only && !forceApacEquitiesOnly && !forceFuturesOnly && !forceStreamCategoryFilter && ( -
- -
- )} + handleDropdownToggle("main-feed-type", isOpen)} + onClose={closeAllDropdowns} + label="Filter category" + options={feedTypeFilterOptions} + value={streamCategoryFilter} + groupId="feed-type-main" + onSelect={(next) => { + setStreamCategoryFilter(next) + setCurrentPage("1") + }} + /> + + )} {show24x5Feeds && ( )} + {show247Streams && ( + handleDropdownToggle("main-247-category", isOpen)} + onClose={closeAllDropdowns} + label="Filter 24/7 product" + options={twentyFourSevenFilterOptions} + value={twentyFourSevenFilter} + groupId="247-category-main" + onSelect={(next) => { + setTwentyFourSevenFilter(next) + setCurrentPage("1") + }} + /> + )} {(searchValue || rwaSchemaFilter !== "all" || cryptoSchemaFilter !== "all" || (!forceStreamCategoryFilter && streamCategoryFilter !== "all") || - show24x5Feeds) && ( + marketHoursFilter !== "all") && (