@databiosphere/findable-ui 32.0.0 → 32.1.0

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.
Files changed (67) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/lib/components/Index/index.js +1 -1
  4. package/lib/components/Index/table/hook.d.ts +2 -2
  5. package/lib/components/Index/table/hook.js +5 -2
  6. package/lib/components/Index/table/types.d.ts +3 -0
  7. package/lib/hooks/useEntityList.js +1 -7
  8. package/lib/hooks/useURLFilterParams.d.ts +1 -0
  9. package/lib/hooks/useURLFilterParams.js +1 -0
  10. package/lib/hooks/useUpdateURLSearchParams.d.ts +1 -0
  11. package/lib/hooks/useUpdateURLSearchParams.js +1 -0
  12. package/lib/providers/exploreState/actions/clearMeta/action.d.ts +9 -0
  13. package/lib/providers/exploreState/actions/clearMeta/action.js +12 -0
  14. package/lib/providers/exploreState/actions/clearMeta/dispatch.d.ts +6 -0
  15. package/lib/providers/exploreState/actions/clearMeta/dispatch.js +11 -0
  16. package/lib/providers/exploreState/actions/clearMeta/types.d.ts +6 -0
  17. package/lib/providers/exploreState/actions/clearMeta/types.js +1 -0
  18. package/lib/providers/exploreState/actions/syncStateFromUrl/action.d.ts +10 -0
  19. package/lib/providers/exploreState/actions/syncStateFromUrl/action.js +25 -0
  20. package/lib/providers/exploreState/actions/syncStateFromUrl/dispatch.d.ts +7 -0
  21. package/lib/providers/exploreState/actions/syncStateFromUrl/dispatch.js +12 -0
  22. package/lib/providers/exploreState/actions/syncStateFromUrl/types.d.ts +8 -0
  23. package/lib/providers/exploreState/actions/syncStateFromUrl/types.js +1 -0
  24. package/lib/providers/exploreState/actions/syncStateFromUrl/utils.d.ts +9 -0
  25. package/lib/providers/exploreState/actions/syncStateFromUrl/utils.js +23 -0
  26. package/lib/providers/exploreState/constants.d.ts +5 -0
  27. package/lib/providers/exploreState/constants.js +5 -0
  28. package/lib/providers/exploreState/entities.d.ts +4 -0
  29. package/lib/providers/exploreState/hooks/UseBeforePopState/useBeforePopState.d.ts +18 -0
  30. package/lib/providers/exploreState/hooks/UseBeforePopState/useBeforePopState.js +41 -0
  31. package/lib/providers/exploreState/hooks/UseBeforePopState/utils.d.ts +23 -0
  32. package/lib/providers/exploreState/hooks/UseBeforePopState/utils.js +68 -0
  33. package/lib/providers/exploreState/hooks/UseMetaCommands/actions.d.ts +13 -0
  34. package/lib/providers/exploreState/hooks/UseMetaCommands/actions.js +24 -0
  35. package/lib/providers/exploreState/hooks/UseMetaCommands/types.d.ts +4 -0
  36. package/lib/providers/exploreState/hooks/UseMetaCommands/types.js +5 -0
  37. package/lib/providers/exploreState/hooks/UseMetaCommands/useMetaCommands.d.ts +2 -0
  38. package/lib/providers/exploreState/hooks/UseMetaCommands/useMetaCommands.js +21 -0
  39. package/lib/providers/exploreState/hooks/UseMetaCommands/utils.d.ts +9 -0
  40. package/lib/providers/exploreState/hooks/UseMetaCommands/utils.js +25 -0
  41. package/lib/providers/exploreState/initializer/constants.js +1 -0
  42. package/lib/providers/exploreState.d.ts +7 -2
  43. package/lib/providers/exploreState.js +36 -1
  44. package/package.json +1 -1
  45. package/src/components/Index/index.tsx +1 -1
  46. package/src/components/Index/table/hook.ts +8 -3
  47. package/src/components/Index/table/types.ts +4 -0
  48. package/src/hooks/useEntityList.ts +1 -14
  49. package/src/hooks/useURLFilterParams.ts +1 -0
  50. package/src/hooks/useUpdateURLSearchParams.ts +1 -0
  51. package/src/providers/exploreState/actions/clearMeta/action.ts +18 -0
  52. package/src/providers/exploreState/actions/clearMeta/dispatch.ts +13 -0
  53. package/src/providers/exploreState/actions/clearMeta/types.ts +8 -0
  54. package/src/providers/exploreState/actions/syncStateFromUrl/action.ts +44 -0
  55. package/src/providers/exploreState/actions/syncStateFromUrl/dispatch.ts +16 -0
  56. package/src/providers/exploreState/actions/syncStateFromUrl/types.ts +13 -0
  57. package/src/providers/exploreState/actions/syncStateFromUrl/utils.ts +31 -0
  58. package/src/providers/exploreState/constants.ts +6 -0
  59. package/src/providers/exploreState/entities.ts +5 -0
  60. package/src/providers/exploreState/hooks/UseBeforePopState/useBeforePopState.ts +46 -0
  61. package/src/providers/exploreState/hooks/UseBeforePopState/utils.ts +93 -0
  62. package/src/providers/exploreState/hooks/UseMetaCommands/actions.ts +29 -0
  63. package/src/providers/exploreState/hooks/UseMetaCommands/types.ts +4 -0
  64. package/src/providers/exploreState/hooks/UseMetaCommands/useMetaCommands.ts +27 -0
  65. package/src/providers/exploreState/hooks/UseMetaCommands/utils.ts +33 -0
  66. package/src/providers/exploreState/initializer/constants.ts +1 -0
  67. package/src/providers/exploreState.tsx +44 -1
