@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/pages.js CHANGED
@@ -4874,6 +4874,19 @@ var resolveCategoryIdsFromPath = (path, fallbackCategoryIds) => {
4874
4874
  if (deepest) return [deepest];
4875
4875
  return fallbackCategoryIds;
4876
4876
  };
4877
+ var validateSubcategoryPath = (requestedPath, rootCategoryId, childrenByParentId) => {
4878
+ var _a;
4879
+ if (!rootCategoryId) return [];
4880
+ const validPath = [];
4881
+ let parentId = rootCategoryId;
4882
+ for (const categoryId of requestedPath) {
4883
+ const category = (_a = childrenByParentId.get(parentId)) == null ? void 0 : _a.find((child) => child.id === categoryId);
4884
+ if (!category) break;
4885
+ validPath.push(category.id);
4886
+ parentId = category.id;
4887
+ }
4888
+ return validPath;
4889
+ };
4877
4890
  var resolveTabVenus = (tab, visibleVenues) => {
4878
4891
  const applyAllowList = (venues) => {
4879
4892
  if (!venues) {
@@ -10301,6 +10314,13 @@ var import_react16 = require("react");
10301
10314
  // src/events/list/event-list.constants.ts
10302
10315
  var import_sdk13 = require("@agg-build/sdk");
10303
10316
  var DEFAULT_EVENTS_LIMIT = 50;
10317
+ var DEFAULT_EVENT_LIST_SORT = "volume24hr";
10318
+ var EVENT_LIST_SORT_VALUES = [
10319
+ "volume24hr",
10320
+ "topArbitrage",
10321
+ "volume",
10322
+ "endDate"
10323
+ ];
10304
10324
  var getDefaultEventListTabs = (labels) => {
10305
10325
  return [
10306
10326
  {
@@ -10370,6 +10390,7 @@ var getDefaultEventListTabs = (labels) => {
10370
10390
 
10371
10391
  // src/events/list/event-list-tabs.tsx
10372
10392
  var import_jsx_runtime134 = require("react/jsx-runtime");
10393
+ var DEFAULT_HIDDEN_VENUES = new Set((0, import_sdk14.getEffectiveDisabledVenues)(void 0));
10373
10394
  var renderTabIcon = (tab, isActive) => {
10374
10395
  if (tab.iconName === "warning-filled") {
10375
10396
  return /* @__PURE__ */ (0, import_jsx_runtime134.jsx)(Icon, { name: "warning-filled", size: "small", color: "currentColor" });
@@ -10398,15 +10419,11 @@ var useEventListTabs = (tabs) => {
10398
10419
  const availabilityState = (0, import_hooks37.getVenueAvailabilityState)({
10399
10420
  venueId: tab.venueLogo,
10400
10421
  disabledVenues,
10401
- // A compile-time-inactive venue (novig today) is hidden by default and
10402
- // un-hidden per-environment by NOVIG_DATA_VISIBLE, so its
10403
- // disabled-ness is only known once `/app/config` resolves — treating
10404
- // it as hidden while that request is in flight stops its tab flashing
10405
- // in and out where the flag is unset. Active venues must NOT be
10406
- // withheld, or they pop in late. Keyed off the shared inactive-venue
10407
- // helper rather than a venue name, so launching or retiring one needs
10408
- // no change here.
10409
- isConfigLoading: isAppConfigLoading && (0, import_sdk14.isVenueInactive)(tab.venueLogo),
10422
+ // A globally inactive or default-disabled venue is hidden until
10423
+ // `/app/config` resolves. That prevents partner-opt-in venues such as
10424
+ // Novig from flashing briefly for apps that still disable them, while
10425
+ // ordinary active venues continue rendering immediately.
10426
+ isConfigLoading: isAppConfigLoading && ((0, import_sdk14.isVenueInactive)(tab.venueLogo) || DEFAULT_HIDDEN_VENUES.has(tab.venueLogo)),
10410
10427
  isLocationBlocked
10411
10428
  });
10412
10429
  if (availabilityState === "hidden") return [];
@@ -10584,10 +10601,16 @@ var EventList = ({
10584
10601
  getMarketHref,
10585
10602
  getEventHref,
10586
10603
  initialVenueTab,
10604
+ activeVenueTab: controlledActiveVenueTab,
10605
+ onActiveVenueTabChange,
10606
+ selectedSort: controlledSelectedSort,
10607
+ onSelectedSortChange,
10608
+ categoryPath: controlledCategoryPath,
10609
+ onCategoryPathChange,
10587
10610
  initialLoadedPageCount,
10588
10611
  stateRef
10589
10612
  }) => {
10590
- var _a, _b, _c, _d, _e;
10613
+ var _a, _b, _c, _d, _e, _f;
10591
10614
  const labels = (0, import_hooks38.useLabels)();
10592
10615
  const { disabledVenues } = (0, import_hooks38.useAppConfig)();
10593
10616
  const {
@@ -10599,13 +10622,37 @@ var EventList = ({
10599
10622
  () => disabledVenues.length === 0 ? void 0 : (0, import_hooks38.getVisibleVenueIdsByConfig)(import_sdk15.VENUES, disabledVenues),
10600
10623
  [disabledVenues]
10601
10624
  );
10602
- const [activeTabValue, setActiveTabValue] = (0, import_react17.useState)(
10625
+ const [internalActiveTabValue, setInternalActiveTabValue] = (0, import_react17.useState)(
10603
10626
  (_b = initialVenueTab != null ? initialVenueTab : (_a = resolvedTabs[0]) == null ? void 0 : _a.value) != null ? _b : "matched"
10604
10627
  );
10605
- const [selectedSort, setSelectedSort] = (0, import_react17.useState)("volume24hr");
10628
+ const activeTabValue = controlledActiveVenueTab != null ? controlledActiveVenueTab : internalActiveTabValue;
10629
+ const [internalSelectedSort, setInternalSelectedSort] = (0, import_react17.useState)(DEFAULT_EVENT_LIST_SORT);
10630
+ const selectedSort = controlledSelectedSort != null ? controlledSelectedSort : internalSelectedSort;
10606
10631
  const [isSubcategoriesModalOpen, setIsSubcategoriesModalOpen] = (0, import_react17.useState)(false);
10607
- const [categoryPath, setCategoryPath] = (0, import_react17.useState)([]);
10632
+ const [internalCategoryPath, setInternalCategoryPath] = (0, import_react17.useState)([]);
10633
+ const categoryPath = controlledCategoryPath != null ? controlledCategoryPath : internalCategoryPath;
10608
10634
  const [collapsedCategoryIds, setCollapsedCategoryIds] = (0, import_react17.useState)(() => /* @__PURE__ */ new Set());
10635
+ const handleActiveTabChange = (0, import_react17.useCallback)(
10636
+ (value) => {
10637
+ if (controlledActiveVenueTab == null) setInternalActiveTabValue(value);
10638
+ onActiveVenueTabChange == null ? void 0 : onActiveVenueTabChange(value, { history: "push" });
10639
+ },
10640
+ [controlledActiveVenueTab, onActiveVenueTabChange]
10641
+ );
10642
+ const handleSelectedSortChange = (0, import_react17.useCallback)(
10643
+ (value) => {
10644
+ if (controlledSelectedSort == null) setInternalSelectedSort(value);
10645
+ onSelectedSortChange == null ? void 0 : onSelectedSortChange(value, { history: "push" });
10646
+ },
10647
+ [controlledSelectedSort, onSelectedSortChange]
10648
+ );
10649
+ const handleCategoryPathChange = (0, import_react17.useCallback)(
10650
+ (value, options) => {
10651
+ if (controlledCategoryPath == null) setInternalCategoryPath(value);
10652
+ onCategoryPathChange == null ? void 0 : onCategoryPathChange(value, options);
10653
+ },
10654
+ [controlledCategoryPath, onCategoryPathChange]
10655
+ );
10609
10656
  const sortItems = (0, import_react17.useMemo)(() => {
10610
10657
  return [
10611
10658
  {
@@ -10638,9 +10685,9 @@ var EventList = ({
10638
10685
  const baseCategoryId = (_c = categoryIds == null ? void 0 : categoryIds[0]) != null ? _c : null;
10639
10686
  (0, import_react17.useEffect)(() => {
10640
10687
  if (!isDiscoveryFiltersEnabled) return;
10641
- setCategoryPath([]);
10688
+ if (controlledCategoryPath == null) setInternalCategoryPath([]);
10642
10689
  setCollapsedCategoryIds(/* @__PURE__ */ new Set());
10643
- }, [baseCategoryId, isDiscoveryFiltersEnabled]);
10690
+ }, [baseCategoryId, controlledCategoryPath, isDiscoveryFiltersEnabled]);
10644
10691
  const { categories: topLevelCategories } = (0, import_hooks38.useCategories)({
10645
10692
  queryKeyScope: "event-list-top-categories",
10646
10693
  enabled: isDiscoveryFiltersEnabled,
@@ -10652,12 +10699,13 @@ var EventList = ({
10652
10699
  const baseCategoryChildPrefetchIds = (0, import_react17.useMemo)(() => {
10653
10700
  return Array.from(
10654
10701
  /* @__PURE__ */ new Set([
10702
+ ...baseCategoryId ? [baseCategoryId] : [],
10655
10703
  ...sortedTopLevelCategories.map((category) => category.id),
10656
10704
  ...categoryPath.map((category) => category.id)
10657
10705
  ])
10658
10706
  );
10659
- }, [categoryPath, sortedTopLevelCategories]);
10660
- const { childrenByParentId: baseChildrenByParentId } = (0, import_hooks38.useCategoryChildren)({
10707
+ }, [baseCategoryId, categoryPath, sortedTopLevelCategories]);
10708
+ const { childrenByParentId: baseChildrenByParentId, isLoading: isBaseCategoryChildrenLoading } = (0, import_hooks38.useCategoryChildren)({
10661
10709
  queryKeyScope: "event-list-category-children",
10662
10710
  enabled: isDiscoveryFiltersEnabled,
10663
10711
  parentIds: baseCategoryChildPrefetchIds,
@@ -10707,7 +10755,36 @@ var EventList = ({
10707
10755
  ...visibleGrandchildrenByParentId
10708
10756
  ]);
10709
10757
  }, [baseChildrenByParentId, visibleChildrenByParentId, visibleGrandchildrenByParentId]);
10710
- const shouldRenderSubcategoryFilters = isDiscoveryFiltersEnabled && baseCategoryId !== null && (((_e = (_d = childrenByParentId.get(baseCategoryId)) == null ? void 0 : _d.length) != null ? _e : 0) > 0 || categoryPath.length > 0);
10758
+ const controlledCategoryPathKey = (_d = controlledCategoryPath == null ? void 0 : controlledCategoryPath.map((entry) => entry.id).join(",")) != null ? _d : "";
10759
+ const controlledCategoryPathValidationKey = `${baseCategoryId != null ? baseCategoryId : ""}|${controlledCategoryPathKey}`;
10760
+ const [validatedCategoryPathKey, setValidatedCategoryPathKey] = (0, import_react17.useState)(
10761
+ () => controlledCategoryPath == null || controlledCategoryPath.length === 0 ? controlledCategoryPathValidationKey : ""
10762
+ );
10763
+ (0, import_react17.useEffect)(() => {
10764
+ if (controlledCategoryPath == null || controlledCategoryPath.length === 0) {
10765
+ setValidatedCategoryPathKey(controlledCategoryPathValidationKey);
10766
+ return;
10767
+ }
10768
+ if (isBaseCategoryChildrenLoading) return;
10769
+ const requestedIds = controlledCategoryPath.map((entry) => entry.id);
10770
+ const validIds = validateSubcategoryPath(requestedIds, baseCategoryId, baseChildrenByParentId);
10771
+ if (validIds.length !== requestedIds.length) {
10772
+ handleCategoryPathChange(controlledCategoryPath.slice(0, validIds.length), {
10773
+ history: "replace"
10774
+ });
10775
+ return;
10776
+ }
10777
+ setValidatedCategoryPathKey(controlledCategoryPathValidationKey);
10778
+ }, [
10779
+ baseCategoryId,
10780
+ baseChildrenByParentId,
10781
+ controlledCategoryPath,
10782
+ controlledCategoryPathKey,
10783
+ controlledCategoryPathValidationKey,
10784
+ handleCategoryPathChange,
10785
+ isBaseCategoryChildrenLoading
10786
+ ]);
10787
+ const shouldRenderSubcategoryFilters = isDiscoveryFiltersEnabled && baseCategoryId !== null && (((_f = (_e = childrenByParentId.get(baseCategoryId)) == null ? void 0 : _e.length) != null ? _f : 0) > 0 || categoryPath.length > 0);
10711
10788
  const baseCategoryEventCount = (0, import_react17.useMemo)(() => {
10712
10789
  var _a2, _b2;
10713
10790
  return (_b2 = (_a2 = sortedTopLevelCategories.find((c) => c.id === baseCategoryId)) == null ? void 0 : _a2.eventCount) != null ? _b2 : 0;
@@ -10765,7 +10842,8 @@ var EventList = ({
10765
10842
  // markets experience is enabled (the sports event-detail view loads the
10766
10843
  // folded companions via aggKey). Gated on the same flag as that view.
10767
10844
  grouped: showSportsMarkets,
10768
- initialPages: initialLoadedPageCount
10845
+ initialPages: initialLoadedPageCount,
10846
+ enabled: controlledCategoryPath == null || validatedCategoryPathKey === controlledCategoryPathValidationKey
10769
10847
  });
10770
10848
  (0, import_react17.useEffect)(() => {
10771
10849
  if (stateRef) {
@@ -10826,7 +10904,7 @@ var EventList = ({
10826
10904
  "grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] md:grid-cols-[repeat(auto-fill,minmax(360px,1fr))] gap-4"
10827
10905
  );
10828
10906
  const handleSelectAllCategories = () => {
10829
- setCategoryPath([]);
10907
+ handleCategoryPathChange([], { history: "push" });
10830
10908
  setCollapsedCategoryIds(/* @__PURE__ */ new Set());
10831
10909
  onCategoryRootChange == null ? void 0 : onCategoryRootChange(baseCategoryId != null ? baseCategoryId : void 0);
10832
10910
  setIsSubcategoriesModalOpen(false);
@@ -10838,7 +10916,7 @@ var EventList = ({
10838
10916
  isExpanded,
10839
10917
  isSelected
10840
10918
  }) => {
10841
- setCategoryPath((prev) => [...prev.slice(0, depth), entry]);
10919
+ handleCategoryPathChange([...categoryPath.slice(0, depth), entry], { history: "push" });
10842
10920
  setCollapsedCategoryIds((prev) => {
10843
10921
  const next = new Set(prev);
10844
10922
  if (hasKnownChildren && isExpanded && isSelected) {
@@ -10878,7 +10956,7 @@ var EventList = ({
10878
10956
  ariaLabel: labels.eventList.tabsAria(title2),
10879
10957
  className: "agg-event-list-tabs min-w-0 flex-1 max-w-[600px]!",
10880
10958
  activeTab: activeTabValue,
10881
- onTabChange: setActiveTabValue,
10959
+ onTabChange: handleActiveTabChange,
10882
10960
  overflowBehavior: "scroll"
10883
10961
  }
10884
10962
  ),
@@ -10888,7 +10966,7 @@ var EventList = ({
10888
10966
  ariaLabel: labels.eventList.sortByLabel,
10889
10967
  className: "min-w-[200px] w-auto! shrink-0 min-h-10.5",
10890
10968
  value: selectedSort,
10891
- onChange: setSelectedSort,
10969
+ onChange: handleSelectedSortChange,
10892
10970
  items: sortItems,
10893
10971
  contentClassName: "py-2",
10894
10972
  triggerClassName: "rounded-agg-lg!"
@@ -10905,7 +10983,7 @@ var EventList = ({
10905
10983
  ariaLabel: labels.eventList.tabsAria(title2),
10906
10984
  className: "agg-event-list-tabs min-w-0 flex-1",
10907
10985
  activeTab: activeTabValue,
10908
- onTabChange: setActiveTabValue,
10986
+ onTabChange: handleActiveTabChange,
10909
10987
  overflowBehavior: "select"
10910
10988
  }
10911
10989
  ),
@@ -10915,7 +10993,7 @@ var EventList = ({
10915
10993
  ariaLabel: labels.eventList.sortByLabel,
10916
10994
  className: "min-w-0 flex-1 md:min-w-[144px]",
10917
10995
  value: selectedSort,
10918
- onChange: setSelectedSort,
10996
+ onChange: handleSelectedSortChange,
10919
10997
  items: sortItems,
10920
10998
  contentClassName: "rounded-[12px] py-2 shadow-[0_8px_16px_rgba(0,0,0,0.1)]"
10921
10999
  }
@@ -18525,7 +18603,7 @@ var getSmartRouteTotalFees = (feeBreakdown) => {
18525
18603
  if (!feeBreakdown) return null;
18526
18604
  const feeParts = [feeBreakdown.venueFees, feeBreakdown.bridgeFees, feeBreakdown.executionGas];
18527
18605
  if (!feeParts.every(Number.isFinite)) return null;
18528
- return feeParts.reduce((total, fee) => total + fee, 0);
18606
+ return feeParts.reduce((total, fee) => total + Math.round(fee * 100) / 100, 0);
18529
18607
  };
18530
18608
  var getPlaceOrderContainerClassName = () => {
18531
18609
  return PLACE_ORDER_CONTAINER_CLASS_NAME;
@@ -18787,7 +18865,7 @@ var buildLiveRouteCards = ({
18787
18865
  // balance condition, and `handleExecuteQuote` rejects an empty quoteId.
18788
18866
  sq.venue !== primaryWinnerVenue
18789
18867
  ).map((sq) => {
18790
- var _a2;
18868
+ var _a2, _b2;
18791
18869
  const parsedVenue = parseVenue(sq.venue);
18792
18870
  const venue = parsedVenue.success ? parsedVenue.data : void 0;
18793
18871
  if (geoBlockedVenues.has(sq.venue)) {
@@ -18820,7 +18898,12 @@ var buildLiveRouteCards = ({
18820
18898
  estimatedProfit: sq.estimatedProfit,
18821
18899
  returnPct: sq.returnPct,
18822
18900
  // venueSoloQuotes stripped on solo card's quoteData (only the primary needs them)
18823
- venueSoloQuotes: void 0
18901
+ venueSoloQuotes: void 0,
18902
+ // This solo's OWN fee breakdown. Without it the spread above leaves the
18903
+ // primary route's breakdown in place, so selecting a bridge-free venue
18904
+ // still showed the primary's bridging charge. Falls back to the
18905
+ // primary only when the router omitted one (non-"ok" solos).
18906
+ feeBreakdown: (_b2 = sq.feeBreakdown) != null ? _b2 : quoteData.feeBreakdown
18824
18907
  }), sq.quoteId ? { status: "ok", error: void 0 } : null);
18825
18908
  return {
18826
18909
  id: `live-solo-${sq.venue}`,
@@ -27283,6 +27366,85 @@ var resolveInitialTabValue = (tabs, defaultActiveTab) => {
27283
27366
  return (_b = (_a = tabs[0]) == null ? void 0 : _a.value) != null ? _b : ALL_CATEGORIES_TAB_VALUE;
27284
27367
  };
27285
27368
 
27369
+ // src/pages/home/event-discovery-url-state.ts
27370
+ var import_zod2 = require("zod");
27371
+ var nonEmptyParamSchema = import_zod2.z.string().trim().min(1);
27372
+ var sortParamSchema = import_zod2.z.enum(EVENT_LIST_SORT_VALUES);
27373
+ var readScalarParam = (searchParams, name, schema) => {
27374
+ const values = searchParams.getAll(name);
27375
+ if (values.length === 0) return { invalid: false };
27376
+ if (values.length !== 1) return { invalid: true };
27377
+ const parsedValue = schema.safeParse(values[0]);
27378
+ if (!parsedValue.success) return { invalid: true };
27379
+ return { value: parsedValue.data, invalid: false };
27380
+ };
27381
+ var readEventDiscoveryUrlState = (searchParams) => {
27382
+ const category = readScalarParam(searchParams, "category", nonEmptyParamSchema);
27383
+ const venue = readScalarParam(searchParams, "venue", nonEmptyParamSchema);
27384
+ const sort = readScalarParam(searchParams, "sort", sortParamSchema);
27385
+ const subcategoryPath = [];
27386
+ let hasInvalidSubcategory = false;
27387
+ for (const value of searchParams.getAll("subcategory")) {
27388
+ const parsedValue = nonEmptyParamSchema.safeParse(value);
27389
+ if (!parsedValue.success) {
27390
+ hasInvalidSubcategory = true;
27391
+ break;
27392
+ }
27393
+ subcategoryPath.push(parsedValue.data);
27394
+ }
27395
+ return {
27396
+ category: category.value,
27397
+ subcategoryPath,
27398
+ venue: venue.value,
27399
+ sort: sort.value,
27400
+ categoryIsInvalid: category.invalid,
27401
+ shouldCanonicalize: category.invalid || venue.invalid || sort.invalid || hasInvalidSubcategory
27402
+ };
27403
+ };
27404
+ var resolveEventDiscoveryUrlState = ({
27405
+ parsedState,
27406
+ categoryValues,
27407
+ venueValues,
27408
+ defaultCategory,
27409
+ defaultVenue
27410
+ }) => {
27411
+ var _a, _b, _c;
27412
+ const categoryIsValid = parsedState.category == null || categoryValues.includes(parsedState.category);
27413
+ const venueIsValid = parsedState.venue == null || venueValues.includes(parsedState.venue);
27414
+ const category = categoryIsValid ? (_a = parsedState.category) != null ? _a : defaultCategory : defaultCategory;
27415
+ const venue = venueIsValid ? (_b = parsedState.venue) != null ? _b : defaultVenue : defaultVenue;
27416
+ const sort = (_c = parsedState.sort) != null ? _c : DEFAULT_EVENT_LIST_SORT;
27417
+ return {
27418
+ state: {
27419
+ category,
27420
+ subcategoryPath: categoryIsValid && !parsedState.categoryIsInvalid ? parsedState.subcategoryPath : [],
27421
+ venue,
27422
+ sort
27423
+ },
27424
+ shouldCanonicalize: parsedState.shouldCanonicalize || !categoryIsValid || !venueIsValid || parsedState.category === defaultCategory || parsedState.venue === defaultVenue || parsedState.sort === DEFAULT_EVENT_LIST_SORT
27425
+ };
27426
+ };
27427
+ var buildEventDiscoveryUrl = ({
27428
+ currentUrl,
27429
+ state,
27430
+ defaultCategory,
27431
+ defaultVenue
27432
+ }) => {
27433
+ const nextUrl = new URL(currentUrl.toString());
27434
+ nextUrl.searchParams.delete("category");
27435
+ nextUrl.searchParams.delete("subcategory");
27436
+ nextUrl.searchParams.delete("venue");
27437
+ nextUrl.searchParams.delete("sort");
27438
+ if (state.category !== defaultCategory) nextUrl.searchParams.set("category", state.category);
27439
+ for (const subcategory of state.subcategoryPath) {
27440
+ nextUrl.searchParams.append("subcategory", subcategory);
27441
+ }
27442
+ if (state.venue !== defaultVenue) nextUrl.searchParams.set("venue", state.venue);
27443
+ if (state.sort !== DEFAULT_EVENT_LIST_SORT) nextUrl.searchParams.set("sort", state.sort);
27444
+ const search = nextUrl.searchParams.toString();
27445
+ return `${nextUrl.pathname}${search ? `?${search}` : ""}${nextUrl.hash}`;
27446
+ };
27447
+
27286
27448
  // src/pages/home/use-scroll-restoration.ts
27287
27449
  var import_react43 = require("react");
27288
27450
  function useScrollRestoration({
@@ -27562,7 +27724,7 @@ var HomePage = ({
27562
27724
  onError,
27563
27725
  stickyOrderPanel
27564
27726
  }) => {
27565
- var _a, _b, _c, _d;
27727
+ var _a, _b, _c, _d, _e, _f, _g;
27566
27728
  const labels = (0, import_hooks71.useLabels)();
27567
27729
  const {
27568
27730
  search: {
@@ -27572,8 +27734,11 @@ var HomePage = ({
27572
27734
  onChange: onSearchChange,
27573
27735
  onSelect: onSelectEventInternal
27574
27736
  },
27575
- trading: { executionMode }
27737
+ trading: { executionMode },
27738
+ features: { enableEventDiscoveryUrlPersistence }
27576
27739
  } = (0, import_hooks71.useSdkUiConfig)();
27740
+ const { isLoading: isAppConfigLoading } = (0, import_hooks71.useAppConfig)();
27741
+ const primaryEventListTabs = useEventListTabs();
27577
27742
  const { capture, consume } = (0, import_hooks71.useEventListState)();
27578
27743
  const [snapshot] = (0, import_react45.useState)(() => consume());
27579
27744
  const eventListStateRef = (0, import_react45.useRef)(null);
@@ -27644,7 +27809,7 @@ var HomePage = ({
27644
27809
  }, [isShowingAllResults, normalizedSearchValue, selectedEvent]);
27645
27810
  const resolvedSelectedEvent = selectedEvent != null ? selectedEvent : stickySelectedEvent;
27646
27811
  const resolvedAllCategoryTabLabel = allCategoryTabLabel === DEFAULT_ALL_CATEGORY_TAB_LABEL ? labels.home.trending : allCategoryTabLabel;
27647
- const { categories } = (0, import_hooks71.useCategories)({
27812
+ const { categories, isLoading: isCategoriesLoading } = (0, import_hooks71.useCategories)({
27648
27813
  limit: categoriesLimit,
27649
27814
  enabled: !hasCustomTabs
27650
27815
  });
@@ -27655,22 +27820,180 @@ var HomePage = ({
27655
27820
  if (hasCustomTabs && tabs) return tabs;
27656
27821
  return categoryTabs;
27657
27822
  }, [categoryTabs, hasCustomTabs, tabs]);
27823
+ const availableCategoryValues = (0, import_react45.useMemo)(
27824
+ () => resolvedTabs.filter((tab) => !tab.disabled).map((tab) => tab.value),
27825
+ [resolvedTabs]
27826
+ );
27827
+ const resolvedDefaultCategory = resolveInitialTabValue(resolvedTabs, defaultActiveTab);
27828
+ const defaultCategory = availableCategoryValues.includes(resolvedDefaultCategory) ? resolvedDefaultCategory : (_a = availableCategoryValues[0]) != null ? _a : resolvedDefaultCategory;
27829
+ const availableVenueTabs = (0, import_react45.useMemo)(
27830
+ () => primaryEventListTabs.filter((tab) => !tab.disabled && !tab.isComingSoon),
27831
+ [primaryEventListTabs]
27832
+ );
27833
+ const availableVenueValues = (0, import_react45.useMemo)(
27834
+ () => availableVenueTabs.map((tab) => tab.value),
27835
+ [availableVenueTabs]
27836
+ );
27837
+ const defaultVenue = (_c = (_b = availableVenueTabs[0]) == null ? void 0 : _b.value) != null ? _c : "matched";
27658
27838
  const resolvedEventSectionItems = eventSectionItems && eventSectionItems.length > 0 ? eventSectionItems : getDefaultEventSectionItems(labels);
27659
- const [activeTabValue, setActiveTabValue] = (0, import_react45.useState)(() => {
27660
- if (snapshot == null ? void 0 : snapshot.categoryTab) {
27661
- return snapshot.categoryTab;
27839
+ const [discoveryFilterState, setDiscoveryFilterState] = (0, import_react45.useState)(
27840
+ () => {
27841
+ var _a2, _b2;
27842
+ return {
27843
+ category: (_a2 = snapshot == null ? void 0 : snapshot.categoryTab) != null ? _a2 : defaultCategory,
27844
+ venue: (_b2 = snapshot == null ? void 0 : snapshot.venueTab) != null ? _b2 : defaultVenue,
27845
+ sort: DEFAULT_EVENT_LIST_SORT,
27846
+ subcategoryPath: []
27847
+ };
27662
27848
  }
27663
- return resolveInitialTabValue(resolvedTabs, defaultActiveTab);
27664
- });
27849
+ );
27850
+ const discoveryFilterStateRef = (0, import_react45.useRef)(discoveryFilterState);
27851
+ discoveryFilterStateRef.current = discoveryFilterState;
27852
+ const [isUrlStateReady, setIsUrlStateReady] = (0, import_react45.useState)(!enableEventDiscoveryUrlPersistence);
27853
+ const hasInitializedUrlStateRef = (0, import_react45.useRef)(false);
27854
+ const activeTabValue = discoveryFilterState.category;
27855
+ const resolveCurrentUrlState = (0, import_react45.useCallback)(() => {
27856
+ if (typeof window === "undefined") return null;
27857
+ const parsedState = readEventDiscoveryUrlState(new URL(window.location.href).searchParams);
27858
+ const resolvedState = resolveEventDiscoveryUrlState({
27859
+ parsedState,
27860
+ categoryValues: availableCategoryValues,
27861
+ venueValues: availableVenueValues,
27862
+ defaultCategory,
27863
+ defaultVenue
27864
+ });
27865
+ return {
27866
+ state: {
27867
+ category: resolvedState.state.category,
27868
+ venue: resolvedState.state.venue,
27869
+ sort: resolvedState.state.sort,
27870
+ subcategoryPath: resolvedState.state.subcategoryPath.map((id) => ({
27871
+ id,
27872
+ label: id,
27873
+ eventCount: 0
27874
+ }))
27875
+ },
27876
+ shouldCanonicalize: resolvedState.shouldCanonicalize
27877
+ };
27878
+ }, [availableCategoryValues, availableVenueValues, defaultCategory, defaultVenue]);
27879
+ const writeFilterStateToHistory = (0, import_react45.useCallback)(
27880
+ (state, historyMode) => {
27881
+ if (!enableEventDiscoveryUrlPersistence || typeof window === "undefined") return;
27882
+ const currentUrl = new URL(window.location.href);
27883
+ const nextUrl = buildEventDiscoveryUrl({
27884
+ currentUrl,
27885
+ state: {
27886
+ category: state.category,
27887
+ venue: state.venue,
27888
+ sort: state.sort,
27889
+ subcategoryPath: state.subcategoryPath.map((entry) => entry.id)
27890
+ },
27891
+ defaultCategory,
27892
+ defaultVenue
27893
+ });
27894
+ const currentRelativeUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
27895
+ if (nextUrl === currentRelativeUrl) return;
27896
+ const method = historyMode === "push" ? "pushState" : "replaceState";
27897
+ window.history[method](window.history.state, "", nextUrl);
27898
+ },
27899
+ [defaultCategory, defaultVenue, enableEventDiscoveryUrlPersistence]
27900
+ );
27901
+ const commitDiscoveryFilterState = (0, import_react45.useCallback)(
27902
+ (state, historyMode) => {
27903
+ discoveryFilterStateRef.current = state;
27904
+ setDiscoveryFilterState(state);
27905
+ if (isUrlStateReady) writeFilterStateToHistory(state, historyMode);
27906
+ },
27907
+ [isUrlStateReady, writeFilterStateToHistory]
27908
+ );
27909
+ (0, import_react45.useEffect)(() => {
27910
+ if (!enableEventDiscoveryUrlPersistence) {
27911
+ hasInitializedUrlStateRef.current = false;
27912
+ setIsUrlStateReady(true);
27913
+ return;
27914
+ }
27915
+ if (hasInitializedUrlStateRef.current) return;
27916
+ if (!hasCustomTabs && isCategoriesLoading || isAppConfigLoading) return;
27917
+ const resolvedUrlState = resolveCurrentUrlState();
27918
+ if (!resolvedUrlState) return;
27919
+ hasInitializedUrlStateRef.current = true;
27920
+ discoveryFilterStateRef.current = resolvedUrlState.state;
27921
+ setDiscoveryFilterState(resolvedUrlState.state);
27922
+ setIsUrlStateReady(true);
27923
+ const currentUrl = new URL(window.location.href);
27924
+ const canonicalUrl = buildEventDiscoveryUrl({
27925
+ currentUrl,
27926
+ state: {
27927
+ category: resolvedUrlState.state.category,
27928
+ venue: resolvedUrlState.state.venue,
27929
+ sort: resolvedUrlState.state.sort,
27930
+ subcategoryPath: resolvedUrlState.state.subcategoryPath.map((entry) => entry.id)
27931
+ },
27932
+ defaultCategory,
27933
+ defaultVenue
27934
+ });
27935
+ const currentRelativeUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
27936
+ if (resolvedUrlState.shouldCanonicalize || canonicalUrl !== currentRelativeUrl) {
27937
+ window.history.replaceState(window.history.state, "", canonicalUrl);
27938
+ }
27939
+ }, [
27940
+ defaultCategory,
27941
+ defaultVenue,
27942
+ enableEventDiscoveryUrlPersistence,
27943
+ hasCustomTabs,
27944
+ isAppConfigLoading,
27945
+ isCategoriesLoading,
27946
+ resolveCurrentUrlState
27947
+ ]);
27948
+ (0, import_react45.useEffect)(() => {
27949
+ if (!enableEventDiscoveryUrlPersistence || !isUrlStateReady) return;
27950
+ const handlePopState = () => {
27951
+ const resolvedUrlState = resolveCurrentUrlState();
27952
+ if (!resolvedUrlState) return;
27953
+ discoveryFilterStateRef.current = resolvedUrlState.state;
27954
+ setDiscoveryFilterState(resolvedUrlState.state);
27955
+ if (resolvedUrlState.shouldCanonicalize) {
27956
+ writeFilterStateToHistory(resolvedUrlState.state, "replace");
27957
+ }
27958
+ };
27959
+ window.addEventListener("popstate", handlePopState);
27960
+ return () => {
27961
+ window.removeEventListener("popstate", handlePopState);
27962
+ };
27963
+ }, [
27964
+ enableEventDiscoveryUrlPersistence,
27965
+ isUrlStateReady,
27966
+ resolveCurrentUrlState,
27967
+ writeFilterStateToHistory
27968
+ ]);
27665
27969
  const activeTabValueRef = (0, import_react45.useRef)(activeTabValue);
27666
27970
  activeTabValueRef.current = activeTabValue;
27667
27971
  (0, import_react45.useEffect)(() => {
27668
- setActiveTabValue((currentValue) => {
27669
- const hasCurrentValue = resolvedTabs.some((tab) => tab.value === currentValue);
27670
- if (hasCurrentValue) return currentValue;
27671
- return resolveInitialTabValue(resolvedTabs, defaultActiveTab);
27972
+ if (enableEventDiscoveryUrlPersistence && !isUrlStateReady) return;
27973
+ const currentState = discoveryFilterStateRef.current;
27974
+ const categoryIsAvailable = availableCategoryValues.includes(currentState.category);
27975
+ const venueIsAvailable = availableVenueValues.includes(currentState.venue);
27976
+ if (categoryIsAvailable && (!enableEventDiscoveryUrlPersistence || venueIsAvailable)) return;
27977
+ const nextState = __spreadProps(__spreadValues({}, currentState), {
27978
+ category: categoryIsAvailable ? currentState.category : defaultCategory,
27979
+ venue: venueIsAvailable ? currentState.venue : defaultVenue,
27980
+ subcategoryPath: categoryIsAvailable ? currentState.subcategoryPath : []
27672
27981
  });
27673
- }, [defaultActiveTab, resolvedTabs]);
27982
+ if (enableEventDiscoveryUrlPersistence) {
27983
+ commitDiscoveryFilterState(nextState, "replace");
27984
+ return;
27985
+ }
27986
+ discoveryFilterStateRef.current = nextState;
27987
+ setDiscoveryFilterState(nextState);
27988
+ }, [
27989
+ availableCategoryValues,
27990
+ availableVenueValues,
27991
+ commitDiscoveryFilterState,
27992
+ defaultCategory,
27993
+ defaultVenue,
27994
+ enableEventDiscoveryUrlPersistence,
27995
+ isUrlStateReady
27996
+ ]);
27674
27997
  const tabsItems = (0, import_react45.useMemo)(() => {
27675
27998
  return resolvedTabs.map((tab) => {
27676
27999
  const isActive = tab.value === activeTabValue;
@@ -27711,7 +28034,13 @@ var HomePage = ({
27711
28034
  ];
27712
28035
  }, [activeTab, activeTabValue, resolvedEventSectionItems]);
27713
28036
  const handleTabChange = (value) => {
27714
- setActiveTabValue(value);
28037
+ commitDiscoveryFilterState(
28038
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), {
28039
+ category: value,
28040
+ subcategoryPath: []
28041
+ }),
28042
+ "push"
28043
+ );
27715
28044
  _onTabChange == null ? void 0 : _onTabChange(value);
27716
28045
  };
27717
28046
  const handleCategoryRootChange = (categoryId) => {
@@ -27733,10 +28062,10 @@ var HomePage = ({
27733
28062
  handleSelectEvent(event);
27734
28063
  };
27735
28064
  const shouldRenderMobileSearch = isMobileSearchViewport && !resolvedSelectedEvent;
27736
- const isScrollRestorationReady = !!snapshot && !isShowingAllResults && !resolvedSelectedEvent && ((_b = (_a = eventListStateRef.current) == null ? void 0 : _a.loadedPageCount) != null ? _b : 0) >= ((_c = snapshot.loadedPageCount) != null ? _c : 1);
28065
+ const isScrollRestorationReady = !!snapshot && !isShowingAllResults && !resolvedSelectedEvent && ((_e = (_d = eventListStateRef.current) == null ? void 0 : _d.loadedPageCount) != null ? _e : 0) >= ((_f = snapshot.loadedPageCount) != null ? _f : 1);
27737
28066
  useScrollRestoration({
27738
28067
  enabled: !!snapshot && snapshot.scrollY > 0,
27739
- scrollY: (_d = snapshot == null ? void 0 : snapshot.scrollY) != null ? _d : 0,
28068
+ scrollY: (_g = snapshot == null ? void 0 : snapshot.scrollY) != null ? _g : 0,
27740
28069
  isReady: isScrollRestorationReady
27741
28070
  });
27742
28071
  return /* @__PURE__ */ (0, import_jsx_runtime168.jsx)(AggErrorBoundary, { onError, children: /* @__PURE__ */ (0, import_jsx_runtime168.jsxs)("section", { className: cn("agg-home-page flex w-full flex-col", classNames == null ? void 0 : classNames.root), children: [
@@ -27818,7 +28147,13 @@ var HomePage = ({
27818
28147
  "div",
27819
28148
  {
27820
28149
  className: "agg-home-section",
27821
- children: /* @__PURE__ */ (0, import_jsx_runtime168.jsx)(
28150
+ children: sectionIndex === 0 && !isUrlStateReady ? /* @__PURE__ */ (0, import_jsx_runtime168.jsx)(
28151
+ Skeleton,
28152
+ {
28153
+ view: "event-list",
28154
+ ariaLabel: labels.eventList.loading(eventSectionItem.title)
28155
+ }
28156
+ ) : /* @__PURE__ */ (0, import_jsx_runtime168.jsx)(
27822
28157
  EventList,
27823
28158
  __spreadValues({
27824
28159
  title: eventSectionItem.title,
@@ -27834,11 +28169,33 @@ var HomePage = ({
27834
28169
  handleSelectEvent(event);
27835
28170
  },
27836
28171
  onMarketClick: onMarketClickProp
27837
- }, sectionIndex === 0 ? {
28172
+ }, sectionIndex === 0 ? __spreadValues({
27838
28173
  stateRef: eventListStateRef,
27839
- initialVenueTab: (_d2 = snapshot == null ? void 0 : snapshot.venueTab) != null ? _d2 : void 0,
28174
+ initialVenueTab: enableEventDiscoveryUrlPersistence ? void 0 : (_d2 = snapshot == null ? void 0 : snapshot.venueTab) != null ? _d2 : void 0,
27840
28175
  initialLoadedPageCount: snapshot == null ? void 0 : snapshot.loadedPageCount
27841
- } : void 0)
28176
+ }, enableEventDiscoveryUrlPersistence ? {
28177
+ activeVenueTab: discoveryFilterState.venue,
28178
+ onActiveVenueTabChange: (venue) => {
28179
+ commitDiscoveryFilterState(
28180
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), { venue }),
28181
+ "push"
28182
+ );
28183
+ },
28184
+ selectedSort: discoveryFilterState.sort,
28185
+ onSelectedSortChange: (sort) => {
28186
+ commitDiscoveryFilterState(
28187
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), { sort }),
28188
+ "push"
28189
+ );
28190
+ },
28191
+ categoryPath: discoveryFilterState.subcategoryPath,
28192
+ onCategoryPathChange: (subcategoryPath, options) => {
28193
+ commitDiscoveryFilterState(
28194
+ __spreadProps(__spreadValues({}, discoveryFilterStateRef.current), { subcategoryPath }),
28195
+ options.history
28196
+ );
28197
+ }
28198
+ } : void 0) : void 0)
27842
28199
  )
27843
28200
  },
27844
28201
  `${(_a2 = eventSectionItem.id) != null ? _a2 : eventSectionItem.title}-${sectionIndex}`
@@ -28198,7 +28555,7 @@ var import_hooks74 = require("@agg-build/hooks");
28198
28555
  var import_sdk26 = require("@agg-build/sdk");
28199
28556
  var Dialog8 = __toESM(require("@radix-ui/react-dialog"));
28200
28557
  var import_react46 = require("react");
28201
- var import_zod2 = require("zod");
28558
+ var import_zod3 = require("zod");
28202
28559
  var import_jsx_runtime171 = require("react/jsx-runtime");
28203
28560
  var USDC_DECIMALS = 6;
28204
28561
  var EDITABLE_AMOUNT_PATTERN = /^\d*(?:\.\d{0,6})?$/;
@@ -28223,7 +28580,7 @@ var VENUE_LABELS = {
28223
28580
  [import_sdk26.Venue.hyperliquid]: "Hyperliquid",
28224
28581
  [import_sdk26.Venue.novig]: "Novig"
28225
28582
  };
28226
- var amountSchema = import_zod2.z.string().min(1).regex(FINAL_AMOUNT_PATTERN);
28583
+ var amountSchema = import_zod3.z.string().min(1).regex(FINAL_AMOUNT_PATTERN);
28227
28584
  var decimalToRaw = (value, decimals = USDC_DECIMALS) => {
28228
28585
  const parsed = amountSchema.safeParse(value.trim());
28229
28586
  if (!parsed.success) return null;