From ffef748dc6a5cbe5a56273e16d5e432556f6f3f4 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Wed, 3 Jul 2024 14:15:44 -0400 Subject: [PATCH 01/29] migrated previous code --- src/js/components/BentoAppRouter.tsx | 9 +- src/js/components/SiteHeader.tsx | 38 +++++- src/js/constants/configConstants.ts | 1 + src/js/features/config/config.store.ts | 7 +- .../features/data/makeGetDataRequest.thunk.ts | 7 +- src/js/features/metadata/metadata.store.ts | 113 ++++++++++++++++++ .../search/makeGetSearchFields.thunk.ts | 19 +-- src/js/store.ts | 6 +- src/js/types/metadata.ts | 36 ++++++ 9 files changed, 214 insertions(+), 22 deletions(-) create mode 100644 src/js/features/metadata/metadata.store.ts create mode 100644 src/js/types/metadata.ts diff --git a/src/js/components/BentoAppRouter.tsx b/src/js/components/BentoAppRouter.tsx index 67ab9e5d..03dc1b6c 100644 --- a/src/js/components/BentoAppRouter.tsx +++ b/src/js/components/BentoAppRouter.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; import { Routes, Route } from 'react-router-dom'; import { useAutoAuthenticate, useIsAuthenticated } from 'bento-auth-js'; -import { useAppDispatch } from '@/hooks'; +import { useAppDispatch, useAppSelector } from '@/hooks'; import { makeGetConfigRequest, makeGetServiceInfoRequest } from '@/features/config/config.store'; import { makeGetAboutRequest } from '@/features/content/content.store'; @@ -11,6 +11,7 @@ import { makeGetProvenanceRequest } from '@/features/provenance/provenance.store import { getBeaconConfig } from '@/features/beacon/beaconConfig.store'; import { fetchGohanData, fetchKatsuData } from '@/features/ingestion/lastIngestion.store'; import { makeGetDataTypes } from '@/features/dataTypes/dataTypes.store'; +import { getProjects } from '@/features/metadata/metadata.store'; import PublicOverview from './Overview/PublicOverview'; import Search from './Search/Search'; @@ -24,6 +25,7 @@ const BentoAppRouter = () => { const { isAutoAuthenticating } = useAutoAuthenticate(); const isAuthenticated = useIsAuthenticated(); + const { selectedDatasetId, selectedProjectId } = useAppSelector((state) => state.metadata); useEffect(() => { dispatch(makeGetConfigRequest()).then(() => dispatch(getBeaconConfig())); @@ -33,6 +35,11 @@ const BentoAppRouter = () => { dispatch(makeGetProvenanceRequest()); dispatch(makeGetKatsuPublic()); dispatch(fetchKatsuData()); + }, [selectedDatasetId, selectedProjectId]); + + useEffect(() => { + dispatch(getProjects()); + dispatch(makeGetAboutRequest()); dispatch(fetchGohanData()); dispatch(makeGetServiceInfoRequest()); diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 1534869a..7ecc5702 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -1,24 +1,53 @@ -import React from 'react'; -import { Button, Flex, Layout, Typography, Space } from 'antd'; +import React, { useMemo } from 'react'; +import { Button, Cascader, Flex, Layout, Typography, Space } from 'antd'; const { Header } = Layout; import { useTranslation } from 'react-i18next'; import { DEFAULT_TRANSLATION, LNG_CHANGE, LNGS_FULL_NAMES } from '@/constants/configConstants'; -import { useAppSelector } from '@/hooks'; +import { useAppDispatch, useAppSelector } from '@/hooks'; import { useNavigate, useLocation } from 'react-router-dom'; import { useIsAuthenticated, usePerformAuth, usePerformSignOut } from 'bento-auth-js'; import { CLIENT_NAME, PORTAL_URL, TRANSLATED } from '@/config'; import { RiTranslate } from 'react-icons/ri'; import { ExportOutlined, LinkOutlined, LoginOutlined, LogoutOutlined } from '@ant-design/icons'; +import { selectDataset, selectProject } from '@/features/metadata/metadata.store'; const openPortalWindow = () => window.open(PORTAL_URL, '_blank'); -const SiteHeader = () => { +const SiteHeader: React.FC = () => { const { t, i18n } = useTranslation(DEFAULT_TRANSLATION); const navigate = useNavigate(); const location = useLocation(); + const dispatch = useAppDispatch(); const { isFetching: openIdConfigFetching } = useAppSelector((state) => state.openIdConfiguration); const { isHandingOffCodeForToken } = useAppSelector((state) => state.auth); + const { projects } = useAppSelector((state) => state.metadata); + const scopeOptions = useMemo(() => { + return projects.map((p) => ({ + value: p.identifier, + label: p.title, + children: p.datasets.map((d) => ({ + value: d.identifier, + label: d.title, + })), + })); + }, [projects]); + + const onScopeChange = (value: (string | number)[]) => { + if (value) { + if (value.length > 0) { + // first value is project + dispatch(selectProject(value[0] as string)); + } + if (value.length === 2) { + // second value is dataset + dispatch(selectDataset(value[1] as string)); + } + } else { + dispatch(selectProject('')); + dispatch(selectDataset('')); + } + }; const isAuthenticated = useIsAuthenticated(); const performSignOut = usePerformSignOut(); @@ -50,6 +79,7 @@ const SiteHeader = () => { > {CLIENT_NAME} + diff --git a/src/js/constants/configConstants.ts b/src/js/constants/configConstants.ts index 2d3cb501..c304ddd1 100644 --- a/src/js/constants/configConstants.ts +++ b/src/js/constants/configConstants.ts @@ -7,6 +7,7 @@ export const katsuPublicRulesUrl = `${PORTAL_URL}/api/metadata/api/public_rules` export const searchFieldsUrl = `${PORTAL_URL}/api/metadata/api/public_search_fields`; export const katsuUrl = `${PORTAL_URL}/api/metadata/api/public`; export const provenanceUrl = `${PORTAL_URL}/api/metadata/api/public_dataset`; +export const projectsUrl = `${PORTAL_URL}/api/metadata/api/projects`; export const katsuLastIngestionsUrl = '/katsu/data-types'; export const gohanLastIngestionsUrl = '/gohan/data-types'; diff --git a/src/js/features/config/config.store.ts b/src/js/features/config/config.store.ts index 6fdbd6eb..d8c39056 100644 --- a/src/js/features/config/config.store.ts +++ b/src/js/features/config/config.store.ts @@ -7,12 +7,11 @@ import { RootState } from '@/store'; import { PUBLIC_URL } from '@/config'; import { DiscoveryRules } from '@/types/configResponse'; -export const makeGetConfigRequest = createAsyncThunk( +export const makeGetConfigRequest = createAsyncThunk( 'config/getConfigData', - (_, { rejectWithValue }) => - // TODO: should be project/dataset scoped with url params + (_, { rejectWithValue, getState }) => axios - .get(katsuPublicRulesUrl) + .get(katsuPublicRulesUrl, { params: getState().metadata.params }) .then((res) => res.data) .catch(printAPIError(rejectWithValue)) ); diff --git a/src/js/features/data/makeGetDataRequest.thunk.ts b/src/js/features/data/makeGetDataRequest.thunk.ts index 9e5022a0..f002f3cb 100644 --- a/src/js/features/data/makeGetDataRequest.thunk.ts +++ b/src/js/features/data/makeGetDataRequest.thunk.ts @@ -10,14 +10,15 @@ import { ChartConfig } from '@/types/chartConfig'; import { ChartDataField, LocalStorageData, Sections } from '@/types/data'; import { Counts, OverviewResponse } from '@/types/overviewResponse'; import { printAPIError } from '@/utils/error.util'; +import { RootState } from '@/store'; export const makeGetDataRequestThunk = createAsyncThunk< { sectionData: Sections; counts: Counts; defaultData: Sections }, void, - { rejectValue: string } ->('data/makeGetDataRequest', async (_, { rejectWithValue }) => { + { rejectValue: string; state: RootState } +>('data/makeGetDataRequest', async (_, { rejectWithValue, getState }) => { const overviewResponse = (await axios - .get(katsuPublicOverviewUrl) + .get(katsuPublicOverviewUrl, { params: getState().metadata.params }) .then((res) => res.data) .catch(printAPIError(rejectWithValue))) as OverviewResponse['overview']; diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts new file mode 100644 index 00000000..4a50f670 --- /dev/null +++ b/src/js/features/metadata/metadata.store.ts @@ -0,0 +1,113 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { projectsUrl } from '@/constants/configConstants'; +import { Project, Dataset, PaginatedResponse } from '@/types/metadata'; +import { RootState } from '@/store'; +import axios from 'axios'; +import { printAPIError } from '@/utils/error.util'; + +interface MetadataState { + projects: Project[]; + datasets: Dataset[]; + projectsById: Record; + datasetsById: Record; + isFetching: boolean; + selectedProjectId: string; + selectedDatasetId: string; + params: { + project: string; + dataset: string; + }; +} + +const initialState: MetadataState = { + projects: [], + datasets: [], + datasetsById: {}, + projectsById: {}, + isFetching: true, + selectedProjectId: '', + selectedDatasetId: '', + params: { + project: '', + dataset: '', + }, +}; + +export const getProjects = createAsyncThunk< + PaginatedResponse, + void, + { state: RootState; rejectValue: string } +>('metadata/getProjects', (_, { rejectWithValue }) => { + return axios + .get(projectsUrl) + .then((res) => res.data) + .catch(printAPIError(rejectWithValue)); +}); + +const metadata = createSlice({ + name: 'metadata', + initialState, + reducers: { + selectProject: (state, { payload }: PayloadAction) => { + // Change project selection if valid + if (payload === '' || state.projectsById[payload]) { + // select project + state.selectedProjectId = payload; + state.params.project = payload; + // unselect dataset + state.selectedDatasetId = ''; + state.params.dataset = ''; + } else { + console.error(`Project ID ${payload} does not exist.`); + } + }, + selectDataset: (state, { payload }: PayloadAction) => { + const selectedProject = state.selectedProjectId; + // Change dataset selection if it is included in the selected project + if ( + payload === '' || + (selectedProject && state.projectsById[selectedProject].datasets.some((d) => d.identifier === payload)) + ) { + state.selectedDatasetId = payload; + state.params.dataset = payload; + } else { + console.error(`Dataset ID ${payload} does not exist, or is not a member of the selected project.`); + } + }, + }, + extraReducers(builder) { + // Projects + builder.addCase(getProjects.pending, (state) => { + state.isFetching = true; + }); + builder.addCase(getProjects.fulfilled, (state, { payload }) => { + const projects = payload?.results ?? []; + const datasets = projects.flatMap((p) => p?.datasets ?? []); + state.projects = projects; + state.datasets = datasets; + state.projectsById = Object.fromEntries(projects.map((p) => [p.identifier, p])); + state.datasetsById = Object.fromEntries(datasets.map((d) => [d.identifier, d])); + state.isFetching = false; + + // let selectedProjectId = state.selectedProjectId; + // if (!selectedProjectId) { + // // set default project + // selectedProjectId = state.projects.at(0)?.identifier ?? ''; + // state.selectedProjectId = selectedProjectId; + // state.params.project = selectedProjectId + // } + // if (!state.selectedDatasetId && selectedProjectId) { + // // set default dataset + // const datasetId = state.projectsById[state.selectedProjectId]?.datasets.at(0)?.identifier ?? ''; + // state.selectedDatasetId = datasetId; + // state.params.dataset = datasetId; + // } + }); + builder.addCase(getProjects.rejected, (state) => { + state.isFetching = false; + }); + }, +}); + +export const { selectProject, selectDataset } = metadata.actions; +export default metadata.reducer; diff --git a/src/js/features/search/makeGetSearchFields.thunk.ts b/src/js/features/search/makeGetSearchFields.thunk.ts index 6d9135d1..918a23b9 100644 --- a/src/js/features/search/makeGetSearchFields.thunk.ts +++ b/src/js/features/search/makeGetSearchFields.thunk.ts @@ -3,12 +3,15 @@ import axios from 'axios'; import { searchFieldsUrl } from '@/constants/configConstants'; import { printAPIError } from '@/utils/error.util'; import { SearchFieldResponse } from '@/types/search'; +import { RootState } from '@/store'; -export const makeGetSearchFields = createAsyncThunk( - 'query/makeGetSearchFields', - (_, { rejectWithValue }) => - axios - .get(searchFieldsUrl) - .then((res) => res.data) - .catch(printAPIError(rejectWithValue)) -); +export const makeGetSearchFields = createAsyncThunk< + SearchFieldResponse, + void, + { rejectValue: string; state: RootState } +>('query/makeGetSearchFields', async (_, { rejectWithValue, getState }) => { + return await axios + .get(searchFieldsUrl, { params: getState().metadata.params }) + .then((res) => res.data) + .catch(printAPIError(rejectWithValue)); +}); diff --git a/src/js/store.ts b/src/js/store.ts index a92d517b..5642f05c 100644 --- a/src/js/store.ts +++ b/src/js/store.ts @@ -14,8 +14,9 @@ import dataReducer from '@/features/data/data.store'; import queryReducer from '@/features/search/query.store'; import lastIngestionDataReducer from '@/features/ingestion/lastIngestion.store'; import provenanceReducer from '@/features/provenance/provenance.store'; -import beaconConfigReducer from './features/beacon/beaconConfig.store'; -import beaconQueryReducer from './features/beacon/beaconQuery.store'; +import beaconConfigReducer from '@/features/beacon/beaconConfig.store'; +import beaconQueryReducer from '@/features/beacon/beaconQuery.store'; +import metadataReducer from '@/features/metadata/metadata.store'; import { getValue, saveValue } from './utils/localStorage'; interface PersistedState { @@ -41,6 +42,7 @@ export const store = configureStore({ lastIngestionData: lastIngestionDataReducer, beaconConfig: beaconConfigReducer, beaconQuery: beaconQueryReducer, + metadata: metadataReducer, }, preloadedState: persistedState, }); diff --git a/src/js/types/metadata.ts b/src/js/types/metadata.ts new file mode 100644 index 00000000..61c766a1 --- /dev/null +++ b/src/js/types/metadata.ts @@ -0,0 +1,36 @@ +interface DiscoveryOverview {} +interface DiscoverySearch {} +interface DiscoveryFields {} +interface DiscoveryRules {} + +export interface Discovery { + overview: DiscoveryOverview[]; + search: DiscoverySearch[]; + fields: DiscoveryFields; + rules: DiscoveryRules; +} + +export interface Project { + identifier: string; + title: string; + description: string; + discovery: Discovery; + datasets: Dataset[]; +} + +export interface Dataset { + identifier: string; + title: string; + description: string; + discovery: Discovery; + dats_file: object; +} + +export interface PaginatedResponse { + count: number; + next: T; + previous: T; + results: T[]; +} + +export type ProjectsResponse = PaginatedResponse; From 354e8977ea9a06edf3484c93bd341b2869612e7d Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Wed, 3 Jul 2024 14:30:27 -0400 Subject: [PATCH 02/29] removed redundant state variables --- src/js/features/metadata/metadata.store.ts | 39 +++++----------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts index 4a50f670..ac138a7d 100644 --- a/src/js/features/metadata/metadata.store.ts +++ b/src/js/features/metadata/metadata.store.ts @@ -1,15 +1,12 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { projectsUrl } from '@/constants/configConstants'; -import { Project, Dataset, PaginatedResponse } from '@/types/metadata'; +import { PaginatedResponse, Project } from '@/types/metadata'; import { RootState } from '@/store'; import axios from 'axios'; import { printAPIError } from '@/utils/error.util'; interface MetadataState { projects: Project[]; - datasets: Dataset[]; - projectsById: Record; - datasetsById: Record; isFetching: boolean; selectedProjectId: string; selectedDatasetId: string; @@ -21,9 +18,6 @@ interface MetadataState { const initialState: MetadataState = { projects: [], - datasets: [], - datasetsById: {}, - projectsById: {}, isFetching: true, selectedProjectId: '', selectedDatasetId: '', @@ -50,7 +44,7 @@ const metadata = createSlice({ reducers: { selectProject: (state, { payload }: PayloadAction) => { // Change project selection if valid - if (payload === '' || state.projectsById[payload]) { + if (payload === '' || state.projects.find(({ identifier }) => identifier === payload)) { // select project state.selectedProjectId = payload; state.params.project = payload; @@ -66,7 +60,10 @@ const metadata = createSlice({ // Change dataset selection if it is included in the selected project if ( payload === '' || - (selectedProject && state.projectsById[selectedProject].datasets.some((d) => d.identifier === payload)) + (selectedProject && + state.projects + .find(({ identifier }) => identifier === selectedProject)! + .datasets.some((d) => d.identifier === payload)) ) { state.selectedDatasetId = payload; state.params.dataset = payload; @@ -76,32 +73,12 @@ const metadata = createSlice({ }, }, extraReducers(builder) { - // Projects builder.addCase(getProjects.pending, (state) => { state.isFetching = true; }); builder.addCase(getProjects.fulfilled, (state, { payload }) => { - const projects = payload?.results ?? []; - const datasets = projects.flatMap((p) => p?.datasets ?? []); - state.projects = projects; - state.datasets = datasets; - state.projectsById = Object.fromEntries(projects.map((p) => [p.identifier, p])); - state.datasetsById = Object.fromEntries(datasets.map((d) => [d.identifier, d])); + state.projects = payload?.results ?? []; state.isFetching = false; - - // let selectedProjectId = state.selectedProjectId; - // if (!selectedProjectId) { - // // set default project - // selectedProjectId = state.projects.at(0)?.identifier ?? ''; - // state.selectedProjectId = selectedProjectId; - // state.params.project = selectedProjectId - // } - // if (!state.selectedDatasetId && selectedProjectId) { - // // set default dataset - // const datasetId = state.projectsById[state.selectedProjectId]?.datasets.at(0)?.identifier ?? ''; - // state.selectedDatasetId = datasetId; - // state.params.dataset = datasetId; - // } }); builder.addCase(getProjects.rejected, (state) => { state.isFetching = false; From 75495081a8adefb68058b1d431c139c4460eddc0 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 15 Jul 2024 12:34:32 -0400 Subject: [PATCH 03/29] reorganized redux state --- src/js/components/SiteHeader.tsx | 19 +++---- src/js/features/config/config.store.ts | 9 +-- .../features/data/makeGetDataRequest.thunk.ts | 2 +- src/js/features/metadata/metadata.store.ts | 57 +++++++------------ .../search/makeGetSearchFields.thunk.ts | 2 +- 5 files changed, 35 insertions(+), 54 deletions(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 7ecc5702..9f9aebbb 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -9,7 +9,7 @@ import { useIsAuthenticated, usePerformAuth, usePerformSignOut } from 'bento-aut import { CLIENT_NAME, PORTAL_URL, TRANSLATED } from '@/config'; import { RiTranslate } from 'react-icons/ri'; import { ExportOutlined, LinkOutlined, LoginOutlined, LogoutOutlined } from '@ant-design/icons'; -import { selectDataset, selectProject } from '@/features/metadata/metadata.store'; +import { selectScope } from '@/features/metadata/metadata.store'; const openPortalWindow = () => window.open(PORTAL_URL, '_blank'); @@ -34,18 +34,13 @@ const SiteHeader: React.FC = () => { }, [projects]); const onScopeChange = (value: (string | number)[]) => { - if (value) { - if (value.length > 0) { - // first value is project - dispatch(selectProject(value[0] as string)); - } - if (value.length === 2) { - // second value is dataset - dispatch(selectDataset(value[1] as string)); - } + if (value.length === 1) { + dispatch(selectScope({ project: value[0] as string })); + } + if (value.length === 2) { + dispatch(selectScope({ project: value[0] as string, dataset: value[1] as string })); } else { - dispatch(selectProject('')); - dispatch(selectDataset('')); + dispatch(selectScope({})); } }; diff --git a/src/js/features/config/config.store.ts b/src/js/features/config/config.store.ts index d8c39056..6b6a5311 100644 --- a/src/js/features/config/config.store.ts +++ b/src/js/features/config/config.store.ts @@ -9,11 +9,12 @@ import { DiscoveryRules } from '@/types/configResponse'; export const makeGetConfigRequest = createAsyncThunk( 'config/getConfigData', - (_, { rejectWithValue, getState }) => - axios - .get(katsuPublicRulesUrl, { params: getState().metadata.params }) + (_, { rejectWithValue, getState }) => { + return axios + .get(katsuPublicRulesUrl, { params: getState().metadata.selectedScope }) .then((res) => res.data) - .catch(printAPIError(rejectWithValue)) + .catch(printAPIError(rejectWithValue)); + } ); export const makeGetServiceInfoRequest = createAsyncThunk< diff --git a/src/js/features/data/makeGetDataRequest.thunk.ts b/src/js/features/data/makeGetDataRequest.thunk.ts index f002f3cb..327b2747 100644 --- a/src/js/features/data/makeGetDataRequest.thunk.ts +++ b/src/js/features/data/makeGetDataRequest.thunk.ts @@ -18,7 +18,7 @@ export const makeGetDataRequestThunk = createAsyncThunk< { rejectValue: string; state: RootState } >('data/makeGetDataRequest', async (_, { rejectWithValue, getState }) => { const overviewResponse = (await axios - .get(katsuPublicOverviewUrl, { params: getState().metadata.params }) + .get(katsuPublicOverviewUrl, { params: getState().metadata.selectedScope }) .then((res) => res.data) .catch(printAPIError(rejectWithValue))) as OverviewResponse['overview']; diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts index ac138a7d..89fb8aaa 100644 --- a/src/js/features/metadata/metadata.store.ts +++ b/src/js/features/metadata/metadata.store.ts @@ -8,22 +8,18 @@ import { printAPIError } from '@/utils/error.util'; interface MetadataState { projects: Project[]; isFetching: boolean; - selectedProjectId: string; - selectedDatasetId: string; - params: { - project: string; - dataset: string; + selectedScope: { + project: string | undefined; + dataset: string | undefined; }; } const initialState: MetadataState = { projects: [], isFetching: true, - selectedProjectId: '', - selectedDatasetId: '', - params: { - project: '', - dataset: '', + selectedScope: { + project: undefined, + dataset: undefined, }, }; @@ -42,33 +38,22 @@ const metadata = createSlice({ name: 'metadata', initialState, reducers: { - selectProject: (state, { payload }: PayloadAction) => { - // Change project selection if valid - if (payload === '' || state.projects.find(({ identifier }) => identifier === payload)) { - // select project - state.selectedProjectId = payload; - state.params.project = payload; - // unselect dataset - state.selectedDatasetId = ''; - state.params.dataset = ''; - } else { - console.error(`Project ID ${payload} does not exist.`); - } - }, - selectDataset: (state, { payload }: PayloadAction) => { - const selectedProject = state.selectedProjectId; - // Change dataset selection if it is included in the selected project - if ( - payload === '' || - (selectedProject && + selectScope: (state, { payload }: PayloadAction<{ project?: string; dataset?: string }>) => { + if (payload.project && state.projects.find(({ identifier }) => identifier === payload.project)) { + state.selectedScope.project = payload.project; + if ( + payload.dataset && state.projects - .find(({ identifier }) => identifier === selectedProject)! - .datasets.some((d) => d.identifier === payload)) - ) { - state.selectedDatasetId = payload; - state.params.dataset = payload; + .find(({ identifier }) => identifier === payload.project)! + .datasets.find(({ identifier }) => identifier === payload.dataset) + ) { + state.selectedScope.dataset = payload.dataset; + } else { + state.selectedScope.dataset = undefined; + } } else { - console.error(`Dataset ID ${payload} does not exist, or is not a member of the selected project.`); + state.selectedScope.project = undefined; + state.selectedScope.dataset = undefined; } }, }, @@ -86,5 +71,5 @@ const metadata = createSlice({ }, }); -export const { selectProject, selectDataset } = metadata.actions; +export const { selectScope } = metadata.actions; export default metadata.reducer; diff --git a/src/js/features/search/makeGetSearchFields.thunk.ts b/src/js/features/search/makeGetSearchFields.thunk.ts index 918a23b9..81a21a2a 100644 --- a/src/js/features/search/makeGetSearchFields.thunk.ts +++ b/src/js/features/search/makeGetSearchFields.thunk.ts @@ -11,7 +11,7 @@ export const makeGetSearchFields = createAsyncThunk< { rejectValue: string; state: RootState } >('query/makeGetSearchFields', async (_, { rejectWithValue, getState }) => { return await axios - .get(searchFieldsUrl, { params: getState().metadata.params }) + .get(searchFieldsUrl, { params: getState().metadata.selectedScope }) .then((res) => res.data) .catch(printAPIError(rejectWithValue)); }); From b15000fa4378df7076e248137497a11c31ee440e Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Wed, 17 Jul 2024 12:50:10 -0400 Subject: [PATCH 04/29] corrected app load sequence --- src/js/index.tsx | 110 ++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/src/js/index.tsx b/src/js/index.tsx index 21a556cc..ea74a17f 100644 --- a/src/js/index.tsx +++ b/src/js/index.tsx @@ -51,17 +51,16 @@ const { Content } = Layout; const createSessionWorker = () => new Worker(new URL('./workers/tokenRefresh.ts', import.meta.url)); -const App = () => { +const BentoApp = () => { const navigate = useNavigate(); const [collapsed, setCollapsed] = useState(false); // TRANSLATION const { lang } = useParams<{ lang?: string }>(); - const { t, i18n } = useTranslation(DEFAULT_TRANSLATION); + const { i18n } = useTranslation(DEFAULT_TRANSLATION); useEffect(() => { console.debug('lang', lang); - if (lang && lang == 'callback') return; if (lang && LNGS_ARRAY.includes(lang)) { i18n.changeLanguage(lang); } else if (i18n.language) { @@ -71,51 +70,8 @@ const App = () => { } }, [lang, i18n.language, navigate]); - // AUTHENTICATION - const [signedOutModal, setSignedOutModal] = useState(false); - - const sessionWorker = useRef(null); - const signInWindow = useRef(null); - const windowMessageHandler = useRef<((event: MessageEvent) => void) | null>(null); - - const openSignInWindow = useOpenSignInWindowCallback(signInWindow, SIGN_IN_WINDOW_FEATURES); - - const popupOpenerAuthCallback = usePopupOpenerAuthCallback(); - const isInAuthPopup = checkIsInAuthPopup(PUBLIC_URL_NO_TRAILING_SLASH); - - useHandleCallback( - CALLBACK_PATH, - () => { - console.debug('authenticated'); - }, - isInAuthPopup ? popupOpenerAuthCallback : undefined, - (msg) => message.error(msg) - ); - - // Set up message handling from sign-in popup - useSignInPopupTokenHandoff(windowMessageHandler); - - useSessionWorkerTokenRefresh(sessionWorker, createSessionWorker, nop); - - useQueryWithAuthIfAllowed(); - return ( - { - setSignedOutModal(false); - }} - open={signedOutModal} - footer={[ - , - ]} - > - {t('Please sign in to the research portal.')} -
-
@@ -130,8 +86,7 @@ const App = () => { }> - } /> - } /> + } /> @@ -143,15 +98,62 @@ const App = () => { ); }; -const BentoApp = () => { - const { i18n } = useTranslation(); +const App = () => { + const { t, i18n } = useTranslation(DEFAULT_TRANSLATION); + console.log('i18n.language', i18n.language); + // AUTHENTICATION + const [signedOutModal, setSignedOutModal] = useState(false); + + const sessionWorker = useRef(null); + const signInWindow = useRef(null); + const windowMessageHandler = useRef<((event: MessageEvent) => void) | null>(null); + + const openSignInWindow = useOpenSignInWindowCallback(signInWindow, SIGN_IN_WINDOW_FEATURES); + + const popupOpenerAuthCallback = usePopupOpenerAuthCallback(); + const isInAuthPopup = checkIsInAuthPopup(PUBLIC_URL_NO_TRAILING_SLASH); + + useHandleCallback( + CALLBACK_PATH, + () => { + console.debug('authenticated'); + }, + isInAuthPopup ? popupOpenerAuthCallback : undefined, + (msg) => message.error(msg) + ); + + // Set up message handling from sign-in popup + useSignInPopupTokenHandoff(windowMessageHandler); + + useSessionWorkerTokenRefresh(sessionWorker, createSessionWorker, nop); + + useQueryWithAuthIfAllowed(); + return ( - - } /> - + <> + { + setSignedOutModal(false); + }} + open={signedOutModal} + footer={[ + , + ]} + > + {t('Please sign in to the research portal.')} +
+
+ + } /> + } /> + +
); }; @@ -170,7 +172,7 @@ root.render( authCallbackUrl: AUTH_CALLBACK_URL, }} > - + From 3eb72db2a9404d7ae88620a33eafee699c42fb82 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 22 Jul 2024 15:46:12 -0400 Subject: [PATCH 05/29] added routing for projects and datasets --- src/js/components/BentoAppRouter.tsx | 73 ++++++++++++++++++---- src/js/components/Overview/Chart.tsx | 2 + src/js/components/SiteHeader.tsx | 26 ++++++-- src/js/components/SiteSider.tsx | 15 +++-- src/js/features/metadata/metadata.store.ts | 21 ++----- src/js/index.tsx | 6 +- src/js/utils/router.ts | 30 ++++++++- 7 files changed, 130 insertions(+), 43 deletions(-) diff --git a/src/js/components/BentoAppRouter.tsx b/src/js/components/BentoAppRouter.tsx index 03dc1b6c..df8823d9 100644 --- a/src/js/components/BentoAppRouter.tsx +++ b/src/js/components/BentoAppRouter.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { Routes, Route } from 'react-router-dom'; +import { Routes, Route, useNavigate, useParams, Outlet } from 'react-router-dom'; import { useAutoAuthenticate, useIsAuthenticated } from 'bento-auth-js'; import { useAppDispatch, useAppSelector } from '@/hooks'; @@ -11,7 +11,7 @@ import { makeGetProvenanceRequest } from '@/features/provenance/provenance.store import { getBeaconConfig } from '@/features/beacon/beaconConfig.store'; import { fetchGohanData, fetchKatsuData } from '@/features/ingestion/lastIngestion.store'; import { makeGetDataTypes } from '@/features/dataTypes/dataTypes.store'; -import { getProjects } from '@/features/metadata/metadata.store'; +import { getProjects, selectScope } from '@/features/metadata/metadata.store'; import PublicOverview from './Overview/PublicOverview'; import Search from './Search/Search'; @@ -19,13 +19,47 @@ import ProvenanceTab from './Provenance/ProvenanceTab'; import BeaconQueryUi from './Beacon/BeaconQueryUi'; import { BentoRoute } from '@/types/routes'; import Loader from '@/components/Loader'; +import { validProjectDataset } from '@/utils/router'; + +const ScopedRoute = () => { + const { projectId, datasetId } = useParams(); + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const { projects } = useAppSelector((state) => state.metadata); + + useEffect(() => { + // Update selectedScope based on URL parameters + const valid = validProjectDataset(projects, projectId, datasetId); + if (datasetId === valid.dataset && projectId === valid.project) { + dispatch(selectScope(valid)); + } else { + const oldpath = location.pathname.split('/').filter(Boolean); + const newPath = [oldpath[0]]; + + if (valid.dataset) { + newPath.push('p', valid.project as string, 'd', valid.dataset); + } else if (valid.project) { + newPath.push('p', valid.project); + } + + const oldPathLength = oldpath.length; + if (oldpath[oldPathLength - 3] === 'p' || oldpath[oldPathLength - 3] === 'd') { + newPath.push(oldpath[oldPathLength - 1]); + } + const newPathString = '/' + newPath.join('/'); + navigate(newPathString, { replace: true }); + } + }, [projects, projectId, datasetId, dispatch]); + + return ; +}; const BentoAppRouter = () => { const dispatch = useAppDispatch(); const { isAutoAuthenticating } = useAutoAuthenticate(); const isAuthenticated = useIsAuthenticated(); - const { selectedDatasetId, selectedProjectId } = useAppSelector((state) => state.metadata); + const { selectedScope, isFetching: isFetchingProjects } = useAppSelector((state) => state.metadata); useEffect(() => { dispatch(makeGetConfigRequest()).then(() => dispatch(getBeaconConfig())); @@ -35,30 +69,47 @@ const BentoAppRouter = () => { dispatch(makeGetProvenanceRequest()); dispatch(makeGetKatsuPublic()); dispatch(fetchKatsuData()); - }, [selectedDatasetId, selectedProjectId]); + }, [selectedScope]); useEffect(() => { dispatch(getProjects()); dispatch(makeGetAboutRequest()); dispatch(fetchGohanData()); dispatch(makeGetServiceInfoRequest()); - if (isAuthenticated) { dispatch(makeGetDataTypes()); } }, [isAuthenticated]); - if (isAutoAuthenticating) { + if (isAutoAuthenticating || isFetchingProjects) { return ; } return ( - } /> - } /> - } /> - } /> - } /> + }> + } /> + } /> + } /> + } /> + } /> + + + }> + } /> + } /> + } /> + } /> + } /> + + + }> + } /> + } /> + } /> + } /> + } /> + ); }; diff --git a/src/js/components/Overview/Chart.tsx b/src/js/components/Overview/Chart.tsx index 18e54561..7d830a5c 100644 --- a/src/js/components/Overview/Chart.tsx +++ b/src/js/components/Overview/Chart.tsx @@ -13,10 +13,12 @@ import { CHART_TYPE_PIE, ChartConfig, } from '@/types/chartConfig'; +import { useAppSelector } from '@/hooks'; const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) => { const { t, i18n } = useTranslation(); const navigate = useNavigate(); + const { selectedScope } = useAppSelector((state) => state.metadata); const translateMap = ({ x, y }: { x: string; y: number }) => ({ x: t(x), y }); const removeMissing = ({ x }: { x: string }) => x !== 'missing'; const barChartOnClickHandler = (d: { payload: { x: string } }) => { diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 9f9aebbb..009a6398 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -21,7 +21,7 @@ const SiteHeader: React.FC = () => { const { isFetching: openIdConfigFetching } = useAppSelector((state) => state.openIdConfiguration); const { isHandingOffCodeForToken } = useAppSelector((state) => state.auth); - const { projects } = useAppSelector((state) => state.metadata); + const { projects, selectedScope } = useAppSelector((state) => state.metadata); const scopeOptions = useMemo(() => { return projects.map((p) => ({ value: p.identifier, @@ -34,14 +34,25 @@ const SiteHeader: React.FC = () => { }, [projects]); const onScopeChange = (value: (string | number)[]) => { + const oldpath = location.pathname.split('/').filter(Boolean); + const newPath = [oldpath[0]]; + console.log('oldpath', oldpath); if (value.length === 1) { dispatch(selectScope({ project: value[0] as string })); - } - if (value.length === 2) { + newPath.push('p', value[0] as string); + } else if (value.length === 2) { dispatch(selectScope({ project: value[0] as string, dataset: value[1] as string })); + newPath.push('p', value[0] as string, 'd', value[1] as string); } else { dispatch(selectScope({})); } + const oldPathLength = oldpath.length; + if (oldpath[oldPathLength - 3] === 'p' || oldpath[oldPathLength - 3] === 'd') { + newPath.push(oldpath[oldPathLength - 1]); + } + const newPathString = '/' + newPath.join('/'); + console.log('newPathString', newPathString); + navigate(newPathString, { replace: true }); }; const isAuthenticated = useIsAuthenticated(); @@ -74,7 +85,14 @@ const SiteHeader: React.FC = () => { > {CLIENT_NAME} - + typeof v === 'string')} + placeholder="Select Scope" + changeOnSelect + allowClear + />
diff --git a/src/js/components/SiteSider.tsx b/src/js/components/SiteSider.tsx index 76f91679..8de43c5b 100644 --- a/src/js/components/SiteSider.tsx +++ b/src/js/components/SiteSider.tsx @@ -31,10 +31,17 @@ const SiteSider: React.FC<{ const handleMenuClick: OnClick = useCallback( ({ key }: { key: string }) => { - const currentPath = location.pathname.split('/'); - const currentLang = currentPath[1]; - const newPath = `/${currentLang}/${key === BentoRoute.Overview ? '' : key}`; - navigate(key === BentoRoute.Search ? buildQueryParamsUrl(newPath, queryParams) : newPath); + const currentPath = location.pathname.split('/').filter(Boolean); + const newPath = [currentPath[0]]; + if (currentPath[1] == 'p') { + newPath.push('p', currentPath[2]); + } + if (currentPath[3] == 'd') { + newPath.push('d', currentPath[4]); + } + newPath.push(key); + const newPathString = '/' + newPath.join('/'); + navigate(key === BentoRoute.Search ? buildQueryParamsUrl(newPathString, queryParams) : newPathString); }, [navigate, queryParams, location.pathname] ); diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts index 89fb8aaa..3cd073e0 100644 --- a/src/js/features/metadata/metadata.store.ts +++ b/src/js/features/metadata/metadata.store.ts @@ -4,8 +4,10 @@ import { PaginatedResponse, Project } from '@/types/metadata'; import { RootState } from '@/store'; import axios from 'axios'; import { printAPIError } from '@/utils/error.util'; +import { stat } from 'copy-webpack-plugin/types/utils'; +import { validProjectDataset } from '@/utils/router'; -interface MetadataState { +export interface MetadataState { projects: Project[]; isFetching: boolean; selectedScope: { @@ -39,22 +41,7 @@ const metadata = createSlice({ initialState, reducers: { selectScope: (state, { payload }: PayloadAction<{ project?: string; dataset?: string }>) => { - if (payload.project && state.projects.find(({ identifier }) => identifier === payload.project)) { - state.selectedScope.project = payload.project; - if ( - payload.dataset && - state.projects - .find(({ identifier }) => identifier === payload.project)! - .datasets.find(({ identifier }) => identifier === payload.dataset) - ) { - state.selectedScope.dataset = payload.dataset; - } else { - state.selectedScope.dataset = undefined; - } - } else { - state.selectedScope.project = undefined; - state.selectedScope.dataset = undefined; - } + state.selectedScope = validProjectDataset(state.projects, payload.project, payload.dataset); }, }, extraReducers(builder) { diff --git a/src/js/index.tsx b/src/js/index.tsx index ea74a17f..b80f545c 100644 --- a/src/js/index.tsx +++ b/src/js/index.tsx @@ -84,11 +84,7 @@ const BentoApp = () => { }} > - }> - - } /> - - + diff --git a/src/js/utils/router.ts b/src/js/utils/router.ts index 534c8a6d..7cd00b26 100644 --- a/src/js/utils/router.ts +++ b/src/js/utils/router.ts @@ -1,11 +1,37 @@ import { BentoRoute } from '@/types/routes'; +import { MetadataState } from '@/features/metadata/metadata.store'; export const getCurrentPage = (): BentoRoute => { const pathArray = window.location.pathname.split('/'); const validPages = Object.values(BentoRoute); - if (pathArray.length > 2 && validPages.includes(pathArray[2] as BentoRoute)) { - return pathArray[2] as BentoRoute; + if (validPages.includes(pathArray[pathArray.length - 1] as BentoRoute)) { + return pathArray[pathArray.length - 1] as BentoRoute; } else { return BentoRoute.Overview; } }; + +export const validProjectDataset = ( + projects: MetadataState['projects'], + projectId?: string, + datasetId?: string +): MetadataState['selectedScope'] => { + const valid: MetadataState['selectedScope'] = { + project: undefined, + dataset: undefined, + }; + + if (projectId && projects.find(({ identifier }) => identifier === projectId)) { + valid.project = projectId; + if (datasetId) { + if ( + projects + .find(({ identifier }) => identifier === projectId)! + .datasets.find(({ identifier }) => identifier === datasetId) + ) { + valid.dataset = datasetId; + } + } + } + return valid; +}; From df495da3d22571df14b2890d8f5658cc4fde9013 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 25 Jul 2024 13:53:38 -0400 Subject: [PATCH 06/29] improved index.tsx --- src/js/components/BentoAppRouter.tsx | 47 +++--- src/js/components/Util/AuthOutlet.tsx | 74 +++++++++ src/js/components/Util/DefaultLayout.tsx | 35 +++++ src/js/components/Util/LanguageHandler.tsx | 33 ++++ src/js/index.tsx | 173 +++++---------------- 5 files changed, 204 insertions(+), 158 deletions(-) create mode 100644 src/js/components/Util/AuthOutlet.tsx create mode 100644 src/js/components/Util/DefaultLayout.tsx create mode 100644 src/js/components/Util/LanguageHandler.tsx diff --git a/src/js/components/BentoAppRouter.tsx b/src/js/components/BentoAppRouter.tsx index df8823d9..300b1fba 100644 --- a/src/js/components/BentoAppRouter.tsx +++ b/src/js/components/BentoAppRouter.tsx @@ -20,6 +20,7 @@ import BeaconQueryUi from './Beacon/BeaconQueryUi'; import { BentoRoute } from '@/types/routes'; import Loader from '@/components/Loader'; import { validProjectDataset } from '@/utils/router'; +import DefaultLayout from '@/components/Util/DefaultLayout'; const ScopedRoute = () => { const { projectId, datasetId } = useParams(); @@ -87,28 +88,30 @@ const BentoAppRouter = () => { return ( - }> - } /> - } /> - } /> - } /> - } /> - - - }> - } /> - } /> - } /> - } /> - } /> - - - }> - } /> - } /> - } /> - } /> - } /> + }> + }> + } /> + } /> + } /> + } /> + } /> + + + }> + } /> + } /> + } /> + } /> + } /> + + + }> + } /> + } /> + } /> + } /> + } /> + ); diff --git a/src/js/components/Util/AuthOutlet.tsx b/src/js/components/Util/AuthOutlet.tsx new file mode 100644 index 00000000..b916887b --- /dev/null +++ b/src/js/components/Util/AuthOutlet.tsx @@ -0,0 +1,74 @@ +import React, { useRef, useState } from 'react'; +import { useBeaconWithAuthIfAllowed, useTranslationDefault } from '@/hooks'; +import { + checkIsInAuthPopup, + nop, + useHandleCallback, + useOpenSignInWindowCallback, + usePopupOpenerAuthCallback, + useSessionWorkerTokenRefresh, + useSignInPopupTokenHandoff, +} from 'bento-auth-js'; +import { PUBLIC_URL_NO_TRAILING_SLASH } from '@/config'; +import { Button, message, Modal } from 'antd'; +import { Outlet } from 'react-router-dom'; + +const SIGN_IN_WINDOW_FEATURES = 'scrollbars=no, toolbar=no, menubar=no, width=800, height=600'; +const CALLBACK_PATH = '/callback'; + +const createSessionWorker = () => new Worker(new URL('./workers/tokenRefresh.ts', import.meta.url)); + +const AuthOutlet = () => { + const t = useTranslationDefault(); + + // AUTHENTICATION + const [signedOutModal, setSignedOutModal] = useState(false); + + const sessionWorker = useRef(null); + const signInWindow = useRef(null); + const windowMessageHandler = useRef<((event: MessageEvent) => void) | null>(null); + + const openSignInWindow = useOpenSignInWindowCallback(signInWindow, SIGN_IN_WINDOW_FEATURES); + + const popupOpenerAuthCallback = usePopupOpenerAuthCallback(); + const isInAuthPopup = checkIsInAuthPopup(PUBLIC_URL_NO_TRAILING_SLASH); + + useHandleCallback( + CALLBACK_PATH, + () => { + console.debug('authenticated'); + }, + isInAuthPopup ? popupOpenerAuthCallback : undefined, + (msg) => message.error(msg) + ); + + // Set up message handling from sign-in popup + useSignInPopupTokenHandoff(windowMessageHandler); + + useSessionWorkerTokenRefresh(sessionWorker, createSessionWorker, nop); + + useBeaconWithAuthIfAllowed(); + + return ( + <> + { + setSignedOutModal(false); + }} + open={signedOutModal} + footer={[ + , + ]} + > + {t('Please sign in to the research portal.')} +
+
+ + + ); +}; + +export default AuthOutlet; diff --git a/src/js/components/Util/DefaultLayout.tsx b/src/js/components/Util/DefaultLayout.tsx new file mode 100644 index 00000000..37d0fe8a --- /dev/null +++ b/src/js/components/Util/DefaultLayout.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import { Outlet } from 'react-router-dom'; +import { Layout } from 'antd'; +import SiteHeader from '@/components/SiteHeader'; +import SiteSider from '@/components/SiteSider'; +import SiteFooter from '@/components/SiteFooter'; + +const { Content } = Layout; + +const DefaultLayout = () => { + const [collapsed, setCollapsed] = useState(false); + + return ( + + + + + + + + + + + + + ); +}; + +export default DefaultLayout; diff --git a/src/js/components/Util/LanguageHandler.tsx b/src/js/components/Util/LanguageHandler.tsx new file mode 100644 index 00000000..62e61427 --- /dev/null +++ b/src/js/components/Util/LanguageHandler.tsx @@ -0,0 +1,33 @@ +import React, { useEffect } from 'react'; +import { Outlet, useNavigate, useParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { DEFAULT_TRANSLATION, SUPPORTED_LNGS } from '@/constants/configConstants'; + +const LNGS_ARRAY = Object.values(SUPPORTED_LNGS); + +const LanguageHandler: React.FC = () => { + const { lang } = useParams<{ lang?: string }>(); + const navigate = useNavigate(); + const { i18n } = useTranslation(DEFAULT_TRANSLATION); + + useEffect(() => { + const setLanguage = (newLang: string) => { + i18n.changeLanguage(newLang); + if (lang !== newLang) { + navigate(`/${newLang}/`, { replace: true }); + } + }; + + if (lang && LNGS_ARRAY.includes(lang)) { + setLanguage(lang); + } else { + const browserLang = navigator.language.split('-')[0]; + const defaultLang = LNGS_ARRAY.includes(browserLang) ? browserLang : SUPPORTED_LNGS.ENGLISH; + setLanguage(defaultLang); + } + }, [lang, i18n, navigate]); + + return ; +}; + +export default LanguageHandler; diff --git a/src/js/index.tsx b/src/js/index.tsx index b80f545c..9925bc0f 100644 --- a/src/js/index.tsx +++ b/src/js/index.tsx @@ -1,37 +1,26 @@ // React and ReactDOM imports -import React, { Suspense, useEffect, useRef, useState } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom/client'; // Redux and routing imports import { Provider } from 'react-redux'; -import { Routes, Route, useParams, useNavigate, BrowserRouter } from 'react-router-dom'; +import { Routes, Route, BrowserRouter } from 'react-router-dom'; // i18n and constants imports import { useTranslation } from 'react-i18next'; import { NEW_BENTO_PUBLIC_THEME } from '@/constants/overviewConstants'; -import { DEFAULT_TRANSLATION, SUPPORTED_LNGS } from '@/constants/configConstants'; +import { SUPPORTED_LNGS } from '@/constants/configConstants'; // Component imports -import { Button, ConfigProvider, Layout, Modal, message } from 'antd'; +import { ConfigProvider } from 'antd'; import { ChartConfigProvider } from 'bento-charts'; -import SiteHeader from '@/components/SiteHeader'; -import SiteFooter from '@/components/SiteFooter'; -import SiteSider from '@/components/SiteSider'; import Loader from '@/components/Loader'; import BentoAppRouter from '@/components/BentoAppRouter'; +import LanguageHandler from '@/components/Util/LanguageHandler'; +import AuthOutlet from '@/components/Util/AuthOutlet'; // Hooks and utilities imports -import { - useHandleCallback, - checkIsInAuthPopup, - useOpenSignInWindowCallback, - usePopupOpenerAuthCallback, - useSignInPopupTokenHandoff, - useSessionWorkerTokenRefresh, - BentoAuthContextProvider, - nop, -} from 'bento-auth-js'; -import { useQueryWithAuthIfAllowed } from '@/hooks'; +import { BentoAuthContextProvider } from 'bento-auth-js'; // Store and configuration imports import { store } from './store'; @@ -43,133 +32,45 @@ import 'bento-charts/src/styles.css'; import './i18n'; import '../styles.css'; -const SIGN_IN_WINDOW_FEATURES = 'scrollbars=no, toolbar=no, menubar=no, width=800, height=600'; -const CALLBACK_PATH = '/callback'; - -const LNGS_ARRAY = Object.values(SUPPORTED_LNGS); -const { Content } = Layout; - -const createSessionWorker = () => new Worker(new URL('./workers/tokenRefresh.ts', import.meta.url)); - -const BentoApp = () => { - const navigate = useNavigate(); - const [collapsed, setCollapsed] = useState(false); - - // TRANSLATION - const { lang } = useParams<{ lang?: string }>(); - const { i18n } = useTranslation(DEFAULT_TRANSLATION); - - useEffect(() => { - console.debug('lang', lang); - if (lang && LNGS_ARRAY.includes(lang)) { - i18n.changeLanguage(lang); - } else if (i18n.language) { - navigate(`/${i18n.language}/`, { replace: true }); - } else { - navigate(`/${SUPPORTED_LNGS.ENGLISH}/`, { replace: true }); - } - }, [lang, i18n.language, navigate]); - +const BaseRoutes: React.FC = () => { return ( - - - - - - - - - - - - - - + + }> + } /> + }> + } /> + + + ); }; -const App = () => { - const { t, i18n } = useTranslation(DEFAULT_TRANSLATION); - - console.log('i18n.language', i18n.language); - - // AUTHENTICATION - const [signedOutModal, setSignedOutModal] = useState(false); - - const sessionWorker = useRef(null); - const signInWindow = useRef(null); - const windowMessageHandler = useRef<((event: MessageEvent) => void) | null>(null); - - const openSignInWindow = useOpenSignInWindowCallback(signInWindow, SIGN_IN_WINDOW_FEATURES); - - const popupOpenerAuthCallback = usePopupOpenerAuthCallback(); - const isInAuthPopup = checkIsInAuthPopup(PUBLIC_URL_NO_TRAILING_SLASH); - - useHandleCallback( - CALLBACK_PATH, - () => { - console.debug('authenticated'); - }, - isInAuthPopup ? popupOpenerAuthCallback : undefined, - (msg) => message.error(msg) - ); - - // Set up message handling from sign-in popup - useSignInPopupTokenHandoff(windowMessageHandler); - - useSessionWorkerTokenRefresh(sessionWorker, createSessionWorker, nop); - - useQueryWithAuthIfAllowed(); +const RootApp: React.FC = () => { + const { i18n } = useTranslation(); return ( - - <> - { - setSignedOutModal(false); + + + - {t('Sign In')} - , - ]} > - {t('Please sign in to the research portal.')} -
-
- - } /> - } /> - - -
+ + + + + + + + ); }; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); -root.render( - - - - - - - -); +root.render(); From 0561f84fa272e9294c30a1a2633111fdfc100479 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 25 Jul 2024 14:00:26 -0400 Subject: [PATCH 07/29] provided correct navigation for chart clicks --- src/js/components/Overview/Chart.tsx | 7 ++++--- src/js/utils/router.ts | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/js/components/Overview/Chart.tsx b/src/js/components/Overview/Chart.tsx index 7d830a5c..f8a729c9 100644 --- a/src/js/components/Overview/Chart.tsx +++ b/src/js/components/Overview/Chart.tsx @@ -14,6 +14,7 @@ import { ChartConfig, } from '@/types/chartConfig'; import { useAppSelector } from '@/hooks'; +import { scopeToUrl } from '@/utils/router'; const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) => { const { t, i18n } = useTranslation(); @@ -22,10 +23,10 @@ const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) = const translateMap = ({ x, y }: { x: string; y: number }) => ({ x: t(x), y }); const removeMissing = ({ x }: { x: string }) => x !== 'missing'; const barChartOnClickHandler = (d: { payload: { x: string } }) => { - navigate(`/${i18n.language}/search?${id}=${d.payload.x}`); + navigate(`/${i18n.language}${scopeToUrl(selectedScope)}/search?${id}=${d.payload.x}`); }; const pieChartOnClickHandler = (d: { name: string }) => { - navigate(`/${i18n.language}/search?${id}=${d.name}`); + navigate(`/${i18n.language}${scopeToUrl(selectedScope)}/search?${id}=${d.name}`); }; const { chart_type: type } = chartConfig; @@ -60,7 +61,7 @@ const Chart = memo(({ chartConfig, data, units, id, isClickable }: ChartProps) = height={PIE_CHART_HEIGHT} preFilter={removeMissing} dataMap={translateMap} - {...(isClickable ? { onClick: pieChartOnClickHandler } : {})} + onClick={pieChartOnClickHandler} /> ); case CHART_TYPE_CHOROPLETH: { diff --git a/src/js/utils/router.ts b/src/js/utils/router.ts index 7cd00b26..6c12f392 100644 --- a/src/js/utils/router.ts +++ b/src/js/utils/router.ts @@ -35,3 +35,13 @@ export const validProjectDataset = ( } return valid; }; + +export const scopeToUrl = (scope: MetadataState['selectedScope']): string => { + if (scope.project && scope.dataset) { + return `/p/${scope.project}/d/${scope.dataset}`; + } else if (scope.project) { + return `/p/${scope.project}`; + } else { + return ''; + } +}; From 829df07345472619253f5918980d793d7a5300b3 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 25 Jul 2024 14:09:09 -0400 Subject: [PATCH 08/29] corrected allow clear button --- src/js/components/SiteHeader.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 009a6398..1b7f0a74 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -33,10 +33,13 @@ const SiteHeader: React.FC = () => { })); }, [projects]); - const onScopeChange = (value: (string | number)[]) => { + const onScopeChange = (value: (string | number)[] | undefined) => { + console.log('value', value); const oldpath = location.pathname.split('/').filter(Boolean); const newPath = [oldpath[0]]; - console.log('oldpath', oldpath); + if (value === undefined) { + value = []; + } if (value.length === 1) { dispatch(selectScope({ project: value[0] as string })); newPath.push('p', value[0] as string); @@ -51,7 +54,6 @@ const SiteHeader: React.FC = () => { newPath.push(oldpath[oldPathLength - 1]); } const newPathString = '/' + newPath.join('/'); - console.log('newPathString', newPathString); navigate(newPathString, { replace: true }); }; From 9854b2ebfba3eebfd33554be0ca3a58a991b61da Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 25 Jul 2024 14:09:36 -0400 Subject: [PATCH 09/29] removed redundant log --- src/js/components/SiteHeader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 1b7f0a74..29d36780 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -34,7 +34,6 @@ const SiteHeader: React.FC = () => { }, [projects]); const onScopeChange = (value: (string | number)[] | undefined) => { - console.log('value', value); const oldpath = location.pathname.split('/').filter(Boolean); const newPath = [oldpath[0]]; if (value === undefined) { From be247080d15486f13f4e6e98c038c1b9b5b8397e Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 29 Jul 2024 13:32:52 -0400 Subject: [PATCH 10/29] modified UI --- src/js/components/ChooseProjectModal.tsx | 78 ++++++++++++++++++++ src/js/components/SiteHeader.tsx | 80 +++++++++------------ src/js/features/metadata/metadata.store.ts | 1 - src/public/assets/database.png | Bin 0 -> 3417 bytes src/styles.css | 9 +++ 5 files changed, 120 insertions(+), 48 deletions(-) create mode 100644 src/js/components/ChooseProjectModal.tsx create mode 100644 src/public/assets/database.png diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx new file mode 100644 index 00000000..227a393c --- /dev/null +++ b/src/js/components/ChooseProjectModal.tsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react'; +import { Tabs, List, Avatar, Modal, Button, Space, Typography } from 'antd'; +import { useAppSelector } from '@/hooks'; +import { Link, useLocation } from 'react-router-dom'; + +const ChooseProjectModal: React.FC = ({ isModalOpen, setIsModalOpen }) => { + const location = useLocation(); + const { projects, selectedScope } = useAppSelector((state) => state.metadata); + const [selectedProject, setSelectedProject] = useState(selectedScope.project ?? projects[0].identifier); + + const baseURL = '/' + location.pathname.split('/')[1]; + + const closeModal = () => setIsModalOpen(false); + + return ( + + + + + +
+ } + > + setSelectedProject(key)} + items={projects.map(({ identifier, title, datasets, description }) => { + return { + key: identifier, + label: title, + children: ( + + + About {title} + + Select Project + + + {description} + ( + + + } + title={{item.title}} + description={item.description} + /> + + + )} + /> + + ), + }; + })} + /> + + ); +}; + +interface ChooseProjectModalProps { + isModalOpen: boolean; + setIsModalOpen: (isOpen: boolean) => void; +} + +export default ChooseProjectModal; diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 29d36780..4b740e71 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -1,15 +1,16 @@ -import React, { useMemo } from 'react'; -import { Button, Cascader, Flex, Layout, Typography, Space } from 'antd'; +import React, { useEffect } from 'react'; +import { Button, Flex, Layout, Typography, Space } from 'antd'; const { Header } = Layout; import { useTranslation } from 'react-i18next'; import { DEFAULT_TRANSLATION, LNG_CHANGE, LNGS_FULL_NAMES } from '@/constants/configConstants'; -import { useAppDispatch, useAppSelector } from '@/hooks'; +import { useAppSelector } from '@/hooks'; import { useNavigate, useLocation } from 'react-router-dom'; import { useIsAuthenticated, usePerformAuth, usePerformSignOut } from 'bento-auth-js'; import { CLIENT_NAME, PORTAL_URL, TRANSLATED } from '@/config'; import { RiTranslate } from 'react-icons/ri'; -import { ExportOutlined, LinkOutlined, LoginOutlined, LogoutOutlined } from '@ant-design/icons'; -import { selectScope } from '@/features/metadata/metadata.store'; +import { ExportOutlined, LinkOutlined, LoginOutlined, LogoutOutlined, ProfileOutlined } from '@ant-design/icons'; +import ChooseProjectModal from '@/components/ChooseProjectModal'; +import { scopeToUrl } from '@/utils/router'; const openPortalWindow = () => window.open(PORTAL_URL, '_blank'); @@ -17,45 +18,26 @@ const SiteHeader: React.FC = () => { const { t, i18n } = useTranslation(DEFAULT_TRANSLATION); const navigate = useNavigate(); const location = useLocation(); - const dispatch = useAppDispatch(); const { isFetching: openIdConfigFetching } = useAppSelector((state) => state.openIdConfiguration); const { isHandingOffCodeForToken } = useAppSelector((state) => state.auth); const { projects, selectedScope } = useAppSelector((state) => state.metadata); - const scopeOptions = useMemo(() => { - return projects.map((p) => ({ - value: p.identifier, - label: p.title, - children: p.datasets.map((d) => ({ - value: d.identifier, - label: d.title, - })), - })); - }, [projects]); - const onScopeChange = (value: (string | number)[] | undefined) => { - const oldpath = location.pathname.split('/').filter(Boolean); - const newPath = [oldpath[0]]; - if (value === undefined) { - value = []; - } - if (value.length === 1) { - dispatch(selectScope({ project: value[0] as string })); - newPath.push('p', value[0] as string); - } else if (value.length === 2) { - dispatch(selectScope({ project: value[0] as string, dataset: value[1] as string })); - newPath.push('p', value[0] as string, 'd', value[1] as string); - } else { - dispatch(selectScope({})); - } - const oldPathLength = oldpath.length; - if (oldpath[oldPathLength - 3] === 'p' || oldpath[oldPathLength - 3] === 'd') { - newPath.push(oldpath[oldPathLength - 1]); - } - const newPathString = '/' + newPath.join('/'); - navigate(newPathString, { replace: true }); + const scopeProps = { + projectTitle: projects.find((project) => project.identifier === selectedScope.project)?.title, + datasetTitle: selectedScope.dataset + ? projects + .find((project) => project.identifier === selectedScope.project) + ?.datasets.find((dataset) => dataset.identifier === selectedScope.dataset)?.title + : null, }; + const [isModalOpen, setIsModalOpen] = React.useState(false); + + useEffect(() => { + setIsModalOpen(false); + }, [location]); + const isAuthenticated = useIsAuthenticated(); const performSignOut = usePerformSignOut(); const performSignIn = usePerformAuth(); @@ -76,9 +58,8 @@ const SiteHeader: React.FC = () => { src="/public/assets/branding.png" alt="logo" style={{ height: '32px', verticalAlign: 'middle', transform: 'translateY(-3px)' }} - onClick={() => navigate('/')} + onClick={() => navigate(`/${i18n.language}${scopeToUrl(selectedScope)}`)} /> - { > {CLIENT_NAME} - typeof v === 'string')} - placeholder="Select Scope" - changeOnSelect - allowClear - /> + setIsModalOpen(true)} + > + + + {selectedScope.project && scopeProps.projectTitle} + {scopeProps.datasetTitle ? ` / ${scopeProps.datasetTitle}` : ''} + + ) + diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts index 3cd073e0..6f001ed6 100644 --- a/src/js/features/metadata/metadata.store.ts +++ b/src/js/features/metadata/metadata.store.ts @@ -4,7 +4,6 @@ import { PaginatedResponse, Project } from '@/types/metadata'; import { RootState } from '@/store'; import axios from 'axios'; import { printAPIError } from '@/utils/error.util'; -import { stat } from 'copy-webpack-plugin/types/utils'; import { validProjectDataset } from '@/utils/router'; export interface MetadataState { diff --git a/src/public/assets/database.png b/src/public/assets/database.png new file mode 100644 index 0000000000000000000000000000000000000000..5b9868db95f95af0f9b1a12e1d3a2386ef335ddb GIT binary patch literal 3417 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 Date: Mon, 29 Jul 2024 13:46:11 -0400 Subject: [PATCH 11/29] Added dataset title --- src/js/components/ChooseProjectModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index 227a393c..3d953bf2 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -46,6 +46,7 @@ const ChooseProjectModal: React.FC = ({ isModalOpen, se {description} + Datasets Date: Mon, 5 Aug 2024 15:04:57 -0400 Subject: [PATCH 12/29] corrected token refresh import url --- src/js/components/Util/AuthOutlet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/Util/AuthOutlet.tsx b/src/js/components/Util/AuthOutlet.tsx index b916887b..f84732a7 100644 --- a/src/js/components/Util/AuthOutlet.tsx +++ b/src/js/components/Util/AuthOutlet.tsx @@ -16,7 +16,7 @@ import { Outlet } from 'react-router-dom'; const SIGN_IN_WINDOW_FEATURES = 'scrollbars=no, toolbar=no, menubar=no, width=800, height=600'; const CALLBACK_PATH = '/callback'; -const createSessionWorker = () => new Worker(new URL('./workers/tokenRefresh.ts', import.meta.url)); +const createSessionWorker = () => new Worker(new URL('../../workers/tokenRefresh.ts', import.meta.url)); const AuthOutlet = () => { const t = useTranslationDefault(); From 4600d38ee12817d75e3dbb7b3652cde1e9c4520d Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 5 Aug 2024 15:21:14 -0400 Subject: [PATCH 13/29] removed React.FC --- src/js/components/ChooseProjectModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index 3d953bf2..37751a42 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -3,7 +3,7 @@ import { Tabs, List, Avatar, Modal, Button, Space, Typography } from 'antd'; import { useAppSelector } from '@/hooks'; import { Link, useLocation } from 'react-router-dom'; -const ChooseProjectModal: React.FC = ({ isModalOpen, setIsModalOpen }) => { +const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalProps) => { const location = useLocation(); const { projects, selectedScope } = useAppSelector((state) => state.metadata); const [selectedProject, setSelectedProject] = useState(selectedScope.project ?? projects[0].identifier); From 6e035c45dc93a5ab6ae19fa4b54849933fc46a0a Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 5 Aug 2024 16:02:49 -0400 Subject: [PATCH 14/29] updated modal UI --- src/js/components/ChooseProjectModal.tsx | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index 37751a42..9f45c699 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -13,26 +13,16 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP const closeModal = () => setIsModalOpen(false); return ( - - - - - - - } - > + setSelectedProject(key)} + tabBarExtraContent={ + + + + } items={projects.map(({ identifier, title, datasets, description }) => { return { key: identifier, @@ -42,7 +32,7 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP About {title} - Select Project + Select {description} From 922b637a65b601c2b4a2c47eecc4458fb6b4b513 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 5 Aug 2024 16:14:54 -0400 Subject: [PATCH 15/29] changed db icon to svg --- src/js/components/ChooseProjectModal.tsx | 3 ++- src/public/assets/database.png | Bin 3417 -> 0 bytes 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 src/public/assets/database.png diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index 9f45c699..9321f371 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Tabs, List, Avatar, Modal, Button, Space, Typography } from 'antd'; import { useAppSelector } from '@/hooks'; import { Link, useLocation } from 'react-router-dom'; +import { FaDatabase } from 'react-icons/fa'; const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalProps) => { const location = useLocation(); @@ -44,7 +45,7 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP } + avatar={} />} title={{item.title}} description={item.description} /> diff --git a/src/public/assets/database.png b/src/public/assets/database.png deleted file mode 100644 index 5b9868db95f95af0f9b1a12e1d3a2386ef335ddb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3417 zcmV-f4W{ymP)==uMz>Hm)4|H$zFx9k6<=Ks&~|G(}3Vb=e9+y9W^|Je2aP(>$;*k42DaJMF9ahC@ewl|1LKL6p&LPL2>r?Z=RXazP5xm&4FoK%W#fn^0;Jy zrF)(bObFrWRvKLV*tEmVzg}Y-ezuXvkJMwpp%8l)loEP+jq(i#u^Hhx$~?{o!1@B< zT+gEOMkCpR@SLPl)u9-?JGRTHSMQ8^^cr_#hHqppP2#v&B{p^j3dkM)z5)d9U(&=jikP*B>(1bSC z(>KiMq8BTY&_3|Z@G9}c-xmpUYiquW5jBqp!lU~MzLL>8eu2;lKE+ou2CH`gYYF`g z?aPn=9v>yViO=$tjNE@Q z*`}%zI^LcD3q()VU9L!&?~6|g6rHPTgtz?x7Km#0Mu{-mkBw=IM^uFVJ^>3vpN4Qp zZ;5xQ>~H1?ulog5LYyVsHJ}RO9N~_aXrn{Noh@(#bF~T1) zqmB63S!#sP`SyTUXvEr#5bEC^?h5Hk2rs|&5LSZWP52<;?bjZ{N_^1aKSXGJd#I~5 zsdhpm5q}Dhg>6@aMFfaAvzi=vn;uPMZODfcoYgENMF}EGU z1J!o`7p}h`?)7B*?)pTOo(^M#e>77F+)CC&ydHg>2(t6qZxd2XA%GRF&+Bm_i)6jF zC$U9H@Du`Eu^ENom4K1Aq8M6)XhRB2<<@yt#skmTa-TMYN9@P03veA0K875cRFsA= zLlVNBh)*iU4yM`Iya-R&pN%#L^oSe{f0?@%VS@YBICYXM((b|Q+Vn+;I~R`lDltO( zY+tugPeSC|e(C~mr9$MQdTyzngaPW~`X_4w3Dk&Qw&qEQv|alnWY8r6$jVj4#bfI#M`9PfwmnSAWw@dAA1Fzasq6y0bk0eAMoLJ-e@$2kF0PuoH-QjkOFZ$Cp z4Bj)z08LmPp(G*R)7|*CqQlc82*5$Qg!6F1xe?~kBR#;&BMjVaTm^XFUs@BQKd9rq zJ6=W0lT^9&J*)KV-_2UFEdG@qMAxZbOuXMdg%Nl+D?;oRm%TZe3xnZXz3T|*sr%O) z+~n<$i6@i?0){_VupXh5^wTR{yVR?BAWw^cFfIwPPBu13DCJbwF7ROfDG%X%WKeo@)?-YXPP%jAy?qjA2*NT9UOTyzn zO?WH_(cA7l6Be6qrak^?!bd@v;y-6982}-Q;H9)6r1%_|AMOfK+?9G7Gtft;Lc~s* zN+cy5gu!T{pvDtA){7+c1sq24ve>|xNR{!ykBq3C$9psxT0|KiU^29;w3bI(S(PfU z3?fDrZmC?TLPca{IKXE#$sOnEGf{!Fit=yMZ znJ8RWsyH&DSg$^${GCY5sm^FVrX1d}GJpWVI-7I6R_<0fRA!lEd3ltC(nLJH-hsI= z1nlopS6hZxC=3xHiA{bbH-^um*8tq|$6QQrI@w9e`1Ai|udM?Kp&%w~G1k6)|32m1 zckJk*v)2E}TKfhfLK1ZnR2{!v-CMdtg&SOx=zcq%&takSIXC0P&r+T7mX{H@mvpv% zL_yiqb6Dzp^#=W;<4*5ly07n(z0+0tf=p6S?r*`;WZ=?q=Y&#ulyw*4o8+`IJ&Q-K zREY>l-xdQpo$r3;daRWzQ)SKwJ-tM?sjD}lw*|?bW`eLm4P~;-?S8ZKe8!zmNvJc6 zUUHAbU^@$|f0EOyn;e zD%XG&i46pk$mb1+1{5Qrsinq`ME4d<&^2hPMRV){hY^)(0c(EDi$qmw%%HhHWaNlc zfF98uuoQT7mop(*ZEbl6Oj4Ll$n7m){D^IpZ!nAG>h@X?5(IrQc$A^yG?CuNGTInU z6nhbf3qlI#IS=b>x{fDlmBK22l>ccV#lUi+m?#jpynurgoWi0d9_z{+2VEUt~9o=J6jdbOYdzPt~m6Q@rfBu}gD1O9LhSxHE+ z_kNxaP0{#v^~+tA(lM`x#w+~l>qI~pQN;*1C*Y*%v;)9J>YLN1$FY;ZYk3{pIsF8L zRg5sf6Wu=JC!Ky?(v|4B7eD?xhRu|o!u-!nuBqM)BL=Rwlc(^6GU0+IR=?}Txb=FH zzFdgqU%=C|`1WsD)-8vECv-Kce2bo+<{q#`r5^x9*1L2>6Tb2-@JGZUPn0dKhgu~2 zNH=FRVOdX!Chuv?*H)O3>QNg4tvZh%#yfJO314|n*i`jtZ=&J+MUPC@gyM~ucSy5G z^f&oF!4kG|*U+19MStnZal$Zk9K|gUny|?~M0+x}@d%>{n;cT~XYG@)l?RM|U+j{w zlgo`wD&D16zmt!T{kixigvf=}si}ZHUnsWES|+47{;e(sHY1)wdks<6mx>sWe?fMq9!TKlcczSiB@JA>?8p)2X7}2lwMNp`mE|UF* z9t*tg!su%5$w+phE4B<@eWwYkZwLt-QZ$k55kcGNR&_PX+Lug5d1I?5#w6EHty_26 z`lU?Hqrcv&8j+yj8>?SR6|oACuCqCIM%C85y~S!1(luWJmfe=!T^MG-$Vw%5`mk+N z3%c}v9dp_PTmKPHgj$}$Ti2;iqHZg_jcc-3?ogqp*&~U} zKReH>n(X(;zXn!Wy@mcOyD7ULs;l2EtiNel{r6(A|F_-<|AYP(|3HtD_u_`VJ~KpF zG+$HC?bjpx(VlBh@!loYzt-=mUnzE=XV#;RJ`Kf>YX=K&G`S> z|M2%3$yQSDVM$V??(>4{Aj?QE*eLO8`obact%|sx<1mQ1rAD?jQO%M|0R((K|d1X_CzhY2F&FDN~RX1qw znrX@~O{n?%N1W?AF+(UDP3&9zF+(s%s9pDsI14yWs9pAqI1i{mxaV2q3Jn((2={Al zlv>-U5Ngpr5xG)ErxGDe!CPu6mN`%()belR3`j9wRgsXcpeT{l^Qu(|cXnQ!I1jET z+|!Fu;WfC1a6k5DQM~RFLe0_lzzF)HIL}3dyI84wVN=(Igqleen6*-{IEr;Cp>}e= zIgLD6oCv>|P}AbCO(U=sDFG`8H3Quke4Y5JW~XZiHO;0816?HW)|I1JM=0ZNKFbJf zZE2Xbgake8m+(QhI`DWkVR7EDKE=pWL@oz{@L4~7Yk`F-m4+fLOwQ)>$X!gm8$4mL zR^1J44gjj24-KKFoddEE^mo3Dpzag_VX>3zRPZJe_Y#sMBND Date: Thu, 8 Aug 2024 14:00:42 -0400 Subject: [PATCH 16/29] fixed compile issues with rebase --- src/js/components/Util/AuthOutlet.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/components/Util/AuthOutlet.tsx b/src/js/components/Util/AuthOutlet.tsx index f84732a7..f464bf33 100644 --- a/src/js/components/Util/AuthOutlet.tsx +++ b/src/js/components/Util/AuthOutlet.tsx @@ -1,5 +1,5 @@ import React, { useRef, useState } from 'react'; -import { useBeaconWithAuthIfAllowed, useTranslationDefault } from '@/hooks'; +import { useQueryWithAuthIfAllowed, useTranslationDefault } from '@/hooks'; import { checkIsInAuthPopup, nop, @@ -47,7 +47,7 @@ const AuthOutlet = () => { useSessionWorkerTokenRefresh(sessionWorker, createSessionWorker, nop); - useBeaconWithAuthIfAllowed(); + useQueryWithAuthIfAllowed(); return ( <> From 7233115910b497832dcd9ad5cd4c52aa6d1d5440 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 8 Aug 2024 14:04:10 -0400 Subject: [PATCH 17/29] fixed index url redirect --- src/js/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/js/index.tsx b/src/js/index.tsx index 9925bc0f..6c7cc1b3 100644 --- a/src/js/index.tsx +++ b/src/js/index.tsx @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom/client'; // Redux and routing imports import { Provider } from 'react-redux'; -import { Routes, Route, BrowserRouter } from 'react-router-dom'; +import { Routes, Route, BrowserRouter, Navigate } from 'react-router-dom'; // i18n and constants imports import { useTranslation } from 'react-i18next'; @@ -39,6 +39,7 @@ const BaseRoutes: React.FC = () => { } /> }> } /> + } /> From 772afedce1bae26099c828f6120b245e77f73731 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 8 Aug 2024 14:12:34 -0400 Subject: [PATCH 18/29] Added transitions to on hover effects to match antd design --- src/styles.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/styles.css b/src/styles.css index 7a6257b3..b97f6f91 100644 --- a/src/styles.css +++ b/src/styles.css @@ -23,11 +23,19 @@ body { border-right: 1px solid #f0f0f0; } +.select-project-title { + transition: all 0.2s; +} + .select-project-title:hover { color: white !important; cursor: pointer; } +.select-dataset-hover { + transition: all 0.2s; +} + .select-dataset-hover:hover { background-color: aliceblue !important; } From 73adcdbddb2c7740ac8db0e4d95198b186b4c27b Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 8 Aug 2024 16:07:15 -0400 Subject: [PATCH 19/29] PR css changes --- src/js/components/ChooseProjectModal.tsx | 10 +++++++--- src/js/components/SiteHeader.tsx | 4 ++-- src/styles.css | 5 +++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index 9321f371..50d5de58 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -21,7 +21,7 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP onChange={(key) => setSelectedProject(key)} tabBarExtraContent={ - + } items={projects.map(({ identifier, title, datasets, description }) => { @@ -31,13 +31,17 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP children: ( - About {title} + + About {title} + Select {description} - Datasets + + Datasets + { /> {CLIENT_NAME} @@ -70,7 +70,7 @@ const SiteHeader: React.FC = () => { setIsModalOpen(true)} > diff --git a/src/styles.css b/src/styles.css index b97f6f91..a9faaa22 100644 --- a/src/styles.css +++ b/src/styles.css @@ -5,6 +5,10 @@ body { margin: 0; } +.no-margin-top { + margin-top: 0 !important; +} + .container { width: 100%; max-width: 1325px; @@ -34,6 +38,7 @@ body { .select-dataset-hover { transition: all 0.2s; + border-radius: 7px; } .select-dataset-hover:hover { From 9200f5de00b27158e296c77855228febb043706e Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 8 Aug 2024 16:15:48 -0400 Subject: [PATCH 20/29] added translations --- src/js/components/ChooseProjectModal.tsx | 22 ++++++++++--------- .../locales/en/default_translation_en.json | 7 +++++- .../locales/fr/default_translation_fr.json | 7 +++++- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index 50d5de58..e4244fab 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -1,10 +1,12 @@ import React, { useState } from 'react'; import { Tabs, List, Avatar, Modal, Button, Space, Typography } from 'antd'; -import { useAppSelector } from '@/hooks'; +import { useAppSelector, useTranslationCustom, useTranslationDefault } from '@/hooks'; import { Link, useLocation } from 'react-router-dom'; import { FaDatabase } from 'react-icons/fa'; const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalProps) => { + const td = useTranslationDefault(); + const t = useTranslationCustom(); const location = useLocation(); const { projects, selectedScope } = useAppSelector((state) => state.metadata); const [selectedProject, setSelectedProject] = useState(selectedScope.project ?? projects[0].identifier); @@ -14,33 +16,33 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP const closeModal = () => setIsModalOpen(false); return ( - + setSelectedProject(key)} tabBarExtraContent={ - + } items={projects.map(({ identifier, title, datasets, description }) => { return { key: identifier, - label: title, + label: t(title), children: ( - About {title} + {td('About')} {t(title)} - Select + {td('Select')} - {description} + {t(description)} - Datasets + {td('Datasets')} } />} - title={{item.title}} - description={item.description} + title={{t(item.title)}} + description={t(item.description)} /> diff --git a/src/public/locales/en/default_translation_en.json b/src/public/locales/en/default_translation_en.json index c7d0db78..f1f519a7 100644 --- a/src/public/locales/en/default_translation_en.json +++ b/src/public/locales/en/default_translation_en.json @@ -91,5 +91,10 @@ "You have been signed out": "You have been signed out", "Please sign in to the research portal.": "Please sign in to the research portal.", "Sign In": "Sign In", - "Sign Out": "Sign Out" + "Sign Out": "Sign Out", + "Select Scope": "Select Scope", + "About": "About", + "Datasets": "Datasets", + "Clear": "Clear", + "Select": "Select" } diff --git a/src/public/locales/fr/default_translation_fr.json b/src/public/locales/fr/default_translation_fr.json index 852a5060..37a2abdf 100644 --- a/src/public/locales/fr/default_translation_fr.json +++ b/src/public/locales/fr/default_translation_fr.json @@ -91,5 +91,10 @@ "You have been signed out": "Vous avez été déconnecté", "Please sign in to the research portal.": "Veuillez vous connecter au portail de recherche.", "Sign In": "Se connecter", - "Sign Out": "Se déconnecter" + "Sign Out": "Se déconnecter", + "Select Scope": "Sélectionner un périmètre", + "About": "À propos", + "Datasets": "Jeux de données", + "Clear": "Effacer", + "Select": "Sélectionner" } From a58619c14cad72da1c212a8e81761fa0696cba85 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 19 Aug 2024 11:49:27 -0400 Subject: [PATCH 21/29] removed stray ) --- src/js/components/SiteHeader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index 71ddea53..d5e81b4c 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -78,7 +78,6 @@ const SiteHeader: React.FC = () => { {selectedScope.project && scopeProps.projectTitle} {scopeProps.datasetTitle ? ` / ${scopeProps.datasetTitle}` : ''} - ) From 1194e93a7e27d6cdcdee2f715afaa0ad1d959124 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 19 Aug 2024 11:55:05 -0400 Subject: [PATCH 22/29] added handling for null edge case --- src/js/components/ChooseProjectModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index e4244fab..a90dfc53 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -9,7 +9,7 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP const t = useTranslationCustom(); const location = useLocation(); const { projects, selectedScope } = useAppSelector((state) => state.metadata); - const [selectedProject, setSelectedProject] = useState(selectedScope.project ?? projects[0].identifier); + const [selectedProject, setSelectedProject] = useState(selectedScope.project ?? projects[0]?.identifier ?? null); const baseURL = '/' + location.pathname.split('/')[1]; From d7a7c6e1539e43bb50b62d3723ae85e1aacf9e7e Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 19 Aug 2024 12:01:51 -0400 Subject: [PATCH 23/29] Added type for selectedProject useState --- src/js/components/ChooseProjectModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index a90dfc53..bc0f49a9 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -9,7 +9,9 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP const t = useTranslationCustom(); const location = useLocation(); const { projects, selectedScope } = useAppSelector((state) => state.metadata); - const [selectedProject, setSelectedProject] = useState(selectedScope.project ?? projects[0]?.identifier ?? null); + const [selectedProject, setSelectedProject] = useState( + selectedScope.project ?? projects[0]?.identifier ?? undefined + ); const baseURL = '/' + location.pathname.split('/')[1]; From ab86cfae7567f7bba0adbabadea32574e5fda171 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 22 Aug 2024 11:16:23 -0400 Subject: [PATCH 24/29] Typed Discovery --- src/js/types/metadata.ts | 7 +++---- src/js/types/overviewResponse.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/js/types/metadata.ts b/src/js/types/metadata.ts index 61c766a1..85d4d2cb 100644 --- a/src/js/types/metadata.ts +++ b/src/js/types/metadata.ts @@ -1,7 +1,6 @@ -interface DiscoveryOverview {} -interface DiscoverySearch {} -interface DiscoveryFields {} -interface DiscoveryRules {} +import { Layout as DiscoveryOverview, Fields as DiscoveryFields } from '@/types/overviewResponse'; +import { Section as DiscoverySearch } from '@/types/search'; +import { DiscoveryRules } from '@/types/configResponse'; export interface Discovery { overview: DiscoveryOverview[]; diff --git a/src/js/types/overviewResponse.ts b/src/js/types/overviewResponse.ts index 999a2c4d..e1755685 100644 --- a/src/js/types/overviewResponse.ts +++ b/src/js/types/overviewResponse.ts @@ -48,7 +48,7 @@ export interface Datum { value: number; } -interface Layout { +export interface Layout { charts: ChartConfig[]; section_title: string; } From ed3101eaeed63e80e32ca13386335f86a2bf95c2 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 22 Aug 2024 13:18:33 -0400 Subject: [PATCH 25/29] Removed React.FC --- src/js/components/SiteHeader.tsx | 2 +- src/js/components/Util/LanguageHandler.tsx | 2 +- src/js/index.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index d5e81b4c..b52a9d77 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -14,7 +14,7 @@ import { scopeToUrl } from '@/utils/router'; const openPortalWindow = () => window.open(PORTAL_URL, '_blank'); -const SiteHeader: React.FC = () => { +const SiteHeader = () => { const { t, i18n } = useTranslation(DEFAULT_TRANSLATION); const navigate = useNavigate(); const location = useLocation(); diff --git a/src/js/components/Util/LanguageHandler.tsx b/src/js/components/Util/LanguageHandler.tsx index 62e61427..b478040e 100644 --- a/src/js/components/Util/LanguageHandler.tsx +++ b/src/js/components/Util/LanguageHandler.tsx @@ -5,7 +5,7 @@ import { DEFAULT_TRANSLATION, SUPPORTED_LNGS } from '@/constants/configConstants const LNGS_ARRAY = Object.values(SUPPORTED_LNGS); -const LanguageHandler: React.FC = () => { +const LanguageHandler = () => { const { lang } = useParams<{ lang?: string }>(); const navigate = useNavigate(); const { i18n } = useTranslation(DEFAULT_TRANSLATION); diff --git a/src/js/index.tsx b/src/js/index.tsx index 6c7cc1b3..5fadcb20 100644 --- a/src/js/index.tsx +++ b/src/js/index.tsx @@ -32,7 +32,7 @@ import 'bento-charts/src/styles.css'; import './i18n'; import '../styles.css'; -const BaseRoutes: React.FC = () => { +const BaseRoutes = () => { return ( }> @@ -46,7 +46,7 @@ const BaseRoutes: React.FC = () => { ); }; -const RootApp: React.FC = () => { +const RootApp = () => { const { i18n } = useTranslation(); return ( From cee5e0691515e74ccdcc767fbd74c51b6118196d Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 22 Aug 2024 13:18:52 -0400 Subject: [PATCH 26/29] provide null option for type --- src/js/types/metadata.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/types/metadata.ts b/src/js/types/metadata.ts index 85d4d2cb..66a8e3c1 100644 --- a/src/js/types/metadata.ts +++ b/src/js/types/metadata.ts @@ -13,7 +13,7 @@ export interface Project { identifier: string; title: string; description: string; - discovery: Discovery; + discovery: Discovery | null; datasets: Dataset[]; } @@ -21,7 +21,7 @@ export interface Dataset { identifier: string; title: string; description: string; - discovery: Discovery; + discovery: Discovery | null; dats_file: object; } From 86a634d75fc40794c3b4b11ab697072ce01943d0 Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 22 Aug 2024 13:20:00 -0400 Subject: [PATCH 27/29] Removed React.useState --- src/js/components/SiteHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index b52a9d77..c9e3baab 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -32,7 +32,7 @@ const SiteHeader = () => { : null, }; - const [isModalOpen, setIsModalOpen] = React.useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); useEffect(() => { setIsModalOpen(false); From 923d2e08a754fa995f39f532d8a724680693116b Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Thu, 22 Aug 2024 13:21:00 -0400 Subject: [PATCH 28/29] organized imports --- src/js/components/SiteHeader.tsx | 20 +++++++++++++------- src/js/features/metadata/metadata.store.ts | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/js/components/SiteHeader.tsx b/src/js/components/SiteHeader.tsx index c9e3baab..3105c4f8 100644 --- a/src/js/components/SiteHeader.tsx +++ b/src/js/components/SiteHeader.tsx @@ -1,17 +1,23 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; + import { Button, Flex, Layout, Typography, Space } from 'antd'; -const { Header } = Layout; import { useTranslation } from 'react-i18next'; -import { DEFAULT_TRANSLATION, LNG_CHANGE, LNGS_FULL_NAMES } from '@/constants/configConstants'; -import { useAppSelector } from '@/hooks'; -import { useNavigate, useLocation } from 'react-router-dom'; import { useIsAuthenticated, usePerformAuth, usePerformSignOut } from 'bento-auth-js'; -import { CLIENT_NAME, PORTAL_URL, TRANSLATED } from '@/config'; + import { RiTranslate } from 'react-icons/ri'; import { ExportOutlined, LinkOutlined, LoginOutlined, LogoutOutlined, ProfileOutlined } from '@ant-design/icons'; -import ChooseProjectModal from '@/components/ChooseProjectModal'; + +import { useAppSelector } from '@/hooks'; import { scopeToUrl } from '@/utils/router'; +import { DEFAULT_TRANSLATION, LNG_CHANGE, LNGS_FULL_NAMES } from '@/constants/configConstants'; +import { CLIENT_NAME, PORTAL_URL, TRANSLATED } from '@/config'; + +import ChooseProjectModal from '@/components/ChooseProjectModal'; + +const { Header } = Layout; + const openPortalWindow = () => window.open(PORTAL_URL, '_blank'); const SiteHeader = () => { diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts index 6f001ed6..1261dcf0 100644 --- a/src/js/features/metadata/metadata.store.ts +++ b/src/js/features/metadata/metadata.store.ts @@ -1,10 +1,10 @@ import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { projectsUrl } from '@/constants/configConstants'; +import axios from 'axios'; import { PaginatedResponse, Project } from '@/types/metadata'; import { RootState } from '@/store'; -import axios from 'axios'; import { printAPIError } from '@/utils/error.util'; import { validProjectDataset } from '@/utils/router'; +import { projectsUrl } from '@/constants/configConstants'; export interface MetadataState { projects: Project[]; From 514fae4d3a2afd220d29a97178b8fdb3efe7ff1d Mon Sep 17 00:00:00 2001 From: Sanjeev Lakhwani Date: Mon, 26 Aug 2024 10:51:38 -0400 Subject: [PATCH 29/29] enabled same page routing for ChooseProjectModal.tsx --- src/js/components/ChooseProjectModal.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/js/components/ChooseProjectModal.tsx b/src/js/components/ChooseProjectModal.tsx index bc0f49a9..0f77f076 100644 --- a/src/js/components/ChooseProjectModal.tsx +++ b/src/js/components/ChooseProjectModal.tsx @@ -3,6 +3,7 @@ import { Tabs, List, Avatar, Modal, Button, Space, Typography } from 'antd'; import { useAppSelector, useTranslationCustom, useTranslationDefault } from '@/hooks'; import { Link, useLocation } from 'react-router-dom'; import { FaDatabase } from 'react-icons/fa'; +import { getCurrentPage } from '@/utils/router'; const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalProps) => { const td = useTranslationDefault(); @@ -14,6 +15,7 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP ); const baseURL = '/' + location.pathname.split('/')[1]; + const page = getCurrentPage(); const closeModal = () => setIsModalOpen(false); @@ -38,7 +40,7 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP {td('About')} {t(title)} - + {td('Select')} @@ -50,11 +52,13 @@ const ChooseProjectModal = ({ isModalOpen, setIsModalOpen }: ChooseProjectModalP dataSource={datasets} bordered renderItem={(item) => ( - + } />} - title={{t(item.title)}} + title={ + {t(item.title)} + } description={t(item.description)} />