@@ -4,9 +4,11 @@ import { CategoryView } from "../common/categories/views/types";
4
4
  import { SelectedFilter } from "../common/entities";
5
5
  import { RowPreviewState } from "../components/Table/features/RowPreview/entities";
6
6
  import { CategoryGroup, SiteConfig } from "../config/entities";
7
+ import { ClearMetaAction } from "./exploreState/actions/clearMeta/types";
8
+ import { SyncStateFromUrlAction } from "./exploreState/actions/syncStateFromUrl/types";
7
9
  import { UpdateGroupingAction } from "./exploreState/actions/updateGrouping/types";
8
10
  import { UpdateColumnVisibilityAction } from "./exploreState/actions/updateVisibility/types";
9
- import { EntityPageStateMapper, EntityStateByCategoryGroupConfigKey, ListItem } from "./exploreState/entities";
11
+ import { EntityPageStateMapper, EntityStateByCategoryGroupConfigKey, ListItem, Meta } from "./exploreState/entities";
10
12
  import { ApplySavedFilterPayload, PaginateTablePayload, PatchExploreResponsePayload, ProcessExploreResponsePayload, ResetExploreResponsePayload, UpdateEntityFiltersPayload, UpdateEntityViewAccessPayload, UpdateFilterPayload, UpdateRowPreviewPayload, UpdateRowSelectionPayload, UpdateSortingPayload } from "./exploreState/payloads/entities";
11
13
  export type CatalogState = string | undefined;
12
14
  /**
@@ -30,6 +32,7 @@ export type ExploreState = {
30
32
  filterState: SelectedFilter[];
31
33
  listItems: ListItems;
32
34
  loading: boolean;
35
+ meta: Meta | null;
33
36
  paginationState: PaginationState;
34
37
  rowPreview: RowPreviewState;
35
38
  tabValue: string;
@@ -96,12 +99,14 @@ export declare function ExploreStateProvider({ children, entityListType, }: {
96
99
  export declare enum ExploreActionKind {
97
100
  ApplySavedFilter = "APPLY_SAVED_FILTER",
98
101
  ClearFilters = "CLEAR_FILTERS",
102
+ ClearMeta = "CLEAR_META",
99
103
  PaginateTable = "PAGINATE_TABLE",
100
104
  PatchExploreResponse = "PATCH_EXPLORE_RESPONSE",
101
105
  ProcessExploreResponse = "PROCESS_EXPLORE_RESPONSE",
102
106
  ResetExploreResponse = "RESET_EXPLORE_RESPONSE",
103
107
  ResetState = "RESET_STATE",
104
108
  SelectEntityType = "SELECT_ENTITY_TYPE",
109
+ SyncStateFromUrl = "SYNC_STATE_FROM_URL",
105
110
  UpdateColumnVisibility = "UPDATE_COLUMN_VISIBILITY",
106
111
  UpdateEntityFilters = "UPDATE_ENTITY_FILTERS",
107
112
  UpdateEntityViewAccess = "UPDATE_ENTITY_VIEW_ACCESS",
@@ -114,7 +119,7 @@ export declare enum ExploreActionKind {
114
119
  /**
115
120
  * Explore action.
116
121
  */
