@agg-build/ui 4.4.0 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5653,6 +5653,19 @@ var resolveCategoryIdsFromPath = (path, fallbackCategoryIds) => {
5653
5653
  if (deepest) return [deepest];
5654
5654
  return fallbackCategoryIds;
5655
5655
  };
5656
+ var validateSubcategoryPath = (requestedPath, rootCategoryId, childrenByParentId) => {
5657
+ var _a;
5658
+ if (!rootCategoryId) return [];
5659
+ const validPath = [];
5660
+ let parentId = rootCategoryId;
5661
+ for (const categoryId of requestedPath) {
5662
+ const category = (_a = childrenByParentId.get(parentId)) == null ? void 0 : _a.find((child) => child.id === categoryId);
5663
+ if (!category) break;
5664
+ validPath.push(category.id);
5665
+ parentId = category.id;
5666
+ }
5667
+ return validPath;
5668
+ };
5656
5669
  var resolveTabVenus = (tab, visibleVenues) => {
5657
5670
  const applyAllowList = (venues) => {
5658
5671
  if (!venues) {
@@ -18460,6 +18473,13 @@ var import_react30 = require("react");
18460
18473
  // src/events/list/event-list.constants.ts
18461
18474
  var import_sdk16 = require("@agg-build/sdk");
18462
18475
  var DEFAULT_EVENTS_LIMIT = 50;
18476
+ var DEFAULT_EVENT_LIST_SORT = "volume24hr";
18477
+ var EVENT_LIST_SORT_VALUES = [
18478
+ "volume24hr",
18479
+ "topArbitrage",
18480
+ "volume",
18481
+ "endDate"
18482
+ ];
18463
18483
  var getDefaultEventListTabs = (labels) => {
18464
18484
  return [
18465
18485
  {
@@ -18529,6 +18549,7 @@ var getDefaultEventListTabs = (labels) => {
18529
18549
 
18530
18550
  // src/events/list/event-list-tabs.tsx
18531
18551
  var import_jsx_runtime155 = require("react/jsx-runtime");
18552
+ var DEFAULT_HIDDEN_VENUES = new Set((0, import_sdk17.getEffectiveDisabledVenues)(void 0));
18532
18553
  var renderTabIcon = (tab, isActive) => {
18533
18554
  if (tab.iconName === "warning-filled") {
18534
18555
  return /* @__PURE__ */ (0, import_jsx_runtime155.jsx)(Icon, { name: "warning-filled", size: "small", color: "currentColor" });
@@ -18557,15 +18578,11 @@ var useEventListTabs = (tabs) => {
18557
18578
  const availabilityState = (0, import_hooks55.getVenueAvailabilityState)({
18558
18579
  venueId: tab.venueLogo,
18559
18580
  disabledVenues,
18560
- // A compile-time-inactive venue (novig today) is hidden by default and
18561
- // un-hidden per-environment by NOVIG_DATA_VISIBLE, so its
18562
- // disabled-ness is only known once `/app/config` resolves — treating
18563
- // it as hidden while that request is in flight stops its tab flashing
18564
- // in and out where the flag is unset. Active venues must NOT be
18565
- // withheld, or they pop in late. Keyed off the shared inactive-venue
18566
- // helper rather than a venue name, so launching or retiring one needs
18567
- // no change here.
18568
- isConfigLoading: isAppConfigLoading && (0, import_sdk17.isVenueInactive)(tab.venueLogo),
18581
+ // A globally inactive or default-disabled venue is hidden until
18582
+ // `/app/config` resolves. That prevents partner-opt-in venues such as
18583
+ // Novig from flashing briefly for apps that still disable them, while
18584
+ // ordinary active venues continue rendering immediately.
18585
+ isConfigLoading: isAppConfigLoading && ((0, import_sdk17.isVenueInactive)(tab.venueLogo) || DEFAULT_HIDDEN_VENUES.has(tab.venueLogo)),
18569
18586
  isLocationBlocked
18570
18587
  });
18571
18588
  if (availabilityState === "hidden") return [];
@@ -18743,10 +18760,16 @@ var EventList = ({
18743
18760
  getMarketHref,
18744
18761
  getEventHref,
18745
18762
  initialVenueTab,
18763
+ activeVenueTab: controlledActiveVenueTab,
18764
+ onActiveVenueTabChange,
18765
+ selectedSort: controlledSelectedSort,
18766
+ onSelectedSortChange,
18767
+ categoryPath: controlledCategoryPath,
18768
+ onCategoryPathChange,
18746
18769
  initialLoadedPageCount,
18747
18770
  stateRef
18748
18771
  }) => {
18749
- var _a, _b, _c, _d, _e;
18772
+ var _a, _b, _c, _d, _e, _f;
18750
18773
  const labels = (0, import_hooks56.useLabels)();
18751
18774
  const { disabledVenues } = (0, import_hooks56.useAppConfig)();
18752
18775
  const {
@@ -18758,13 +18781,37 @@ var EventList = ({
18758
18781
  () => disabledVenues.length === 0 ? void 0 : (0, import_hooks56.getVisibleVenueIdsByConfig)(import_sdk18.VENUES, disabledVenues),
18759
18782
  [disabledVenues]
18760
18783
  );
18761
- const [activeTabValue, setActiveTabValue] = (0, import_react31.useState)(
18784
+ const [internalActiveTabValue, setInternalActiveTabValue] = (0, import_react31.useState)(
18762
18785
  (_b = initialVenueTab != null ? initialVenueTab : (_a = resolvedTabs[0]) == null ? void 0 : _a.value) != null ? _b : "matched"
18763
18786
  );
18764
- const [selectedSort, setSelectedSort] = (0, import_react31.useState)("volume24hr");
18787
+ const activeTabValue = controlledActiveVenueTab != null ? controlledActiveVenueTab : internalActiveTabValue;
18788
+ const [internalSelectedSort, setInternalSelectedSort] = (0, import_react31.useState)(DEFAULT_EVENT_LIST_SORT);
18789
+ const selectedSort = controlledSelectedSort != null ? controlledSelectedSort : internalSelectedSort;
18765
18790
  const [isSubcategoriesModalOpen, setIsSubcategoriesModalOpen] = (0, import_react31.useState)(false);
18766
- const [categoryPath, setCategoryPath] = (0, import_react31.useState)([]);
18791
+ const [internalCategoryPath, setInternalCategoryPath] = (0, import_react31.useState)([]);
18792
+ const categoryPath = controlledCategoryPath != null ? controlledCategoryPath : internalCategoryPath;
18767
18793
  const [collapsedCategoryIds, setCollapsedCategoryIds] = (0, import_react31.useState)(() => /* @__PURE__ */ new Set());
18794
+ const handleActiveTabChange = (0, import_react31.useCallback)(
18795
+ (value) => {
18796
+ if (controlledActiveVenueTab == null) setInternalActiveTabValue(value);
18797
+ onActiveVenueTabChange == null ? void 0 : onActiveVenueTabChange(value, { history: "push" });
18798
+ },
18799
+ [controlledActiveVenueTab, onActiveVenueTabChange]
18800
+ );
18801
+ const handleSelectedSortChange = (0, import_react31.useCallback)(
18802
+ (value) => {
18803
+ if (controlledSelectedSort == null) setInternalSelectedSort(value);
18804
+ onSelectedSortChange == null ? void 0 : onSelectedSortChange(value, { history: "push" });
18805
+ },
18806
+ [controlledSelectedSort, onSelectedSortChange]
18807
+ );
18808
+ const handleCategoryPathChange = (0, import_react31.useCallback)(
18809
+ (value, options) => {
18810
+ if (controlledCategoryPath == null) setInternalCategoryPath(value);
18811
+ onCategoryPathChange == null ? void 0 : onCategoryPathChange(value, options);
18812
+ },
18813
+ [controlledCategoryPath, onCategoryPathChange]
18814
+ );
18768
18815
  const sortItems = (0, import_react31.useMemo)(() => {
18769
18816
  return [
18770
18817
  {
@@ -18797,9 +18844,9 @@ var EventList = ({
18797
18844
  const baseCategoryId = (_c = categoryIds == null ? void 0 : categoryIds[0]) != null ? _c : null;
18798
18845
  (0, import_react31.useEffect)(() => {
18799
18846
  if (!isDiscoveryFiltersEnabled) return;
18800
- setCategoryPath([]);
18847
+ if (controlledCategoryPath == null) setInternalCategoryPath([]);
18801
18848
  setCollapsedCategoryIds(/* @__PURE__ */ new Set());
18802
- }, [baseCategoryId, isDiscoveryFiltersEnabled]);
18849
+ }, [baseCategoryId, controlledCategoryPath, isDiscoveryFiltersEnabled]);
18803
18850
  const { categories: topLevelCategories } = (0, import_hooks56.useCategories)({
18804
18851
  queryKeyScope: "event-list-top-categories",
18805
18852
  enabled: isDiscoveryFiltersEnabled,
@@ -18811,12 +18858,13 @@ var EventList = ({
18811
18858
  const baseCategoryChildPrefetchIds = (0, import_react31.useMemo)(() => {
18812
18859
  return Array.from(
18813
18860
  /* @__PURE__ */ new Set([
18861
+ ...baseCategoryId ? [baseCategoryId] : [],
18814
18862
  ...sortedTopLevelCategories.map((category) => category.id),
18815
18863
  ...categoryPath.map((category) => category.id)
18816
18864
  ])
18817
18865
  );
18818
- }, [categoryPath, sortedTopLevelCategories]);
18819
- const { childrenByParentId: baseChildrenByParentId } = (0, import_hooks56.useCategoryChildren)({
18866
+ }, [baseCategoryId, categoryPath, sortedTopLevelCategories]);
18867
+ const { childrenByParentId: baseChildrenByParentId, isLoading: isBaseCategoryChildrenLoading } = (0, import_hooks56.useCategoryChildren)({
18820
18868
  queryKeyScope: "event-list-category-children",
18821
18869
  enabled: isDiscoveryFiltersEnabled,
18822
18870
  parentIds: baseCategoryChildPrefetchIds,
@@ -18866,7 +18914,36 @@ var EventList = ({
18866
18914
  ...visibleGrandchildrenByParentId
18867
18915
  ]);
18868
18916
  }, [baseChildrenByParentId, visibleChildrenByParentId, visibleGrandchildrenByParentId]);
18869
- const shouldRenderSubcategoryFilters = isDiscoveryFiltersEnabled && baseCategoryId !== null && (((_e = (_d = childrenByParentId.get(baseCategoryId)) == null ? void 0 : _d.length) != null ? _e : 0) > 0 || categoryPath.length > 0);
18917
+ const controlledCategoryPathKey = (_d = controlledCategoryPath == null ? void 0 : controlledCategoryPath.map((entry) => entry.id).join(",")) != null ? _d : "";
18918
+ const controlledCategoryPathValidationKey = `${baseCategoryId != null ? baseCategoryId : ""}|${controlledCategoryPathKey}`;
18919
+ const [validatedCategoryPathKey, setValidatedCategoryPathKey] = (0, import_react31.useState)(
18920
+ () => controlledCategoryPath == null || controlledCategoryPath.length === 0 ? controlledCategoryPathValidationKey : ""
18921
+ );
18922
+ (0, import_react31.useEffect)(() => {
18923
+ if (controlledCategoryPath == null || controlledCategoryPath.length === 0) {
18924
+ setValidatedCategoryPathKey(controlledCategoryPathValidationKey);
18925
+ return;
18926
+ }
18927
+ if (isBaseCategoryChildrenLoading) return;
18928
+ const requestedIds = controlledCategoryPath.map((entry) => entry.id);
18929
+ const validIds = validateSubcategoryPath(requestedIds, baseCategoryId, baseChildrenByParentId);
18930
+ if (validIds.length !== requestedIds.length) {
18931
+ handleCategoryPathChange(controlledCategoryPath.slice(0, validIds.length), {
18932
+ history: "replace"
18933
+ });
18934
+ return;
18935
+ }
18936
+ setValidatedCategoryPathKey(controlledCategoryPathValidationKey);
18937
+ }, [
18938
+ baseCategoryId,
18939
+ baseChildrenByParentId,
18940
+ controlledCategoryPath,
18941
+ controlledCategoryPathKey,
18942
+ controlledCategoryPathValidationKey,
18943
+ handleCategoryPathChange,
18944
+ isBaseCategoryChildrenLoading
18945
+ ]);
18946
+ const shouldRenderSubcategoryFilters = isDiscoveryFiltersEnabled && baseCategoryId !== null && (((_f = (_e = childrenByParentId.get(baseCategoryId)) == null ? void 0 : _e.length) != null ? _f : 0) > 0 || categoryPath.length > 0);
18870
18947
  const baseCategoryEventCount = (0, import_react31.useMemo)(() => {
18871
18948
  var _a2, _b2;
18872
18949
  return (_b2 = (_a2 = sortedTopLevelCategories.find((c) => c.id === baseCategoryId)) == null ? void 0 : _a2.eventCount) != null ? _b2 : 0;
@@ -18924,7 +19001,8 @@ var EventList = ({
18924
19001
  // markets experience is enabled (the sports event-detail view loads the
18925
19002
  // folded companions via aggKey). Gated on the same flag as that view.
18926
19003
  grouped: showSportsMarkets,
18927
- initialPages: initialLoadedPageCount
19004
+ initialPages: initialLoadedPageCount,
19005
+ enabled: controlledCategoryPath == null || validatedCategoryPathKey === controlledCategoryPathValidationKey
18928
19006
  });
18929
19007
  (0, import_react31.useEffect)(() => {
18930
19008
  if (stateRef) {
@@ -18985,7 +19063,7 @@ var EventList = ({
18985
19063
  "grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] md:grid-cols-[repeat(auto-fill,minmax(360px,1fr))] gap-4"
18986
19064
  );
18987
19065
  const handleSelectAllCategories = () => {
18988
- setCategoryPath([]);
19066
+ handleCategoryPathChange([], { history: "push" });
18989
19067
  setCollapsedCategoryIds(/* @__PURE__ */ new Set());
18990
19068
  onCategoryRootChange == null ? void 0 : onCategoryRootChange(baseCategoryId != null ? baseCategoryId : void 0);
18991
19069
  setIsSubcategoriesModalOpen(false);
@@ -18997,7 +19075,7 @@ var EventList = ({
18997
19075
  isExpanded,
18998
19076
  isSelected
18999
19077
  }) => {
19000
- setCategoryPath((prev) => [...prev.slice(0, depth), entry]);
19078
+ handleCategoryPathChange([...categoryPath.slice(0, depth), entry], { history: "push" });
19001
19079
  setCollapsedCategoryIds((prev) => {
19002
19080
  const next = new Set(prev);
19003
19081
  if (hasKnownChildren && isExpanded && isSelected) {
@@ -19037,7 +19115,7 @@ var EventList = ({
19037
19115
  ariaLabel: labels.eventList.tabsAria(title2),
19038
19116
  className: "agg-event-list-tabs min-w-0 flex-1 max-w-[600px]!",
19039
19117
  activeTab: activeTabValue,
19040
- onTabChange: setActiveTabValue,
19118
+ onTabChange: handleActiveTabChange,
19041
19119
  overflowBehavior: "scroll"
19042
19120
  }
19043
19121
  ),
@@ -19047,7 +19125,7 @@ var EventList = ({
19047
19125
  ariaLabel: labels.eventList.sortByLabel,
19048
19126
  className: "min-w-[200px] w-auto! shrink-0 min-h-10.5",
19049
19127
  value: selectedSort,
19050
- onChange: setSelectedSort,
19128
+ onChange: handleSelectedSortChange,
19051
19129
  items: sortItems,
19052
19130
  contentClassName: "py-2",
19053
19131
  triggerClassName: "rounded-agg-lg!"
@@ -19064,7 +19142,7 @@ var EventList = ({
19064
19142
  ariaLabel: labels.eventList.tabsAria(title2),
19065
19143
  className: "agg-event-list-tabs min-w-0 flex-1",
19066
19144
  activeTab: activeTabValue,
19067
- onTabChange: setActiveTabValue,
19145
+ onTabChange: handleActiveTabChange,
19068
19146
  overflowBehavior: "select"
19069
19147
  }
19070
19148
  ),
@@ -19074,7 +19152,7 @@ var EventList = ({
19074
19152
  ariaLabel: labels.eventList.sortByLabel,
19075
19153
  className: "min-w-0 flex-1 md:min-w-[144px]",
19076
19154
  value: selectedSort,
19077
- onChange: setSelectedSort,
19155
+ onChange: handleSelectedSortChange,
19078
19156
  items: sortItems,
19079
19157
  contentClassName: "rounded-[12px] py-2 shadow-[0_8px_16px_rgba(0,0,0,0.1)]"
19080
19158
  }
@@ -20750,7 +20828,7 @@ var getSmartRouteTotalFees = (feeBreakdown) => {
20750
20828
  if (!feeBreakdown) return null;
20751
20829
  const feeParts = [feeBreakdown.venueFees, feeBreakdown.bridgeFees, feeBreakdown.executionGas];
20752
20830
  if (!feeParts.every(Number.isFinite)) return null;
20753
- return feeParts.reduce((total, fee) => total + fee, 0);
20831
+ return feeParts.reduce((total, fee) => total + Math.round(fee * 100) / 100, 0);
20754
20832
  };
20755
20833
  var getPlaceOrderContainerClassName = () => {
20756
20834
  return PLACE_ORDER_CONTAINER_CLASS_NAME;
@@ -21012,7 +21090,7 @@ var buildLiveRouteCards = ({
21012
21090
  // balance condition, and `handleExecuteQuote` rejects an empty quoteId.
21013
21091
  sq.venue !== primaryWinnerVenue
21014
21092
  ).map((sq) => {
21015
- var _a2;
21093
+ var _a2, _b2;
21016
21094
  const parsedVenue = parseVenue(sq.venue);
21017
21095
  const venue = parsedVenue.success ? parsedVenue.data : void 0;
21018
21096
  if (geoBlockedVenues.has(sq.venue)) {
@@ -21045,7 +21123,12 @@ var buildLiveRouteCards = ({
21045
21123
  estimatedProfit: sq.estimatedProfit,
21046
21124
  returnPct: sq.returnPct,
21047
21125
  // venueSoloQuotes stripped on solo card's quoteData (only the primary needs them)
21048
- venueSoloQuotes: void 0
21126
+ venueSoloQuotes: void 0,
21127
+ // This solo's OWN fee breakdown. Without it the spread above leaves the
21128
+ // primary route's breakdown in place, so selecting a bridge-free venue
21129
+ // still showed the primary's bridging charge. Falls back to the
21130
+ // primary only when the router omitted one (non-"ok" solos).
21131
+ feeBreakdown: (_b2 = sq.feeBreakdown) != null ? _b2 : quoteData.feeBreakdown
21049
21132
  }), sq.quoteId ? { status: "ok", error: void 0 } : null);
21050
21133
  return {
21051
21134
  id: `live-solo-${sq.venue}`,
@@ -29508,6 +29591,85 @@ var resolveInitialTabValue = (tabs, defaultActiveTab) => {
29508
29591
  return (_b = (_a = tabs[0]) == null ? void 0 : _a.value) != null ? _b : ALL_CATEGORIES_TAB_VALUE;
29509
29592
  };
29510
29593
 
29594
+ // src/pages/home/event-discovery-url-state.ts
29595
+ var import_zod2 = require("zod");
29596
+ var nonEmptyParamSchema = import_zod2.z.string().trim().min(1);
29597
+ var sortParamSchema = import_zod2.z.enum(EVENT_LIST_SORT_VALUES);
29598
+ var readScalarParam = (searchParams, name, schema) => {
29599
+ const values = searchParams.getAll(name);
29600
+ if (values.length === 0) return { invalid: false };
29601
+ if (values.length !== 1) return { invalid: true };
29602
+ const parsedValue = schema.safeParse(values[0]);
29603
+ if (!parsedValue.success) return { invalid: true };
29604
+ return { value: parsedValue.data, invalid: false };
29605
+ };
29606
+ var readEventDiscoveryUrlState = (searchParams) => {
29607
+ const category = readScalarParam(searchParams, "category", nonEmptyParamSchema);
29608
+ const venue = readScalarParam(searchParams, "venue", nonEmptyParamSchema);
29609
+ const sort = readScalarParam(searchParams, "sort", sortParamSchema);
29610
+ const subcategoryPath = [];
29611
+ let hasInvalidSubcategory = false;
29612
+ for (const value of searchParams.getAll("subcategory")) {
29613
+ const parsedValue = nonEmptyParamSchema.safeParse(value);
29614
+ if (!parsedValue.success) {
29615
+ hasInvalidSubcategory = true;
29616
+ break;
29617
+ }
29618
+ subcategoryPath.push(parsedValue.data);
29619
+ }
29620
+ return {
29621
+ category: category.value,
29622
+ subcategoryPath,
29623
+ venue: venue.value,
29624
+ sort: sort.value,
29625
+ categoryIsInvalid: category.invalid,
29626
+ shouldCanonicalize: category.invalid || venue.invalid || sort.invalid || hasInvalidSubcategory
29627
+ };
29628
+ };
29629
+ var resolveEventDiscoveryUrlState = ({
29630
+ parsedState,
29631
+ categoryValues,
29632
+ venueValues,
29633
+ defaultCategory,
29634
+ defaultVenue
29635
+ }) => {
29636
+ var _a, _b, _c;
29637
+ const categoryIsValid = parsedState.category == null || categoryValues.includes(parsedState.category);
29638
+ const venueIsValid = parsedState.venue == null || venueValues.includes(parsedState.venue);
29639
+ const category = categoryIsValid ? (_a = parsedState.category) != null ? _a : defaultCategory : defaultCategory;
29640
+ const venue = venueIsValid ? (_b = parsedState.venue) != null ? _b : defaultVenue : defaultVenue;
29641
+ const sort = (_c = parsedState.sort) != null ? _c : DEFAULT_EVENT_LIST_SORT;
29642
+ return {
29643
+ state: {
29644
+ category,
29645
+ subcategoryPath: categoryIsValid && !parsedState.categoryIsInvalid ? parsedState.subcategoryPath : [],
29646
+ venue,
29647
+ sort
29648
+ },
29649
+ shouldCanonicalize: parsedState.shouldCanonicalize || !categoryIsValid || !venueIsValid || parsedState.category === defaultCategory || parsedState.venue === defaultVenue || parsedState.sort === DEFAULT_EVENT_LIST_SORT
29650
+ };
29651
+ };
29652
+ var buildEventDiscoveryUrl = ({
29653
+ currentUrl,
29654
+ state,
29655
+ defaultCategory,
29656
+ defaultVenue
29657
+ }) => {
29658
+ const nextUrl = new URL(currentUrl.toString());
29659
+ nextUrl.searchParams.delete("category");
29660
+ nextUrl.searchParams.delete("subcategory");
29661
+ nextUrl.searchParams.delete("venue");
29662
+ nextUrl.searchParams.delete("sort");
29663
+ if (state.category !== defaultCategory) nextUrl.searchParams.set("category", state.category);
29664
+ for (const subcategory of state.subcategoryPath) {
29665
+ nextUrl.searchParams.append("subcategory", subcategory);
29666
+ }
29667
+ if (state.venue !== defaultVenue) nextUrl.searchParams.set("venue", state.venue);
29668
+ if (state.sort !== DEFAULT_EVENT_LIST_SORT) nextUrl.searchParams.set("sort", state.sort);
29669
+ const search = nextUrl.searchParams.toString();
29670
+ return `${nextUrl.pathname}${search ? `?${search}` : ""}${nextUrl.hash}`;
29671
+ };
29672
+
29511
29673
  // src/pages/home/use-scroll-restoration.ts
29512
29674
  var import_react46 = require("react");
29513
29675
  function useScrollRestoration({
@@ -29787,7 +29949,7 @@ var HomePage = ({
29787
29949
  onError,
29788
29950
  stickyOrderPanel
29789
29951
  }) => {
29790
- var _a, _b, _c, _d;
29952
+ var _a, _b, _c, _d, _e, _f, _g;
29791
29953
  const labels = (0, import_hooks74.useLabels)();
29792
29954
  const {
29793
29955
  search: {
@@ -29797,8 +29959,11 @@ var HomePage = ({
29797
29959
  onChange: onSearchChange,
29798
29960
  onSelect: onSelectEventInternal
29799
29961
  },
29800
- trading: { executionMode }
29962
+ trading: { executionMode },
29963
+ features: { enableEventDiscoveryUrlPersistence }
29801
29964
  } = (0, import_hooks74.useSdkUiConfig)();
29965
+ const { isLoading: isAppConfigLoading } = (0, import_hooks74.useAppConfig)();
29966
+ const primaryEventListTabs = useEventListTabs();
29802
29967
  const { capture, consume } = (0, import_hooks74.useEventListState)();
29803
29968
  const [snapshot] = (0, import_react48.useState)(() => consume());
29804
29969
  const eventListStateRef = (0, import_react48.useRef)(null);
@@ -29869,7 +30034,7 @@ var HomePage = ({
29869
30034
  }, [isShowingAllResults, normalizedSearchValue, selectedEvent]);
29870
30035
  const resolvedSelectedEvent = selectedEvent != null ? selectedEvent : stickySelectedEvent;
29871
30036
  const resolvedAllCategoryTabLabel = allCategoryTabLabel === DEFAULT_ALL_CATEGORY_TAB_LABEL ? labels.home.trending : allCategoryTabLabel;
29872
- const { categories } = (0, import_hooks74.useCategories)({
30037
+ const { categories, isLoading: isCategoriesLoading } = (0, import_hooks74.useCategories)({
29873
30038
  limit: categoriesLimit,
29874
30039
  enabled: !hasCustomTabs
29875
30040
  });
@@ -29880,22 +30045,180 @@ var HomePage = ({
29880
30045
  if (hasCustomTabs && tabs) return tabs;
29881
30046
  return categoryTabs;
29882
30047
  }, [categoryTabs, hasCustomTabs, tabs]);
30048
+ const availableCategoryValues = (0, import_react48.useMemo)(
30049
+ () => resolvedTabs.filter((tab) => !tab.disabled).map((tab) => tab.value),
30050
+ [resolvedTabs]
30051
+ );
30052
+ const resolvedDefaultCategory = resolveInitialTabValue(resolvedTabs, defaultActiveTab);
30053
+ const defaultCategory = availableCategoryValues.includes(resolvedDefaultCategory) ? resolvedDefaultCategory : (_a = availableCategoryValues[0]) != null ? _a : resolvedDefaultCategory;
30054
+ const availableVenueTabs = (0, import_react48.useMemo)(
30055
+ () => primaryEventListTabs.filter((tab) => !tab.disabled && !tab.isComingSoon),
30056
+ [primaryEventListTabs]
30057
+ );
30058
+ const availableVenueValues = (0, import_react48.useMemo)(
30059
+ () => availableVenueTabs.map((tab) => tab.value),
30060
+ [availableVenueTabs]
30061
+ );
30062
+ const defaultVenue = (_c = (_b = availableVenueTabs[0]) == null ? void 0 : _b.value) != null ? _c : "matched";
29883
30063
  const resolvedEventSectionItems = eventSectionItems && eventSectionItems.length > 0 ? eventSectionItems : getDefaultEventSectionItems(labels);
29884
- const [activeTabValue, setActiveTabValue] = (0, import_react48.useState)(() => {
29885
- if (snapshot == null ? void 0 : snapshot.categoryTab) {
29886
- return snapshot.categoryTab;
30064
+ const [discoveryFilterState, setDiscoveryFilterState] = (0, import_react48.useState)(
30065
+ () => {
30066
+ var _a2, _b2;
30067
+ return {
30068
+ category: (_a2 = snapshot == null ? void 0 : snapshot.categoryTab) != null ? _a2 : defaultCategory,
30069
+ venue: (_b2 = snapshot == null ? void 0 : snapshot.venueTab) != null ? _b2 : defaultVenue,
30070
+ sort: DEFAULT_EVENT_LIST_SORT,
30071
+ subcategoryPath: []
30072
+ };
29887
30073
  }
29888
- return resolveInitialTabValue(resolvedTabs, defaultActiveTab);
29889
- });
30074
+ );
30075
+ const discoveryFilterStateRef = (0, import_react48.useRef)(discoveryFilterState);
30076
+ discoveryFilterStateRef.current = discoveryFilterState;
30077
+ const [isUrlStateReady, setIsUrlStateReady] = (0, import_react48.useState)(!enableEventDiscoveryUrlPersistence);
30078
+ const hasInitializedUrlStateRef = (0, import_react48.useRef)(false);
30079
+ const activeTabValue = discoveryFilterState.category;
30080
+ const resolveCurrentUrlState = (0, import_react48.useCallback)(() => {
30081
+ if (typeof window === "undefined") return null;
30082
+ const parsedState = readEventDiscoveryUrlState(new URL(window.location.href).searchParams);
30083
+ const resolvedState = resolveEventDiscoveryUrlState({
30084
+ parsedState,
30085
+ categoryValues: availableCategoryValues,
30086
+ venueValues: availableVenueValues,
30087
+ defaultCategory,
30088
+ defaultVenue
30089
+ });
30090
+ return {
30091
+ state: {
30092
+ category: resolvedState.state.category,
30093
+ venue: resolvedState.state.venue,
30094
+ sort: resolvedState.state.sort,
30095
+ subcategoryPath: resolvedState.state.subcategoryPath.map((id) => ({
30096
+ id,
30097
+ label: id,
30098
+ eventCount: 0
30099
+ }))
30100
+ },
30101
+ shouldCanonicalize: resolvedState.shouldCanonicalize
30102
+ };
30103
+ }, [availableCategoryValues, availableVenueValues, defaultCategory, defaultVenue]);
30104
+ const writeFilterStateToHistory = (0, import_react48.useCallback)(
30105
+ (state, historyMode) => {
30106
+ if (!enableEventDiscoveryUrlPersistence || typeof window === "undefined") return;
30107
+ const currentUrl = new URL(window.location.href);
30108
+ const nextUrl = buildEventDiscoveryUrl({
30109
+ currentUrl,
30110
+ state: {
30111
+ category: state.category,
30112
+ venue: state.venue,
30113
+ sort: state.sort,
30114
+ subcategoryPath: state.subcategoryPath.map((entry) => entry.id)
30115
+ },
30116
+ defaultCategory,
30117
+ defaultVenue
30118
+ });
30119
+ const currentRelativeUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
30120
+ if (nextUrl === currentRelativeUrl) return;
30121
+ const method = historyMode === "push" ? "pushState" : "replaceState";
30122
+ window.history[method](window.history.state, "", nextUrl);
30123
+ },
30124
+ [defaultCategory, defaultVenue, enableEventDiscoveryUrlPersistence]
30125
+ );
30126
+ const commitDiscoveryFilterState = (0, import_react48.useCallback)(
30127
+ (state, historyMode) => {
30128
+ discoveryFilterStateRef.current = state;
30129
+ setDiscoveryFilterState(state);
30130
+ if (isUrlStateReady) writeFilterStateToHistory(state, historyMode);
30131
+ },
30132
+ [isUrlStateReady, writeFilterStateToHistory]
30133
+ );
30134
+ (0, import_react48.useEffect)(() => {
30135
+ if (!enableEventDiscoveryUrlPersistence) {
30136
+ hasInitializedUrlStateRef.current = false;
30137
+ setIsUrlStateReady(true);
30138
+ return;
30139
+ }
30140
+ if (hasInitializedUrlStateRef.current) return;
30141
+ if (!hasCustomTabs && isCategoriesLoading || isAppConfigLoading) return;
30142
+ const resolvedUrlState = resolveCurrentUrlState();
30143
+ if (!resolvedUrlState) return;
30144
+ hasInitializedUrlStateRef.current = true;
30145
+ discoveryFilterStateRef.current = resolvedUrlState.state;
30146
+ setDiscoveryFilterState(resolvedUrlState.state);
30147
+ setIsUrlStateReady(true);
30148
+ const currentUrl = new URL(window.location.href);
30149
+ const canonicalUrl = buildEventDiscoveryUrl({
30150
+ currentUrl,
30151
+ state: {
30152
+ category: resolvedUrlState.state.category,
30153
+ venue: resolvedUrlState.state.venue,
30154
+ sort: resolvedUrlState.state.sort,
30155
+ subcategoryPath: resolvedUrlState.state.subcategoryPath.map((entry) => entry.id)
30156
+ },
30157
+ defaultCategory,
30158
+ defaultVenue
30159
+ });
30160
+ const currentRelativeUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
30161
+ if (resolvedUrlState.shouldCanonicalize || canonicalUrl !== currentRelativeUrl) {
30162
+ window.history.replaceState(window.history.state, "", canonicalUrl);
30163
+ }
30164
+ }, [
30165
+ defaultCategory,
30166
+ defaultVenue,
30167
+ enableEventDiscoveryUrlPersistence,
30168
+ hasCustomTabs,
30169
+ isAppConfigLoading,
30170
+ isCategoriesLoading,
30171
+ resolveCurrentUrlState
30172
+ ]);
30173
+ (0, import_react48.useEffect)(() => {
30174
+ if (!enableEventDiscoveryUrlPersistence || !isUrlStateReady) return;
30175
+ const handlePopState = () => {
30176
+ const resolvedUrlState = resolveCurrentUrlState();
30177
+ if (!resolvedUrlState) return;
30178
+ discoveryFilterStateRef.current = resolvedUrlState.state;
30179
+ setDiscoveryFilterState(resolvedUrlState.state);
30180
+ if (resolvedUrlState.shouldCanonicalize) {
30181
+ writeFilterStateToHistory(resolvedUrlState.state, "replace");
30182
+ }
30183
+ };
30184
+ window.addEventListener("popstate", handlePopState);
30185
+ return () => {
30186
+ window.removeEventListener("popstate", handlePopState);
30187
+ };
30188
+ }, [
30189
+ enableEventDiscoveryUrlPersistence,
30190
+ isUrlStateReady,
30191
+ resolveCurrentUrlState,
30192
+ writeFilterStateToHistory
30193
+ ]);
29890
30194
  const activeTabValueRef = (0, import_react48.useRef)(activeTabValue);
29891
30195
  activeTabValueRef.current = activeTabValue;
29892
30196
  (0, import_react48.useEffect)(() => {
29893
- setActiveTabValue((currentValue) => {
29894
- const hasCurrentValue = resolvedTabs.some((tab) => tab.value === currentValue);
29895
- if (hasCurrentValue) return currentValue;
29896
- return resolveInitialTabValue(resolvedTabs, defaultActiveTab);
30197
+ if (enableEventDiscoveryUrlPersistence && !isUrlStateReady) return;
30198
+ const currentState = discoveryFilterStateRef.current;
30199
+ const categoryIsAvailable = availableCategoryValues.includes(currentState.category);
30200
+ const venueIsAvailable = availableVenueValues.includes(currentState.venue);
30201
+ if (categoryIsAvailable && (!enableEventDiscoveryUrlPersistence || venueIsAvailable)) return;
30202
+ const nextState = __spreadProps(__spreadValues({}, currentState), {
30203
+ category: categoryIsAvailable ? currentState.category : defaultCategory,
30204
+ venue: venueIsAvailable ? currentState.venue : defaultVenue,
30205
+ subcategoryPath: categoryIsAvailable ? currentState.subcategoryPath : []
29897
30206
  });
29898
- }, [defaultActiveTab, resolvedTabs]);
30207
+ if (enableEventDiscoveryUrlPersistence) {
30208
+ commitDiscoveryFilterState(nextState, "replace");
30209
+ return;
30210
+ }
30211
+ discoveryFilterStateRef.current = nextState;
30212
+ setDiscoveryFilterState(nextState);
30213
+ }, [
30214
+ availableCategoryValues,
30215
+ availableVenueValues,
30216
+ commitDiscoveryFilterState,
30217
+ defaultCategory,
30218
+ defaultVenue,
30219
+ enableEventDiscoveryUrlPersistence,
30220
+ isUrlStateReady
30221
+ ]);
29899
30222
  const tabsItems = (0, import_react48.useMemo)(() => {
29900
30223
  return resolvedTabs.map((tab) => {
29901
30224
  const isActive = tab.value === activeTabValue;
@@ -29936,7 +30259,13 @@ var HomePage = ({
29936
30259
  ];
29937
30260
  }, [activeTab, activeTabValue, resolvedEventSectionItems]);
29938
30261
  const handleTabChange = (value) => {
29939
- setActiveTabValue(value);
30262
+ commitDiscoveryFilterState(
30263
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), {
30264
+ category: value,
30265
+ subcategoryPath: []
30266
+ }),
30267
+ "push"
30268
+ );
29940
30269
  _onTabChange == null ? void 0 : _onTabChange(value);
29941
30270
  };
29942
30271
  const handleCategoryRootChange = (categoryId) => {
@@ -29958,10 +30287,10 @@ var HomePage = ({
29958
30287
  handleSelectEvent(event);
29959
30288
  };
29960
30289
  const shouldRenderMobileSearch = isMobileSearchViewport && !resolvedSelectedEvent;
29961
- const isScrollRestorationReady = !!snapshot && !isShowingAllResults && !resolvedSelectedEvent && ((_b = (_a = eventListStateRef.current) == null ? void 0 : _a.loadedPageCount) != null ? _b : 0) >= ((_c = snapshot.loadedPageCount) != null ? _c : 1);
30290
+ const isScrollRestorationReady = !!snapshot && !isShowingAllResults && !resolvedSelectedEvent && ((_e = (_d = eventListStateRef.current) == null ? void 0 : _d.loadedPageCount) != null ? _e : 0) >= ((_f = snapshot.loadedPageCount) != null ? _f : 1);
29962
30291
  useScrollRestoration({
29963
30292
  enabled: !!snapshot && snapshot.scrollY > 0,
29964
- scrollY: (_d = snapshot == null ? void 0 : snapshot.scrollY) != null ? _d : 0,
30293
+ scrollY: (_g = snapshot == null ? void 0 : snapshot.scrollY) != null ? _g : 0,
29965
30294
  isReady: isScrollRestorationReady
29966
30295
  });
29967
30296
  return /* @__PURE__ */ (0, import_jsx_runtime176.jsx)(AggErrorBoundary, { onError, children: /* @__PURE__ */ (0, import_jsx_runtime176.jsxs)("section", { className: cn("agg-home-page flex w-full flex-col", classNames == null ? void 0 : classNames.root), children: [
@@ -30043,7 +30372,13 @@ var HomePage = ({
30043
30372
  "div",
30044
30373
  {
30045
30374
  className: "agg-home-section",
30046
- children: /* @__PURE__ */ (0, import_jsx_runtime176.jsx)(
30375
+ children: sectionIndex === 0 && !isUrlStateReady ? /* @__PURE__ */ (0, import_jsx_runtime176.jsx)(
30376
+ Skeleton,
30377
+ {
30378
+ view: "event-list",
30379
+ ariaLabel: labels.eventList.loading(eventSectionItem.title)
30380
+ }
30381
+ ) : /* @__PURE__ */ (0, import_jsx_runtime176.jsx)(
30047
30382
  EventList,
30048
30383
  __spreadValues({
30049
30384
  title: eventSectionItem.title,
@@ -30059,11 +30394,33 @@ var HomePage = ({
30059
30394
  handleSelectEvent(event);
30060
30395
  },
30061
30396
  onMarketClick: onMarketClickProp
30062
- }, sectionIndex === 0 ? {
30397
+ }, sectionIndex === 0 ? __spreadValues({
30063
30398
  stateRef: eventListStateRef,
30064
- initialVenueTab: (_d2 = snapshot == null ? void 0 : snapshot.venueTab) != null ? _d2 : void 0,
30399
+ initialVenueTab: enableEventDiscoveryUrlPersistence ? void 0 : (_d2 = snapshot == null ? void 0 : snapshot.venueTab) != null ? _d2 : void 0,
30065
30400
  initialLoadedPageCount: snapshot == null ? void 0 : snapshot.loadedPageCount
30066
- } : void 0)
30401
+ }, enableEventDiscoveryUrlPersistence ? {
30402
+ activeVenueTab: discoveryFilterState.venue,
30403
+ onActiveVenueTabChange: (venue) => {
30404
+ commitDiscoveryFilterState(
30405
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), { venue }),
30406
+ "push"
30407
+ );
30408
+ },
30409
+ selectedSort: discoveryFilterState.sort,
30410
+ onSelectedSortChange: (sort) => {
30411
+ commitDiscoveryFilterState(
30412
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), { sort }),
30413
+ "push"
30414
+ );
30415
+ },
30416
+ categoryPath: discoveryFilterState.subcategoryPath,
30417
+ onCategoryPathChange: (subcategoryPath, options) => {
30418
+ commitDiscoveryFilterState(
30419
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), { subcategoryPath }),
30420
+ options.history
30421
+ );
30422
+ }
30423
+ } : void 0) : void 0)
30067
30424
  )
30068
30425
  },
30069
30426
  `${(_a2 = eventSectionItem.id) != null ? _a2 : eventSectionItem.title}-${sectionIndex}`
@@ -30423,7 +30780,7 @@ var import_hooks77 = require("@agg-build/hooks");
30423
30780
  var import_sdk27 = require("@agg-build/sdk");
30424
30781
  var Dialog8 = __toESM(require("@radix-ui/react-dialog"));
30425
30782
  var import_react49 = require("react");
30426
- var import_zod2 = require("zod");
30783
+ var import_zod3 = require("zod");
30427
30784
  var import_jsx_runtime179 = require("react/jsx-runtime");
30428
30785
  var USDC_DECIMALS = 6;
30429
30786
  var EDITABLE_AMOUNT_PATTERN = /^\d*(?:\.\d{0,6})?$/;
@@ -30448,7 +30805,7 @@ var VENUE_LABELS = {
30448
30805
  [import_sdk27.Venue.hyperliquid]: "Hyperliquid",
30449
30806
  [import_sdk27.Venue.novig]: "Novig"
30450
30807
  };
30451
- var amountSchema = import_zod2.z.string().min(1).regex(FINAL_AMOUNT_PATTERN);
30808
+ var amountSchema = import_zod3.z.string().min(1).regex(FINAL_AMOUNT_PATTERN);
30452
30809
  var decimalToRaw = (value, decimals = USDC_DECIMALS) => {
30453
30810
  const parsed = amountSchema.safeParse(value.trim());
30454
30811
  if (!parsed.success) return null;