117
- export type ExploreAction = ApplySavedFilterAction | ClearFiltersAction | PaginateTableAction | PatchExploreResponseAction | ProcessExploreResponseAction | ResetExploreResponseAction | ResetStateAction | SelectEntityTypeAction | UpdateColumnVisibilityAction | UpdateEntityFiltersAction | UpdateEntityViewAccessAction | UpdateFilterAction | UpdateGroupingAction | UpdateRowPreviewAction | UpdateRowSelectionAction | UpdateSortingAction;
122
+ export type ExploreAction = ApplySavedFilterAction | ClearFiltersAction | ClearMetaAction | PaginateTableAction | PatchExploreResponseAction | ProcessExploreResponseAction | ResetExploreResponseAction | ResetStateAction | SelectEntityTypeAction | SyncStateFromUrlAction | UpdateColumnVisibilityAction | UpdateEntityFiltersAction | UpdateEntityViewAccessAction | UpdateFilterAction | UpdateGroupingAction | UpdateRowPreviewAction | UpdateRowSelectionAction | UpdateSortingAction;
118
123
  /**
119
124
  * Apply saved filter action.
120
125
  */
@@ -3,8 +3,13 @@ import { useToken } from "../hooks/authentication/token/useToken";
3
3
  import { buildCategoryViews, buildNextFilterState, } from "../hooks/useCategoryFilter";
4
4
  import { useConfig } from "../hooks/useConfig";
5
5
  import { useURLFilterParams } from "../hooks/useURLFilterParams";
6
+ import { clearMetaAction } from "./exploreState/actions/clearMeta/action";
7
+ import { syncStateFromUrlAction } from "./exploreState/actions/syncStateFromUrl/action";
6
8
  import { updateGroupingAction } from "./exploreState/actions/updateGrouping/action";
7
9
  import { updateColumnVisibilityAction } from "./exploreState/actions/updateVisibility/action";
10
+ import { useBeforePopState } from "./exploreState/hooks/UseBeforePopState/useBeforePopState";
11
+ import { META_COMMAND } from "./exploreState/hooks/UseMetaCommands/types";
12
+ import { useMetaCommands } from "./exploreState/hooks/UseMetaCommands/useMetaCommands";
8
13
  import { DEFAULT_PAGINATION_STATE, INITIAL_STATE, } from "./exploreState/initializer/constants";
9
14
  import { initReducerArguments } from "./exploreState/initializer/utils";
10
15
  import { buildEntityStateSavedFilterState, buildNextSavedFilterState, closeRowPreview, getEntityCategoryGroupConfigKey, getEntityState, getEntityStateSavedProperty, getFilterCount, patchEntityListItems, resetPage, updateEntityPageState, updateEntityPageStateWithCommonCategoryGroupConfigKey, updateEntityStateByCategoryGroupConfigKey, updateSelectColumnVisibility, } from "./exploreState/utils";
@@ -51,6 +56,10 @@ export function ExploreStateProvider({ children, entityListType, }) {
51
56
  type: ExploreActionKind.ResetExploreResponse,
52
57
  });
53
58
  }, [exploreDispatch, token]);
59
+ // Meta-command related side effects.
60
+ useMetaCommands({ exploreDispatch, exploreState });
61
+ // Before pop state related side effects (forward / backward navigation by browser buttons).
62
+ useBeforePopState({ exploreDispatch, exploreState });
54
63
  return (React.createElement(ExploreStateContext.Provider, { value: exploreContextValue }, children));
55
64
  }
56
65
  /**
@@ -60,12 +69,14 @@ export var ExploreActionKind;
60
69
  (function (ExploreActionKind) {
61
70
  ExploreActionKind["ApplySavedFilter"] = "APPLY_SAVED_FILTER";
62
71
  ExploreActionKind["ClearFilters"] = "CLEAR_FILTERS";
72
+ ExploreActionKind["ClearMeta"] = "CLEAR_META";
63
73
  ExploreActionKind["PaginateTable"] = "PAGINATE_TABLE";
64
74
  ExploreActionKind["PatchExploreResponse"] = "PATCH_EXPLORE_RESPONSE";
65
75
  ExploreActionKind["ProcessExploreResponse"] = "PROCESS_EXPLORE_RESPONSE";
66
76
  ExploreActionKind["ResetExploreResponse"] = "RESET_EXPLORE_RESPONSE";
67
77
  ExploreActionKind["ResetState"] = "RESET_STATE";
68
78
  ExploreActionKind["SelectEntityType"] = "SELECT_ENTITY_TYPE";
79
+ ExploreActionKind["SyncStateFromUrl"] = "SYNC_STATE_FROM_URL";
69
80
  ExploreActionKind["UpdateColumnVisibility"] = "UPDATE_COLUMN_VISIBILITY";
70
81
  ExploreActionKind["UpdateEntityFilters"] = "UPDATE_ENTITY_FILTERS";
71
82
  ExploreActionKind["UpdateEntityViewAccess"] = "UPDATE_ENTITY_VIEW_ACCESS";
@@ -107,6 +118,7 @@ function exploreReducer(state, action, exploreContext) {
107
118
  entityPageState: updateEntityPageStateWithCommonCategoryGroupConfigKey(state, { grouping, rowPreview, rowSelection, sorting }),
108
119
  filterCount: getFilterCount(filterState),
109
120
  filterState,
121
+ meta: { command: META_COMMAND.NAVIGATE_TO_FILTERS },
110
122
  paginationState: resetPage(state.paginationState),
111
123
  rowPreview,
112
124
  };
@@ -129,10 +141,17 @@ function exploreReducer(state, action, exploreContext) {
129
141
  entityPageState: updateEntityPageStateWithCommonCategoryGroupConfigKey(state, { rowPreview, rowSelection }),
130
142
  filterCount,
131
143
  filterState,
144
+ meta: { command: META_COMMAND.NAVIGATE_TO_FILTERS },
132
145
  paginationState: resetPage(state.paginationState),
133
146
  rowPreview,
134
147
  };
135
148
  }
149
+ /**
150
+ * Clear meta
151
+ */
152
+ case ExploreActionKind.ClearMeta: {
153
+ return clearMetaAction(state, payload);
154
+ }
136
155
  /**
137
156
  * Paginate table
138
157
  **/
@@ -214,7 +233,8 @@ function exploreReducer(state, action, exploreContext) {
214
233
  **/
215
234
  case ExploreActionKind.SelectEntityType: {
216
235
  if (payload === state.tabValue) {
217
- return state;
236
+ // Update meta to match command "REPLACE_TO_FILTERS" - facilitates navigation to filters on return back to entity from elsewhere.
237
+ return { ...state, meta: { command: META_COMMAND.REPLACE_TO_FILTERS } };
218
238
  }
219
239
  const entityState = getEntityState(state, getEntityCategoryGroupConfigKey(payload, state.entityPageState));
220
240
  const rowPreview = closeRowPreview(state.rowPreview); // Close row preview, without updating the entity page state row preview.
@@ -226,11 +246,18 @@ function exploreReducer(state, action, exploreContext) {
226
246
  filterState: entityState.filterState,
227
247
  listItems: [],
228
248
  loading: true,
249
+ meta: { command: META_COMMAND.REPLACE_TO_FILTERS },
229
250
  paginationState: { ...resetPage(state.paginationState), rows: 0 },
230
251
  rowPreview,
231
252
  tabValue: payload,
232
253
  };
233
254
  }
255
+ /**
256
+ * Sync state from URL.
257
+ */
258
+ case ExploreActionKind.SyncStateFromUrl: {
259
+ return syncStateFromUrlAction(state, payload);
260
+ }
234
261
  /**
235
262
  * Update column visibility
236
263
  **/
@@ -262,6 +289,13 @@ function exploreReducer(state, action, exploreContext) {
262
289
  ...state,
263
290
  entityPageState: updateEntityPageStateWithCommonCategoryGroupConfigKey(state, { grouping, rowPreview, rowSelection, sorting }, categoryGroupConfigKey),
264
291
  rowPreview: closeRowPreview(state.rowPreview),
292
+ ...(payload.entityListType === state.tabValue
293
+ ? {
294
+ filterCount: getFilterCount(filterState),
295
+ filterState,
296
+ paginationState: { ...resetPage(state.paginationState), rows: 0 },
297
+ }
298
+ : {}),
265
299
  };
266
300
  }
267
301
  /**
@@ -290,6 +324,7 @@ function exploreReducer(state, action, exploreContext) {
290
324
  entityPageState: updateEntityPageStateWithCommonCategoryGroupConfigKey(state, { rowPreview, rowSelection }),
291
325
  filterCount: getFilterCount(filterState),
292
326
  filterState,
327
+ meta: { command: META_COMMAND.NAVIGATE_TO_FILTERS },
293
328
  paginationState: resetPage(state.paginationState),
294
329
  rowPreview,
295
330
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "32.0.0",
3
+ "version": "32.1.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -25,7 +25,7 @@ export const Index = ({
25
25
  }: IndexProps): JSX.Element => {
26
26
  const { onChange, viewMode, viewStatus } = useEntitiesView();
27
27
  const { dimensions } = useLayoutDimensions();
28
- const { table } = useTable();
28
+ const { table } = useTable({ entityListType });
29
29
  return (
30
30
  <IndexLayout className={className} marginTop={dimensions.header.height}>
31
31
  <Hero SideBarButton={SideBarButton} Summaries={Summaries} title={title} />
@@ -40,10 +40,12 @@ import { RowPreviewState } from "../../Table/features/RowPreview/entities";
40
40
  import { buildBaseColumnDef } from "../../TableCreator/common/utils";
41
41
  import { useTableOptions } from "../../TableCreator/options/hook";
42
42
  import { createCell } from "./coreOptions/columns/cellFactory";
43
- import { UseTable } from "./types";
43
+ import { UseTable, UseTableProps } from "./types";
44
44
 
45
- // eslint-disable-next-line sonarjs/cognitive-complexity -- TODO fix component length / complexity
46
- export const useTable = <T extends RowData>(): UseTable<T> => {
45
+ export const useTable = <T extends RowData>({
46
+ entityListType,
47
+ }: // eslint-disable-next-line sonarjs/cognitive-complexity -- TODO fix complexity
48
+ UseTableProps): UseTable<T> => {
47
49
  const { entityConfig } = useConfig();
48
50
  const exploreMode = useExploreMode();
49
51
  const { exploreDispatch, exploreState } = useExploreState();
@@ -209,6 +211,7 @@ export const useTable = <T extends RowData>(): UseTable<T> => {
209
211
 
210
212
  // Process explore response - client-side filtering only.
211
213
  useEffect(() => {
214
+ if (entityListType !== tabValue) return;
212
215
  if (!listItems || listItems.length === 0) return;
213
216
  if (clientFiltering) {
214
217
  exploreDispatch({
@@ -229,9 +232,11 @@ export const useTable = <T extends RowData>(): UseTable<T> => {
229
232
  allColumns,
230
233
  clientFiltering,
231
234
  columnFilters,
235
+ entityListType,
232
236
  exploreDispatch,
233
237
  listItems,
234
238
  rows,
239
+ tabValue,
235
240
  ]);
236
241
 
237
242
  return { table };
@@ -3,3 +3,7 @@ import { RowData, Table } from "@tanstack/react-table";
3
3
  export interface UseTable<T extends RowData> {
4
4
  table: Table<T>;
5
5
  }
6
+
7
+ export interface UseTableProps {
8
+ entityListType: string;
9
+ }
@@ -20,7 +20,6 @@ import { useEntityService } from "./useEntityService";
20
20
  import { EXPLORE_MODE, ExploreMode } from "./useExploreMode/types";
21
21
  import { useExploreMode } from "./useExploreMode/useExploreMode";
22
22
  import { useExploreState } from "./useExploreState";
23
- import { useURLFilterParams } from "./useURLFilterParams";
24
23
 
25
24
  /**
26
25
  * Hook handling the load and transformation of the values used by index pages. If the current entity loaded statically,
@@ -45,15 +44,8 @@ export const useEntityList = (
45
44
  } = useEntityService();
46
45
  const { exploreDispatch, exploreState } = useExploreState();
47
46
  const { data, isIdle, isLoading, run } = useAsync<AzulEntitiesResponse>();
48
- const {
49
- catalogState,
50
- entityPageState,
51
- featureFlagState,
52
- filterState,
53
- tabValue,
54
- } = exploreState;
47
+ const { entityPageState, filterState, tabValue } = exploreState;
55
48
  const { pagination, termFacets } = data || {};
56
- const { updateFilterQueryString } = useURLFilterParams();
57
49
  const { sorting } = entityPageState[tabValue];
58
50
  const entities = getEntities(staticData, data);
59
51
  const shouldDispatchResponse = isDispatchable(
@@ -63,11 +55,6 @@ export const useEntityList = (
63
55
  );
64
56
  const isFetching = isIdle || isLoading;
65
57
 
66
- // Update the filter query string when the filter state changes.
67
- useEffect(() => {
68
- updateFilterQueryString(catalogState, featureFlagState, filterState);
69
- }, [catalogState, featureFlagState, filterState, updateFilterQueryString]);
70
-
71
58
  // Fetch entities - on change of filter state - server-side fetching and server-side filtering.
72
59
  useEffect(() => {
73
60
  if (exploreMode === EXPLORE_MODE.SS_FETCH_SS_FILTERING) {
@@ -18,6 +18,7 @@ interface UseURLFilterParamsResult {
18
18
  /**
19
19
  * useURLFilterParams hook is used to keep track of the url search params, and update them,
20
20
  * if needed
21
+ * @deprecated - Avoid using this hook if possible; we intend on using ExploreState actions to manage URL state.
21
22
  * @returns an object containing a update function and the current filter
22
23
  */
23
24
  export const useURLFilterParams = (): UseURLFilterParamsResult => {
@@ -4,6 +4,7 @@ import { useURLFilterParams } from "./useURLFilterParams";
4
4
 
5
5
  /**
6
6
  * Updates URL search params when the filter state changes.
7
+ * @deprecated - Avoid using this hook if possible; we intend on using ExploreState actions to manage URL state.
7
8
  */
8
9
  export const useUpdateURLSearchParams = (): void => {
9
10
  const { exploreState } = useExploreState();
@@ -0,0 +1,18 @@
1
+ import { ExploreState } from "../../../exploreState";
2
+ import { ClearMetaPayload } from "./types";
3
+
4
+ /**
5
+ * Reducer function to handle the "clear meta" action.
6
+ * @param state - Explore State.
7
+ * @param payload - Payload.
8
+ * @returns explore state.
9
+ */
10
+ export function clearMetaAction(
11
+ state: ExploreState,
12
+ payload: ClearMetaPayload
13
+ ): ExploreState {
14
+ return {
15
+ ...state,
16
+ meta: payload,
17
+ };
18
+ }
@@ -0,0 +1,13 @@
1
+ import { ExploreActionKind } from "../../../exploreState";
2
+ import { ClearMetaAction } from "./types";
3
+
4
+ /**
5
+ * Action creator for clearing meta in the state.
6
+ * @returns Action with payload and action type.
7
+ */
8
+ export function clearMeta(): ClearMetaAction {
9
+ return {
10
+ payload: null,
11
+ type: ExploreActionKind.ClearMeta,
12
+ };
13
+ }
@@ -0,0 +1,8 @@
1
+ import { ExploreActionKind } from "../../../exploreState";
2
+
3
+ export type ClearMetaAction = {
4
+ payload: ClearMetaPayload;
5
+ type: ExploreActionKind.ClearMeta;
6
+ };
7
+
8
+ export type ClearMetaPayload = null;
@@ -0,0 +1,44 @@
1
+ import { ExploreState } from "../../../exploreState";
2
+ import {
3
+ getEntityCategoryGroupConfigKey,
4
+ updateEntityStateByCategoryGroupConfigKey,
5
+ } from "../../utils";
6
+ import { SyncStateFromUrlPayload } from "./types";
7
+ import { buildNextState } from "./utils";
8
+
9
+ /**
10
+ * Reducer function to handle the "sync state from URL" action.
11
+ * Updates the catalog state, feature flag state, and the payload entity's filter state.
12
+ * @param state - Explore State.
13
+ * @param payload - Payload.
14
+ * @returns explore state.
15
+ */
16
+ export function syncStateFromUrlAction(
17
+ state: ExploreState,
18
+ payload: SyncStateFromUrlPayload
19
+ ): ExploreState {
20
+ // Build the next state.
21
+ const nextState = buildNextState(state, payload);
22
+
23
+ // Grab the category group config key for the payload entityListType.
24
+ const categoryGroupConfigKey = getEntityCategoryGroupConfigKey(
25
+ payload.entityListType,
26
+ state.entityPageState
27
+ );
28
+
29
+ // Update the entity state by category group config key.
30
+ updateEntityStateByCategoryGroupConfigKey(
31
+ state,
32
+ {
33
+ filterState: payload.filterState,
34
+ savedFilterState: [],
35
+ },
36
+ categoryGroupConfigKey
37
+ );
38
+
39
+ // Return the updated state.
40
+ return {
41
+ ...state,
42
+ ...nextState,
43
+ };
44
+ }
@@ -0,0 +1,16 @@
1
+ import { ExploreActionKind } from "../../../exploreState";
2
+ import { SyncStateFromUrlAction, SyncStateFromUrlPayload } from "./types";
3
+
4
+ /**
5
+ * Action creator for syncing state from URL.
6
+ * @param payload - Payload.
7
+ * @returns Action with payload and action type.
8
+ */
9
+ export function syncStateFromUrl(
10
+ payload: SyncStateFromUrlPayload
11
+ ): SyncStateFromUrlAction {
12
+ return {
13
+ payload,
14
+ type: ExploreActionKind.SyncStateFromUrl,
15
+ };
16
+ }
@@ -0,0 +1,13 @@
1
+ import { ExploreActionKind, ExploreState } from "../../../exploreState";
2
+
3
+ export type SyncStateFromUrlAction = {
4
+ payload: SyncStateFromUrlPayload;
5
+ type: ExploreActionKind.SyncStateFromUrl;
6
+ };
7
+
8
+ export type SyncStateFromUrlPayload = Pick<
9
+ ExploreState,
10
+ "catalogState" | "featureFlagState" | "filterState"
11
+ > & {
12
+ entityListType: string;
13
+ };
@@ -0,0 +1,31 @@
1
+ import { ExploreState } from "../../../exploreState";
2
+ import { getFilterCount } from "../../../exploreState/utils";
3
+ import { SyncStateFromUrlPayload } from "./types";
4
+
5
+ /**
6
+ * Builds the next state, syncing the catalog state, feature flag state, and filter state.
7
+ * @param state - Explore state.
8
+ * @param payload - Payload.
9
+ * @returns state.
10
+ */
11
+ export function buildNextState(
12
+ state: ExploreState,
13
+ payload: SyncStateFromUrlPayload
14
+ ): Partial<ExploreState> {
15
+ // Initialize filter count and filter state from current state.
16
+ let filterCount = state.filterCount;
17
+ let filterState = state.filterState;
18
+
19
+ // Only update filter count and filter state if the payload entityListType matches the current tab value.
20
+ if (payload.entityListType === state.tabValue) {
21
+ filterCount = getFilterCount(payload.filterState);
22
+ filterState = payload.filterState;
23
+ }
24
+
25
+ return {
26
+ catalogState: payload.catalogState,
27
+ featureFlagState: payload.featureFlagState,
28
+ filterCount,
29
+ filterState,
30
+ };
31
+ }
@@ -1,3 +1,9 @@
1
+ export const EXPLORE_URL_PARAMS = {
2
+ CATALOG: "catalog",
3
+ FEATURE_FLAG: "ff",
4
+ FILTER: "filter",
5
+ } as const;
6
+
1
7
  export const SELECT_CATEGORY_KEY = {
2
8
  SAVED_FILTERS: "savedFilters",
3
9
  };
@@ -18,6 +18,7 @@ import {
18
18
  EntityPath,
19
19
  SavedFilter,
20
20
  } from "../../config/entities";
21
+ import { META_COMMAND } from "./hooks/UseMetaCommands/types";
21
22
 
22
23
  export interface EntityPageState {
23
24
  categoryGroupConfigKey: CategoryGroupConfigKey;
@@ -55,6 +56,10 @@ export type EntityStateSavedFilter = SavedFilter;
55
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO revisit when adding react query or similar
56
57
  export type ListItem = any;
57
58
 
59
+ export interface Meta {
60
+ command: META_COMMAND;
61
+ }
62
+
58
63
  export type SavedFilterByCategoryValueKey = Map<
59
64
  CategoryValueKey,
60
65
  EntityStateSavedFilter
@@ -0,0 +1,46 @@
1
+ import { useRouter } from "next/router";
2
+ import { useEffect } from "react";
3
+ import { ExploreStateContextProps } from "../../../exploreState";
4
+ import { syncStateFromUrl } from "../../../exploreState/actions/syncStateFromUrl/dispatch";
5
+ import { getSyncStateFromUrl } from "./utils";
6
+
7
+ /**
8
+ * useBeforePopState
9
+ *
10
+ * Keeps ExploreState, URL, and page props aligned when the user clicks
11
+ * the browser Back / Forward buttons.
12
+ *
13
+ * This hook runs inside `ExploreStateProvider` (mounted once in `_app`).
14
+ * It adds a single `router.beforePopState` handler that:
15
+ * - Parses the destination URL.
16
+ * - Dispatches `syncStateFromUrl` so filters, catalogue, and feature-flags in context match the URL.
17
+ * - Calls `router.replace(...{ shallow: false })` to trigger a full page transition, ensuring Next.js
18
+ * re-executes data-fetching methods that shallow routing would otherwise skip.
19
+ *
20
+ * The provider never unmounts, and a second call to `router.beforePopState` simply overwrites the handler.
21
+ * Cleaning up here would wipe out any handler registered later by other code.
22
+ */
23
+
24
+ export const useBeforePopState = ({
25
+ exploreDispatch,
26
+ }: ExploreStateContextProps): void => {
27
+ const router = useRouter();
28
+ useEffect(() => {
29
+ router.beforePopState(({ as, options, url }) => {
30
+ // Grab the expected state from URL.
31
+ const payload = getSyncStateFromUrl(url, as);
32
+ // Only dispatch if the url contains the dynamic segment `[entityListType]`.
33
+ if (payload.entityListType) {
34
+ // Sync state from URL.
35
+ exploreDispatch(syncStateFromUrl(payload));
36
+ // Force a full route transition to ensure page props are refreshed.
37
+ router.replace(url, as, { ...options, shallow: false });
38
+ return false;
39
+ }
40
+ // Return true to allow the popstate event to continue.
41
+ return true;
42
+ });
43
+ // No cleanup handler: see the JSDoc above for why we do not reset beforePopState here.
44
+ // Resetting would remove any handler registered by other parts of the app after this hook mounted.
45
+ }, [exploreDispatch, router]);
46
+ };
@@ -0,0 +1,93 @@
1
+ import { SelectedFilter } from "common/entities";
2
+ import { SyncStateFromUrlPayload } from "providers/exploreState/actions/syncStateFromUrl/types";
3
+ import { EXPLORE_URL_PARAMS } from "../../../exploreState/constants";
4
+
5
+ /**
6
+ * Returns the filters from the resolved URL.
7
+ * @param paramValue - The filter parameter value.
8
+ * @returns The filters or an empty array.
9
+ */
10
+ export function decodeFiltersParamValue(
11
+ paramValue: string | undefined
12
+ ): SelectedFilter[] {
13
+ return JSON.parse(decodeURIComponent(paramValue || "[]"));
14
+ }
15
+
16
+ /**
17
+ * Returns the dynamic segment from the resolved URL.
18
+ * @param pattern - The pattern URL.
19
+ * @param resolved - The resolved URL.
20
+ * @param dynamicSegment - The dynamic segment to extract.
21
+ * @returns The dynamic segment or an empty string.
22
+ */
23
+ function getDynamicSegment(
24
+ pattern: string,
25
+ resolved: string,
26
+ dynamicSegment = "[entityListType]"
27
+ ): string {
28
+ // Remove query strings.
29
+ const patternPath = pattern.split("?")[0];
30
+ const resolvedPath = resolved.split("?")[0];
31
+
32
+ // Check if the pattern ends with the dynamic segment e.g. /data/[entityListType].
33
+ // We are not interested in a pattern the suggests we are on an entity page e.g. /data/[entityListType]/[entityId].
34
+ if (!patternPath.endsWith(`/${dynamicSegment}`)) return "";
35
+
36
+ // Split into segments.
37
+ const patternSegments = patternPath.split("/");
38
+ const resolvedSegments = resolvedPath.split("/");
39
+
40
+ // Find the dynamic segment.
41
+ const idx = patternSegments.findIndex((seg) => seg === dynamicSegment);
42
+
43
+ if (idx === -1) return "";
44
+
45
+ return resolvedSegments[idx] || "";
46
+ }
47
+
48
+ /**
49
+ * Returns a parameter from the resolved URL.
50
+ * @param resolved - The resolved URL.
51
+ * @param param - The parameter to extract.
52
+ * @returns The parameter value or undefined.
53
+ */
54
+ export function getParamValue(
55
+ resolved: string,
56
+ param: string
57
+ ): string | undefined {
58
+ // Grab the params from the resolved URL.
59
+ const params = new URLSearchParams(resolved.split("?")[1]);
60
+ const paramValue = params.get(param);
61
+
62
+ // Return the parameter value or undefined.
63
+ if (!paramValue) return;
64
+
65
+ return paramValue;
66
+ }
67
+
68
+ /**
69
+ * Returns the entity list type, filters, and feature flag state from the URL.
70
+ * A dynamic segment that is not exactly `[entityListType]` is not supported will return entityListType as an empty string.
71
+ * @param url - The URL to extract from.
72
+ * @param as - The resolved URL.
73
+ * @returns An object containing the entity list type, filters, and feature flag state.
74
+ */
75
+ export function getSyncStateFromUrl(
76
+ url: string,
77
+ as: string
78
+ ): SyncStateFromUrlPayload {
79
+ // Get the entity list type from the url.
80
+ const entityListType = getDynamicSegment(url, as);
81
+
82
+ // Get the param values for each param.
83
+ const [catalogState, featureFlagState, filter] = [
84
+ EXPLORE_URL_PARAMS.CATALOG,
85
+ EXPLORE_URL_PARAMS.FEATURE_FLAG,
86
+ EXPLORE_URL_PARAMS.FILTER,
87
+ ].map((param) => getParamValue(as, param));
88
+
89
+ // Decode the filter param value to selected filters.
90
+ const filterState = decodeFiltersParamValue(filter);
91
+
92
+ return { catalogState, entityListType, featureFlagState, filterState };
93
+ }
@@ -0,0 +1,29 @@
1
+ import Router from "next/router";
2
+ import { ExploreState } from "../../../exploreState";
3
+ import { buildQuery } from "./utils";
4
+
5
+ /**
6
+ * Updates the URL query parameters for the entity list page based on the current explore state.
7
+ * Pushes the query to the router.
8
+ * @param exploreState - The current explore state.
9
+ */
10
+ export function navigateToFilters(exploreState: ExploreState): void {
11
+ // Build the query object.
12
+ const query = buildQuery(exploreState);
13
+
14
+ // Push the query to the router.
15
+ Router.push({ query }, undefined, { shallow: true });
16
+ }
17
+
18
+ /**
19
+ * Updates the URL query parameters for the entity list page based on the current explore state.
20
+ * Replaces the query to the router.
21
+ * @param exploreState - The current explore state.
22
+ */
23
+ export function replaceToFilters(exploreState: ExploreState): void {
24
+ // Build the query object.
25
+ const query = buildQuery(exploreState);
26
+
27
+ // Replace the query to the router.
28
+ Router.replace({ query }, undefined, { shallow: true });
29
+ }
@@ -0,0 +1,4 @@
1
+ export enum META_COMMAND {
2
+ NAVIGATE_TO_FILTERS = "NAVIGATE_TO_FILTERS",
3
+ REPLACE_TO_FILTERS = "REPLACE_TO_FILTERS",
4
+ }