diff --git a/package.json b/package.json index 17d066937..9beb23ed2 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "url": "https://github.com/zimmerman-team/dx.client.git" }, "dependencies": { + "@auth0/auth0-react": "^2.2.1", "@craco/craco": "^6.4.3", "@devhammed/use-cookie": "^1.1.1", "@draft-js-plugins/anchor": "^4.1.4", @@ -162,7 +163,7 @@ "initialise": "yarn link '@rawgraphs/rawgraphs-charts' && yarn install --network-timeout 100000", "docker-staging": "cp -r /app/client/build /app/client/staging && tail -f /dev/null", "docker-test": "cp -r /app/client/build /app/client/test && tail -f /dev/null", - "docker-prod": "cp -r /app/client/build /app/client/prod" + "docker-prod": "cp -r /app/client/build /app/client/prod && tail -f /dev/null" }, "browserslist": { "production": [ diff --git a/src/app/Routes.tsx b/src/app/Routes.tsx index 5128d3b05..ff6d6ff27 100755 --- a/src/app/Routes.tsx +++ b/src/app/Routes.tsx @@ -2,11 +2,18 @@ // base import React, { Suspense, lazy } from "react"; -import { Switch } from "react-router-dom"; import { useScrollToTop } from "app/hooks/useScrollToTop"; import { PageLoader } from "app/modules/common/page-loader"; import { RouteWithAppBar } from "app/utils/RouteWithAppBar"; +import { Route, Switch, useHistory } from "react-router-dom"; import { NoMatchPage } from "app/modules/common/no-match-page"; +import { + AppState, + Auth0Provider, + User, + WithAuthenticationRequiredOptions, + withAuthenticationRequired, +} from "@auth0/auth0-react"; const HomeModule = lazy(() => import("app/modules/home-module")); const CasesModule = lazy( @@ -15,7 +22,6 @@ const CasesModule = lazy( const ContactModule = lazy( () => import("app/modules/home-module/sub-modules/contact") ); - const AboutModule = lazy( () => import("app/modules/home-module/sub-modules/about") ); @@ -25,8 +31,6 @@ const WhyDXModule = lazy( const ExploreAssetsModule = lazy( () => import("app/modules/home-module/sub-modules/explore-assets") ); -const DatasetsModule = lazy(() => import("app/modules/datasets-module")); -const ChartsModule = lazy(() => import("app/modules/charts-module")); const ChartModule = lazy(() => import("app/modules/chart-module")); const ReportModule = lazy(() => import("app/modules/report-module")); const DatasetUploadSteps = lazy( @@ -35,47 +39,105 @@ const DatasetUploadSteps = lazy( const EditMetaData = lazy( () => import("app/modules/datasets-module/editMetaData") ); +const AuthCallbackModule = lazy(() => import("app/modules/callback-module")); +const OnboardingModule = lazy(() => import("app/modules/onboarding-module")); +const UserProfileModule = lazy(() => import("app/modules/user-profile-module")); + +const ProtectedRoute = (props: { + component: React.ComponentType; + args?: WithAuthenticationRequiredOptions; +}) => { + const Component = withAuthenticationRequired(props.component, props.args); + + return ; +}; + +const Auth0ProviderWithRedirectCallback = (props: { + domain: string; + clientId: string; + authorizationParams: { + audience: string; + redirect_uri: string; + }; + children: React.ReactNode; +}) => { + const history = useHistory(); + + const onRedirectCallback = (appState?: AppState, user?: User) => { + history.push( + appState && appState.returnTo + ? appState.returnTo + : window.location.pathname + ); + }; + + return ( + + {props.children} + + ); +}; export function MainRoutes() { useScrollToTop(); return ( - }> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + }> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + /> + + + + + + ); } diff --git a/src/app/components/AppBar/index.tsx b/src/app/components/AppBar/index.tsx index fbb6694eb..3d7f3c804 100644 --- a/src/app/components/AppBar/index.tsx +++ b/src/app/components/AppBar/index.tsx @@ -1,27 +1,27 @@ import React from "react"; import get from "lodash/get"; -import { useRecoilState } from "recoil"; +import Grid from "@material-ui/core/Grid"; +import { useAuth0 } from "@auth0/auth0-react"; +import Popover from "@material-ui/core/Popover"; import Toolbar from "@material-ui/core/Toolbar"; import MUIAppBar from "@material-ui/core/AppBar"; import MenuItem from "@material-ui/core/MenuItem"; import { useCMSData } from "app/hooks/useCMSData"; import Container from "@material-ui/core/Container"; import IconButton from "@material-ui/core/IconButton"; -import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; import { withStyles } from "@material-ui/core/styles"; -import { homeDisplayAtom } from "app/state/recoil/atoms"; import Menu, { MenuProps } from "@material-ui/core/Menu"; import useMediaQuery from "@material-ui/core/useMediaQuery"; import IconChevronLeft from "@material-ui/icons/ChevronLeft"; +import { MobileAppbarSearch } from "app/components/Mobile/AppBarSearch"; +import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; +import { NavLink, useLocation, useHistory, Link } from "react-router-dom"; import { headercss, loginBtn, logocss, navLinkcss, } from "app/components/AppBar/style"; -import { MobileAppbarSearch } from "app/components/Mobile/AppBarSearch"; -import { NavLink, useLocation, useHistory, Link } from "react-router-dom"; -import { Grid, Popover } from "@material-ui/core"; const TextHeader = (label: string) => (

(null); + const navLocation = location.pathname.split("/").join(""); + function handleClick(event: React.MouseEvent) { setAnchorEl(event.currentTarget); } @@ -152,9 +153,6 @@ export function AppBar() { if (location.pathname === "/" && isMobile) { return ; } - const handlePath = (path: "charts" | "reports" | "data") => { - setDisplay(path); - }; return ( -
- handlePath("data")}> + Why Dx?
@@ -240,19 +237,18 @@ export function AppBar() { Explore Assets -
- handlePath("reports")}> + About
- handlePath("reports")}> + Cases
- handlePath("reports")}> + Contact
@@ -267,6 +263,9 @@ export function AppBar() { } const ActionMenu = () => { + const history = useHistory(); + const { user, isAuthenticated } = useAuth0(); + const [actionPopoverAnchorEl, setActionPopoverAnchorEl] = React.useState(null); @@ -280,10 +279,9 @@ const ActionMenu = () => {
{ :nth-child(1) { width: 146px; height: 34px; - border-radius: 24px 0px 0px 24px; + border-radius: ${isAuthenticated ? "24px 0px 0px 24px" : "24px"}; &:hover { opacity: 1; } @@ -318,18 +316,40 @@ const ActionMenu = () => { } `} > - - - - + + + {isAuthenticated && ( + + )} + {isAuthenticated && ( + + )}
{ actions.dataThemes.DatasetGetList.clear + ); + const clearCharts = useStoreActions( + (actions) => actions.charts.ChartGetList.clear + ); + const clearReports = useStoreActions( + (actions) => actions.reports.ReportGetList.clear + ); + + function clearAssets() { + setToken(""); + clearDatasets(); + clearCharts(); + clearReports(); + } + + function onLogout() { + clearAssets(); + logout({ + logoutParams: { + returnTo: window.location.origin, + }, + }); + } + return (
); }; + + if (chartError401 || Error401) { + return ( + <> +
+ + + ); + } + return ( ); } + +export default withAuthenticationRequired(ChartBuilderChartType); diff --git a/src/app/modules/chart-module/routes/customize/index.tsx b/src/app/modules/chart-module/routes/customize/index.tsx index 7a632edeb..fb124b838 100644 --- a/src/app/modules/chart-module/routes/customize/index.tsx +++ b/src/app/modules/chart-module/routes/customize/index.tsx @@ -12,8 +12,9 @@ import { styles as commonStyles } from "app/modules/chart-module/routes/common/s import { ChartBuilderCustomizeProps } from "app/modules/chart-module/routes/customize/data"; import { useRecoilState } from "recoil"; import { createChartFromReportAtom } from "app/state/recoil/atoms"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; -export function ChartBuilderCustomize(props: ChartBuilderCustomizeProps) { +function ChartBuilderCustomize(props: ChartBuilderCustomizeProps) { useTitle("DX DataXplorer - Customize"); const history = useHistory(); @@ -74,3 +75,5 @@ export function ChartBuilderCustomize(props: ChartBuilderCustomizeProps) {
); } + +export default withAuthenticationRequired(ChartBuilderCustomize); diff --git a/src/app/modules/chart-module/routes/data/index.tsx b/src/app/modules/chart-module/routes/data/index.tsx index 275c286e6..63fcaf7b2 100644 --- a/src/app/modules/chart-module/routes/data/index.tsx +++ b/src/app/modules/chart-module/routes/data/index.tsx @@ -2,12 +2,13 @@ import React from "react"; import useTitle from "react-use/lib/useTitle"; import { useStoreActions } from "app/state/store/hooks"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; /* project */ import { PageLoader } from "app/modules/common/page-loader"; import { styles } from "app/modules/chart-module/routes/data/styles"; import { styles as commonStyles } from "app/modules/chart-module/routes/common/styles"; -export function ChartModuleDataView() { +function ChartModuleDataView() { useTitle("DX DataXplorer - Select Data"); const setActivePanels = useStoreActions( @@ -49,3 +50,5 @@ export function ChartModuleDataView() {
); } + +export default withAuthenticationRequired(ChartModuleDataView); diff --git a/src/app/modules/chart-module/routes/export/index.tsx b/src/app/modules/chart-module/routes/export/index.tsx index 512723b86..949dbaf4a 100644 --- a/src/app/modules/chart-module/routes/export/index.tsx +++ b/src/app/modules/chart-module/routes/export/index.tsx @@ -3,13 +3,14 @@ import React from "react"; import isEmpty from "lodash/isEmpty"; import useTitle from "react-use/lib/useTitle"; import { useHistory, useParams } from "react-router-dom"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; /* project */ import { CommonChart } from "app/modules/chart-module/components/common-chart"; import { ChartBuilderExportProps } from "app/modules/chart-module/routes/export/data"; import { styles as commonStyles } from "app/modules/data-themes-module/sub-modules/theme-builder/views/common/styles"; -export function ChartBuilderExport(props: ChartBuilderExportProps) { +function ChartBuilderExport(props: ChartBuilderExportProps) { useTitle("DX DataXplorer - Export"); const history = useHistory(); @@ -56,3 +57,5 @@ export function ChartBuilderExport(props: ChartBuilderExportProps) { ); } + +export default withAuthenticationRequired(ChartBuilderExport); diff --git a/src/app/modules/chart-module/routes/filters/index.tsx b/src/app/modules/chart-module/routes/filters/index.tsx index 9936a336b..ee71ad32f 100644 --- a/src/app/modules/chart-module/routes/filters/index.tsx +++ b/src/app/modules/chart-module/routes/filters/index.tsx @@ -3,6 +3,7 @@ import React from "react"; import isEmpty from "lodash/isEmpty"; import useTitle from "react-use/lib/useTitle"; import { useHistory, useParams } from "react-router-dom"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; /* project */ import { CHART_DEFAULT_WIDTH } from "app/modules/chart-module/data"; @@ -11,7 +12,7 @@ import { CommonChart } from "app/modules/chart-module/components/common-chart"; import { styles as commonStyles } from "app/modules/chart-module/routes/common/styles"; import { ChartBuilderFiltersProps } from "app/modules/chart-module/routes/filters/data"; -export function ChartBuilderFilters(props: ChartBuilderFiltersProps) { +function ChartBuilderFilters(props: ChartBuilderFiltersProps) { useTitle("DX DataXplorer - Filters"); const history = useHistory(); @@ -70,3 +71,5 @@ export function ChartBuilderFilters(props: ChartBuilderFiltersProps) { ); } + +export default withAuthenticationRequired(ChartBuilderFilters); diff --git a/src/app/modules/chart-module/routes/lock/index.tsx b/src/app/modules/chart-module/routes/lock/index.tsx index 820e2c2e8..abb84bf90 100644 --- a/src/app/modules/chart-module/routes/lock/index.tsx +++ b/src/app/modules/chart-module/routes/lock/index.tsx @@ -3,13 +3,14 @@ import React from "react"; import isEmpty from "lodash/isEmpty"; import useTitle from "react-use/lib/useTitle"; import { useHistory, useParams } from "react-router-dom"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; /* project */ import { CommonChart } from "app/modules/chart-module/components/common-chart"; import { ChartBuilderLockProps } from "app/modules/chart-module/routes/lock/data"; import { styles as commonStyles } from "app/modules/chart-module/routes/common/styles"; -export function ChartBuilderLock(props: ChartBuilderLockProps) { +function ChartBuilderLock(props: ChartBuilderLockProps) { useTitle("DX DataXplorer - Lock"); const history = useHistory(); @@ -55,3 +56,5 @@ export function ChartBuilderLock(props: ChartBuilderLockProps) { ); } + +export default withAuthenticationRequired(ChartBuilderLock); diff --git a/src/app/modules/chart-module/routes/mapping/index.tsx b/src/app/modules/chart-module/routes/mapping/index.tsx index 6cc2147f7..98d539bf3 100644 --- a/src/app/modules/chart-module/routes/mapping/index.tsx +++ b/src/app/modules/chart-module/routes/mapping/index.tsx @@ -8,6 +8,7 @@ import uniqueId from "lodash/uniqueId"; import Grid from "@material-ui/core/Grid"; import useTitle from "react-use/lib/useTitle"; import { useHistory, useParams } from "react-router-dom"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; import { useStoreState, useStoreActions } from "app/state/store/hooks"; import { getTypeName, @@ -32,7 +33,7 @@ import { } from "app/modules/chart-module/routes/mapping/data"; import { useDebounce } from "react-use"; -export function ChartBuilderMapping(props: ChartBuilderMappingProps) { +function ChartBuilderMapping(props: ChartBuilderMappingProps) { useTitle("DX DataXplorer - Mapping"); const history = useHistory(); @@ -703,3 +704,5 @@ function ChartBuilderMappingDimensionStatic(
); } + +export default withAuthenticationRequired(ChartBuilderMapping); diff --git a/src/app/modules/common/not-authorized-message/index.tsx b/src/app/modules/common/not-authorized-message/index.tsx new file mode 100644 index 000000000..626865515 --- /dev/null +++ b/src/app/modules/common/not-authorized-message/index.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { WarningOutlined } from "@material-ui/icons"; + +export function NotAuthorizedMessageModule(props: { + asset: "chart" | "report"; +}) { + return ( +
+ +

You are not authorized to view this {props.asset}

+
+ ); +} diff --git a/src/app/modules/common/subheader-toolbar/SubheaderToolbar.tsx b/src/app/modules/common/subheader-toolbar/SubheaderToolbar.tsx new file mode 100644 index 000000000..237574ce5 --- /dev/null +++ b/src/app/modules/common/subheader-toolbar/SubheaderToolbar.tsx @@ -0,0 +1,628 @@ +import React from "react"; +import axios from "axios"; +import isEmpty from "lodash/isEmpty"; +import { useRecoilState } from "recoil"; +import { useSessionStorage } from "react-use"; +import { useAuth0 } from "@auth0/auth0-react"; +import Button from "@material-ui/core/Button"; +import SaveIcon from "@material-ui/icons/Save"; +import EditIcon from "@material-ui/icons/Edit"; +import Tooltip from "@material-ui/core/Tooltip"; +import Popover from "@material-ui/core/Popover"; +import ShareIcon from "@material-ui/icons/Share"; +import { LinkIcon } from "app/assets/icons/Link"; +import Snackbar from "@material-ui/core/Snackbar"; +import DeleteIcon from "@material-ui/icons/Delete"; +import Container from "@material-ui/core/Container"; +import IconButton from "@material-ui/core/IconButton"; +import CopyToClipboard from "react-copy-to-clipboard"; +import FileCopyIcon from "@material-ui/icons/FileCopy"; +import { PageLoader } from "app/modules/common/page-loader"; +import { Link, useHistory, useParams } from "react-router-dom"; +import SnackbarContent from "@material-ui/core/SnackbarContent"; +import { styles } from "app/modules/common/subheader-toolbar/styles"; +import { useStoreActions, useStoreState } from "app/state/store/hooks"; +import DeleteChartDialog from "app/components/Dialogs/deleteChartDialog"; +import DeleteReportDialog from "app/components/Dialogs/deleteReportDialog"; +import { ChartAPIModel, emptyChartAPI } from "app/modules/chart-module/data"; +import { SubheaderToolbarProps } from "app/modules/common/subheader-toolbar/data"; +import { ExportChartButton } from "app/modules/common/subheader-toolbar/exportButton"; +import { ISnackbarState } from "app/fragments/datasets-fragment/upload-steps/previewFragment"; +import { + homeDisplayAtom, + createChartFromReportAtom, + unSavedReportPreviewModeAtom, + reportRightPanelViewAtom, +} from "app/state/recoil/atoms"; +import { InfoSnackbar } from "."; + +export function SubheaderToolbar(props: SubheaderToolbarProps) { + const { user } = useAuth0(); + const history = useHistory(); + const token = useSessionStorage("authToken", "")[0]; + const { page, view } = useParams<{ page: string; view?: string }>(); + const [modalDisplay, setModalDisplay] = React.useState({ + report: false, + chart: false, + }); + const [enableButton, setEnableButton] = React.useState(false); + + const setHomeTab = useRecoilState(homeDisplayAtom)[1]; + const [createChartFromReport, setCreateChartFromReport] = useRecoilState( + createChartFromReportAtom + ); + const setRightPanelView = useRecoilState(reportRightPanelViewAtom)[1]; + + const setReportPreviewMode = useRecoilState(unSavedReportPreviewModeAtom)[1]; + + const [openSnackbar, setOpenSnackbar] = React.useState(false); + const [isSavedEnabled, setIsSavedEnabled] = React.useState(false); + const [isPreviewEnabled, setIsPreviewEnabled] = React.useState(false); + const [showSnackbar, setShowSnackbar] = React.useState(null); + const [duplicatedReportId, setDuplicatedReportId] = React.useState< + string | null + >(null); + const [duplicatedChartId, setDuplicatedChartId] = React.useState< + string | null + >(null); + const [anchorEl, setAnchorEl] = React.useState( + null + ); + + const mapping = useStoreState((state) => state.charts.mapping.value); + const dataset = useStoreState((state) => state.charts.dataset.value); + const appliedFilters = useStoreState( + (state) => state.charts.appliedFilters.value + ); + const enabledFilterOptionGroups = useStoreState( + (state) => state.charts.enabledFilterOptionGroups.value + ); + const activePanels = useStoreState( + (state) => state.charts.activePanels.value + ); + const selectedChartType = useStoreState( + (state) => state.charts.chartType.value + ); + + const loadReports = useStoreActions( + (actions) => actions.reports.ReportGetList.fetch + ); + + const loadCharts = useStoreActions( + (actions) => actions.charts.ChartGetList.fetch + ); + const loadedChart = useStoreState( + (state) => + (state.charts.ChartGet.crudData ?? emptyChartAPI) as ChartAPIModel + ); + const createChartData = useStoreState( + (state) => + (state.charts.ChartCreate.crudData ?? emptyChartAPI) as ChartAPIModel + ); + const createChartSuccess = useStoreState( + (state) => state.charts.ChartCreate.success + ); + const editChartSuccess = useStoreState( + (state) => state.charts.ChartUpdate.success + ); + const createOrEditChartLoading = useStoreState( + (state) => + state.charts.ChartCreate.loading || state.charts.ChartUpdate.loading + ); + + const createChart = useStoreActions( + (actions) => actions.charts.ChartCreate.post + ); + const editChart = useStoreActions( + (actions) => actions.charts.ChartUpdate.patch + ); + const createChartClear = useStoreActions( + (actions) => actions.charts.ChartCreate.clear + ); + const editChartClear = useStoreActions( + (actions) => actions.charts.ChartUpdate.clear + ); + + const [snackbarState, setSnackbarState] = React.useState({ + open: false, + vertical: "bottom", + horizontal: "center", + }); + + const onNameChange = (event: React.ChangeEvent) => { + props.setName(event.target.value); + }; + + const handleDeleteModalInputChange = ( + e: React.ChangeEvent + ) => { + if (e.target.value === "DELETE") { + setEnableButton(true); + } else { + setEnableButton(false); + } + }; + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleCopy = (text: string, result: boolean) => { + setOpenSnackbar(result); + }; + + const handleCloseSnackbar = () => { + setOpenSnackbar(false); + }; + + const onSave = () => { + if (props.onReportSave) { + props.onReportSave(); + return; + } + const chart = { + name: props.name, + authId: user?.sub, + vizType: selectedChartType, + mapping, + datasetId: dataset, + vizOptions: props.visualOptions || {}, + appliedFilters, + enabledFilterOptionGroups, + }; + if (view !== undefined && page !== "new") { + editChart({ + token, + patchId: page, + values: chart, + }); + } else { + createChart({ + token, + values: chart, + }); + } + + //completes chart creation, returns back to persisted report view + if (createChartFromReport.state) { + setCreateChartFromReport({ + ...createChartFromReport, + state: false, + }); + setRightPanelView("charts"); + if (createChartFromReport.view === undefined) { + history.push(`/report/${createChartFromReport.page}/edit`); + } else { + history.push( + `/report/${createChartFromReport.page}/${createChartFromReport.view}` + ); + } + } + }; + + React.useEffect(() => { + return () => { + createChartClear(); + editChartClear(); + }; + }, []); + + React.useEffect(() => { + const newValue = !isEmpty(selectedChartType) && !isEmpty(mapping); + + if (newValue !== isPreviewEnabled) { + setIsPreviewEnabled(newValue); + } + }, [selectedChartType, mapping]); + + React.useEffect(() => { + const newValue = + (!isEmpty(selectedChartType) && !isEmpty(mapping)) || + (view !== undefined && page !== "new" && props.name !== loadedChart.name); + if (newValue !== isSavedEnabled) { + setIsSavedEnabled(newValue); + } + }, [ + view, + props.name, + mapping, + activePanels, + loadedChart.name, + selectedChartType, + ]); + + React.useEffect(() => { + if ( + (createChartSuccess && + createChartData.id && + createChartData.id.length > 0) || + editChartSuccess + ) { + setShowSnackbar( + `Chart ${ + view !== undefined && page !== "new" ? "saved" : "created" + } successfully!` + ); + const id = createChartSuccess ? createChartData.id : page; + if (createChartFromReport.view === "") { + history.push(`/chart/${id}`); + } + } + }, [createChartSuccess, editChartSuccess, createChartData]); + + const open = Boolean(anchorEl); + const id = open ? "simple-popover" : undefined; + + const handleModalDisplay = () => { + if (props.pageType === "chart") { + setModalDisplay({ + ...modalDisplay, + chart: true, + }); + } else { + setModalDisplay({ + ...modalDisplay, + report: true, + }); + } + }; + + const handleDelete = () => { + setEnableButton(false); + if (props.pageType === "report") { + setModalDisplay({ + ...modalDisplay, + report: false, + }); + axios + .delete(`${process.env.REACT_APP_API}/report/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async () => { + loadReports({ + token, + storeInCrudData: true, + filterString: "filter[order]=createdDate desc", + }); + }) + .catch((error) => console.log(error)); + setHomeTab("reports"); + } else { + setModalDisplay({ + ...modalDisplay, + chart: false, + }); + axios + .delete(`${process.env.REACT_APP_API}/chart/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async () => { + loadCharts({ + token, + storeInCrudData: true, + filterString: "filter[order]=createdDate desc", + }); + }) + .catch((error) => console.log(error)); + setHomeTab("charts"); + } + history.replace("/"); + }; + + const handleDuplicate = () => { + if (props.pageType === "report") { + axios + .get(`${process.env.REACT_APP_API}/report/duplicate/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((response) => { + loadReports({ + token, + storeInCrudData: true, + filterString: "filter[order]=createdDate desc", + }); + + setDuplicatedReportId(response.data.id); + setSnackbarState({ + ...snackbarState, + open: true, + }); + }) + .catch((error) => console.log(error)); + } else { + axios + .get(`${process.env.REACT_APP_API}/chart/duplicate/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async (response) => { + loadCharts({ + token, + storeInCrudData: true, + filterString: "filter[order]=createdDate desc", + }); + setDuplicatedChartId(response.data.id); + setSnackbarState({ + ...snackbarState, + open: true, + }); + }) + .catch((error) => console.log(error)); + } + }; + + const handlePreviewMode = () => { + if (props.pageType === "report") { + setReportPreviewMode(true); + props.handlePersistReportState?.(); + if (props.setStopInitializeFramesWidth) { + props.setStopInitializeFramesWidth(true); + } + history.push(`/${props.pageType}/${page}/preview`); + } else { + history.push(`/${props.pageType}/${page}/preview`); + } + }; + + const handleBackToEdit = () => { + if (props.pageType === "report") { + setReportPreviewMode(false); + if (page === "new") { + history.push(`/report/new/create`); + } else { + history.push(`/${props.pageType}/${page}/${"edit"}`); + } + } else { + history.goBack(); + } + }; + + return ( +
+ {createOrEditChartLoading && } + setShowSnackbar(null)} + open={showSnackbar !== null && showSnackbar !== ""} + > + + {createChartFromReport.view === "" && ( + + )} + + } + /> + + + +
+ { + if (props.name === "Untitled report") { + e.currentTarget.value = ""; + } + }} + disabled={props.isPreviewView} + style={ + page !== "new" && !view + ? { + pointerEvents: "none", + } + : {} + } + /> + + {view !== "initial" && ( +
+ {view === "preview" && ( + <> + + + )} +
+ {(page === "new" || view) && ( + + + + + + + + + + + + + + + + + + + + )} + {page !== "new" && !view && ( + + + + + + + + + + + + + +
+ + + +
+
+ + + + + + + + + + +
+ )} +
+
+ )} +
+
+ setSnackbarState({ ...snackbarState, open: false })} + message={`${ + props.pageType === "report" ? "Report" : "Chart" + } has been duplicated successfully!`} + key={snackbarState.vertical + snackbarState.horizontal} + action={ + + } + /> + + +
+ ); +} diff --git a/src/app/modules/common/subheader-toolbar/index.tsx b/src/app/modules/common/subheader-toolbar/index.tsx index 32ead9dd4..d52aeea6c 100644 --- a/src/app/modules/common/subheader-toolbar/index.tsx +++ b/src/app/modules/common/subheader-toolbar/index.tsx @@ -4,6 +4,8 @@ import isEmpty from "lodash/isEmpty"; import { useRecoilState } from "recoil"; import styled from "styled-components/macro"; import Button from "@material-ui/core/Button"; +import { useSessionStorage } from "react-use"; +import { useAuth0 } from "@auth0/auth0-react"; import SaveIcon from "@material-ui/icons/Save"; import EditIcon from "@material-ui/icons/Edit"; import Tooltip from "@material-ui/core/Tooltip"; @@ -29,13 +31,12 @@ import { ExportChartButton } from "app/modules/common/subheader-toolbar/exportBu import { ISnackbarState } from "app/fragments/datasets-fragment/upload-steps/previewFragment"; import { homeDisplayAtom, - persistedReportStateAtom, createChartFromReportAtom, unSavedReportPreviewModeAtom, reportRightPanelViewAtom, } from "app/state/recoil/atoms"; -const InfoSnackbar = styled((props) => )` +export const InfoSnackbar = styled((props) => )` && { bottom: 40px; } @@ -83,23 +84,23 @@ const InfoSnackbar = styled((props) => )` `; export function SubheaderToolbar(props: SubheaderToolbarProps) { + const { user } = useAuth0(); const history = useHistory(); const { page, view } = useParams<{ page: string; view?: string }>(); + const token = useSessionStorage("authToken", "")[0]; const [modalDisplay, setModalDisplay] = React.useState({ report: false, chart: false, }); const [enableButton, setEnableButton] = React.useState(false); - const [_, setHomeTab] = useRecoilState(homeDisplayAtom); + const setHomeTab = useRecoilState(homeDisplayAtom)[1]; const [createChartFromReport, setCreateChartFromReport] = useRecoilState( createChartFromReportAtom ); const setRightPanelView = useRecoilState(reportRightPanelViewAtom)[1]; - const [__, setReportPreviewMode] = useRecoilState( - unSavedReportPreviewModeAtom - ); + const setReportPreviewMode = useRecoilState(unSavedReportPreviewModeAtom)[1]; const [openSnackbar, setOpenSnackbar] = React.useState(false); const [isSavedEnabled, setIsSavedEnabled] = React.useState(false); @@ -212,6 +213,7 @@ export function SubheaderToolbar(props: SubheaderToolbarProps) { } const chart = { name: props.name, + authId: user?.sub, vizType: selectedChartType, mapping, datasetId: dataset, @@ -221,11 +223,13 @@ export function SubheaderToolbar(props: SubheaderToolbarProps) { }; if (view !== undefined && page !== "new") { editChart({ + token, patchId: page, values: chart, }); } else { createChart({ + token, values: chart, }); } @@ -322,9 +326,14 @@ export function SubheaderToolbar(props: SubheaderToolbarProps) { report: false, }); axios - .delete(`${process.env.REACT_APP_API}/report/${page}`) - .then(() => { + .delete(`${process.env.REACT_APP_API}/report/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async () => { loadReports({ + token, storeInCrudData: true, filterString: "filter[order]=createdDate desc", }); @@ -337,9 +346,14 @@ export function SubheaderToolbar(props: SubheaderToolbarProps) { chart: false, }); axios - .delete(`${process.env.REACT_APP_API}/chart/${page}`) - .then(() => { + .delete(`${process.env.REACT_APP_API}/chart/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async () => { loadCharts({ + token, storeInCrudData: true, filterString: "filter[order]=createdDate desc", }); @@ -353,9 +367,14 @@ export function SubheaderToolbar(props: SubheaderToolbarProps) { const handleDuplicate = () => { if (props.pageType === "report") { axios - .get(`${process.env.REACT_APP_API}/report/duplicate/${page}`) + .get(`${process.env.REACT_APP_API}/report/duplicate/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then((response) => { loadReports({ + token, storeInCrudData: true, filterString: "filter[order]=createdDate desc", }); @@ -368,9 +387,14 @@ export function SubheaderToolbar(props: SubheaderToolbarProps) { .catch((error) => console.log(error)); } else { axios - .get(`${process.env.REACT_APP_API}/chart/duplicate/${page}`) - .then((response) => { + .get(`${process.env.REACT_APP_API}/chart/duplicate/${page}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then(async (response) => { loadCharts({ + token, storeInCrudData: true, filterString: "filter[order]=createdDate desc", }); diff --git a/src/app/modules/home-module/components/Charts/chartsGrid.tsx b/src/app/modules/home-module/components/Charts/chartsGrid.tsx index a6b2260af..aaa8d2ec8 100644 --- a/src/app/modules/home-module/components/Charts/chartsGrid.tsx +++ b/src/app/modules/home-module/components/Charts/chartsGrid.tsx @@ -1,17 +1,18 @@ -import React, { useRef } from "react"; +import React from "react"; import axios from "axios"; +import get from "lodash/get"; import find from "lodash/find"; import Box from "@material-ui/core/Box"; import Grid from "@material-ui/core/Grid"; +import { useSessionStorage } from "react-use"; import useDebounce from "react-use/lib/useDebounce"; +import { useInfinityScroll } from "app/hooks/useInfinityScroll"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; import DeleteChartDialog from "app/components/Dialogs/deleteChartDialog"; import { HomepageTable } from "app/modules/home-module/components/Table"; import { coloredEchartTypes } from "app/modules/chart-module/routes/chart-type/data"; import ReformedGridItem from "app/modules/home-module/components/Charts/reformedGridItem"; import ChartAddnewCard from "./chartAddNewCard"; -import { useInfinityScroll } from "app/hooks/useInfinityScroll"; -import { get } from "lodash"; interface Props { sortBy: string; @@ -21,12 +22,14 @@ interface Props { } export default function ChartsGrid(props: Props) { - const observerTarget = useRef(null); + const observerTarget = React.useRef(null); const [cardId, setCardId] = React.useState(0); const [modalDisplay, setModalDisplay] = React.useState(false); const [enableButton, setEnableButton] = React.useState(false); const [loadedCharts, setLoadedCharts] = React.useState([]); + const token = useSessionStorage("authToken", "")[0]; + const limit = 15; //used over usestate to get current offset value in the IntersectionObserver api, as it is not updated in usestate. const [offset, setOffset] = React.useState(0); @@ -46,9 +49,7 @@ export default function ChartsGrid(props: Props) { const loadCharts = useStoreActions( (actions) => actions.charts.ChartGetList.fetch ); - const loading = useStoreState( - (actions) => actions.charts.ChartGetList.loading - ); + const chartsLoadSuccess = useStoreState( (state) => state.charts.ChartGetList.success ); @@ -59,23 +60,33 @@ export default function ChartsGrid(props: Props) { ? `"where":{"name":{"like":"${searchStr}.*","options":"i"}},` : ""; //refrain from loading data if all the data is loaded - if (loadedCharts.length !== ChartsCount) { + // if (loadedCharts.length !== ChartsCount) { + if (token) { await loadCharts({ + token, storeInCrudData: true, filterString: `filter={${value}"order":"${sortByStr} desc","limit":${limit},"offset":${offset}}`, }); } + // } }; - const reloadData = () => { + const reloadData = async () => { setOffset(0); - loadChartsCount({}); + if (token) { + loadChartsCount({ token }); + } setLoadedCharts([]); loadData(props.searchStr, props.sortBy); }; + React.useEffect(() => { - loadChartsCount({}); - }, []); + if (token) { + loadChartsCount({ + token, + }); + } + }, [token]); React.useEffect(() => { //load data if intersection observer is triggered @@ -103,7 +114,11 @@ export default function ChartsGrid(props: Props) { return; } axios - .delete(`${process.env.REACT_APP_API}/chart/${id}`) + .delete(`${process.env.REACT_APP_API}/chart/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then(() => { reloadData(); }) @@ -116,7 +131,11 @@ export default function ChartsGrid(props: Props) { return; } axios - .get(`${process.env.REACT_APP_API}/chart/duplicate/${id}`) + .get(`${process.env.REACT_APP_API}/chart/duplicate/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then(() => { reloadData(); }) diff --git a/src/app/modules/home-module/components/Datasets/datasetsGrid.tsx b/src/app/modules/home-module/components/Datasets/datasetsGrid.tsx index c991a97d3..8411aef16 100644 --- a/src/app/modules/home-module/components/Datasets/datasetsGrid.tsx +++ b/src/app/modules/home-module/components/Datasets/datasetsGrid.tsx @@ -1,16 +1,17 @@ -import React, { useRef } from "react"; +import React from "react"; import axios from "axios"; import get from "lodash/get"; import Box from "@material-ui/core/Box"; import Grid from "@material-ui/core/Grid"; +import { useSessionStorage } from "react-use"; import useDebounce from "react-use/lib/useDebounce"; +import { useInfinityScroll } from "app/hooks/useInfinityScroll"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; import { HomepageTable } from "app/modules/home-module/components/Table"; import DeleteDatasetDialog from "app/components/Dialogs/deleteDatasetDialog"; import { DatasetListItemAPIModel } from "app/modules/data-themes-module/sub-modules/list"; import ReformedGridItem from "app/modules/home-module/components/Datasets/reformedGridItem"; -import DatasetAddnewCard from "./datasetAddNewCard"; -import { useInfinityScroll } from "app/hooks/useInfinityScroll"; +import DatasetAddnewCard from "app/modules/home-module/components/Datasets/datasetAddNewCard"; interface Props { sortBy: string; @@ -20,7 +21,8 @@ interface Props { } export default function DatasetsGrid(props: Props) { - const observerTarget = useRef(null); + const observerTarget = React.useRef(null); + const token = useSessionStorage("authToken", "")[0]; const [cardId, setCardId] = React.useState(""); const [enableButton, setEnableButton] = React.useState(false); const [modalDisplay, setModalDisplay] = React.useState(false); @@ -56,21 +58,27 @@ export default function DatasetsGrid(props: Props) { //refrain from loading data if all the data is loaded if (loadedDatasets.length !== datasetCount) { await loadDatasets({ + token, storeInCrudData: true, filterString: `filter={${value}"order":"${sortByStr} desc","limit":${limit},"offset":${offset}}`, }); } }; - const reloadData = () => { - loadDatasetCount({}); + const reloadData = async () => { + if (token) { + loadDatasetCount({ token }); + } setLoadedDatasets([]); setOffset(0); loadData(props.searchStr, props.sortBy); }; + React.useEffect(() => { - loadDatasetCount({}); - }, []); + if (token) { + loadDatasetCount({ token }); + } + }, [token]); React.useEffect(() => { //load data if intersection observer is triggered @@ -88,7 +96,7 @@ export default function DatasetsGrid(props: Props) { return () => { setOffset(0); }; - }, [props.sortBy, datasetCount]); + }, [props.sortBy, token, datasetCount]); const handleDelete = (id: string) => { deleteDataset(id); @@ -118,11 +126,16 @@ export default function DatasetsGrid(props: Props) { ) as DatasetListItemAPIModel[] ); - function deleteDataset(id: string) { + async function deleteDataset(id: string) { axios - .delete(`${process.env.REACT_APP_API}/datasets/${id}`) + .delete(`${process.env.REACT_APP_API}/datasets/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then(() => { loadDatasets({ + token, storeInCrudData: true, filterString: "filter[order]=createdDate desc", }); @@ -134,6 +147,7 @@ export default function DatasetsGrid(props: Props) { clearDatasets(); setLoadedDatasets([]); }, []); + React.useEffect(() => { if (datasets === null) { setLoadedDatasets([]); diff --git a/src/app/modules/home-module/components/Reports/reportsGrid.tsx b/src/app/modules/home-module/components/Reports/reportsGrid.tsx index 8c5a33db0..d75474bd7 100644 --- a/src/app/modules/home-module/components/Reports/reportsGrid.tsx +++ b/src/app/modules/home-module/components/Reports/reportsGrid.tsx @@ -1,8 +1,9 @@ -import React, { useRef } from "react"; +import React from "react"; import axios from "axios"; import get from "lodash/get"; import Box from "@material-ui/core/Box"; import Grid from "@material-ui/core/Grid"; +import { useSessionStorage } from "react-use"; import useDebounce from "react-use/lib/useDebounce"; import { ReportModel } from "app/modules/report-module/data"; import ColoredReportIcon from "app/assets/icons/ColoredReportIcon"; @@ -22,7 +23,8 @@ interface Props { } export default function ReportsGrid(props: Props) { - const observerTarget = useRef(null); + const observerTarget = React.useRef(null); + const token = useSessionStorage("authToken", "")[0]; const [cardId, setCardId] = React.useState(0); const [modalDisplay, setModalDisplay] = React.useState(false); const [enableButton, setEnableButton] = React.useState(false); @@ -54,8 +56,9 @@ export default function ReportsGrid(props: Props) { ? `"where":{"title":{"like":"${searchStr}.*","options":"i"}},` : ""; //refrain from loading data if all the data is loaded - if (loadedReports.length !== reportsCount) { + if (loadedReports.length !== reportsCount && token) { await loadReports({ + token, storeInCrudData: true, filterString: `filter={${value}"order":"${sortByStr} desc","limit":${limit},"offset":${offset}}`, }); @@ -64,13 +67,18 @@ export default function ReportsGrid(props: Props) { const reloadData = async () => { setOffset(0); - await loadReportsCount({}); + if (token) { + await loadReportsCount({ token }); + } setLoadedReports([]); loadData(props.searchStr, props.sortBy); }; + React.useEffect(() => { - loadReportsCount({}); - }, []); + if (token) { + loadReportsCount({ token }); + } + }, [token]); React.useEffect(() => { //load data if intersection observer is triggered @@ -89,7 +97,7 @@ export default function ReportsGrid(props: Props) { return () => { setOffset(0); }; - }, [props.sortBy, reportsCount]); + }, [props.sortBy, token, reportsCount]); const handleDelete = (index?: number) => { setModalDisplay(false); @@ -99,7 +107,11 @@ export default function ReportsGrid(props: Props) { return; } axios - .delete(`${process.env.REACT_APP_API}/report/${id}`) + .delete(`${process.env.REACT_APP_API}/report/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then(() => { reloadData(); }) @@ -112,7 +124,11 @@ export default function ReportsGrid(props: Props) { return; } axios - .get(`${process.env.REACT_APP_API}/report/duplicate/${id}`) + .get(`${process.env.REACT_APP_API}/report/duplicate/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) .then(() => { reloadData(); }) diff --git a/src/app/modules/home-module/index.tsx b/src/app/modules/home-module/index.tsx index dcb4c7bd6..f9244bb90 100644 --- a/src/app/modules/home-module/index.tsx +++ b/src/app/modules/home-module/index.tsx @@ -1,16 +1,12 @@ /* third-party */ import React from "react"; -import { useRecoilState, useResetRecoilState } from "recoil"; import { Link } from "react-router-dom"; +import { useAuth0 } from "@auth0/auth0-react"; import useTitle from "react-use/lib/useTitle"; +import { useRecoilState, useResetRecoilState } from "recoil"; import { Box, Grid, Container, IconButton, Popover } from "@material-ui/core"; /* project */ -import { - createChartFromReportAtom, - homeDisplayAtom, - persistedReportStateAtom, - unSavedReportPreviewModeAtom, -} from "app/state/recoil/atoms"; +import { Tab } from "app/components/Styled/tabs"; import HomeFooter from "app/modules/home-module/components/Footer"; import ChartsGrid from "app/modules/home-module/components/Charts/chartsGrid"; import ReportsGrid from "app/modules/home-module/components/Reports/reportsGrid"; @@ -20,9 +16,17 @@ import { ReactComponent as SortIcon } from "app/modules/home-module/assets/sort- import { ReactComponent as GridIcon } from "app/modules/home-module/assets/grid-fill.svg"; import { ReactComponent as CloseIcon } from "app/modules/home-module/assets/close-icon.svg"; import { ReactComponent as SearchIcon } from "app/modules/home-module/assets/search-fill.svg"; +import { ReactComponent as GoogleIcon } from "app/modules/onboarding-module/asset/google-img.svg"; +import { ReactComponent as LinkedInIcon } from "app/modules/onboarding-module/asset/linkedIn-img.svg"; import { ReactComponent as TopRightEllipse } from "app/modules/home-module/assets/top-right-ellipse.svg"; import { ReactComponent as BottomLeftEllipse } from "app/modules/home-module/assets/bottom-left-ellipse.svg"; import { ReactComponent as BottomRightEllipse } from "app/modules/home-module/assets/bottom-right-ellipse.svg"; +import { + homeDisplayAtom, + persistedReportStateAtom, + createChartFromReportAtom, + unSavedReportPreviewModeAtom, +} from "app/state/recoil/atoms"; import { TopRightEllipseCss, bottomLeftEllipseCss, @@ -35,11 +39,12 @@ import { sortByItemCss, turnsDataCss, } from "app/modules/home-module/style"; -import { Tab } from "app/components/Styled/tabs"; export default function HomeModule() { useTitle("DX DataXplorer"); + const { isAuthenticated, loginWithRedirect } = useAuth0(); + // clear persisted states const clearPersistedReportState = useResetRecoilState( persistedReportStateAtom @@ -124,6 +129,10 @@ export default function HomeModule() { setSortPopoverAnchorEl(null); }; + const handleLogin = () => { + loginWithRedirect(); + }; + const openSortPopover = Boolean(sortPopoverAnchorEl); React.useEffect(() => { @@ -160,39 +169,73 @@ export default function HomeModule() { max-width: 450px; `} > -

Turns data into impact

+

Turn data into impact with DataXplorer

- Dx drives better business outcomes and intelligent customer - experiences with insights everywhere, -
for everyone. + DataXplorer simplifies and empowers visual data reporting + for all.

-
- - CREATE REPORT - - +
+ )} + {!isAuthenticated && ( +
button { + gap: 10px; + color: #fff; + display: flex; + padding: 9px 18px; + background: #a1a2ff; + align-items: center; + justify-content: center; + text-transform: uppercase; + + > svg { + transform: scale(0.8); + } + } `} > - EXPLORE REPORTS - -
+ + + + )} ) => { + setChecked(event.target.checked); + }; + + const onButtonClick = () => { + loginWithRedirect(); + }; + + return ( +
button { + opacity: ${checked || props.isLogin ? "1" : "0.5"}; + pointer-events: ${checked || props.isLogin ? "auto" : "none"}; + } + `} + > + + + {!props.isLogin && ( + + } + label={ +

+ I agree with DX's{" "} + + terms of services and privacy policy + +

+ } + css={termsOfServiceCss} + /> + )} +
+ ); +} diff --git a/src/app/modules/onboarding-module/component/signupCard/style.ts b/src/app/modules/onboarding-module/component/card/style.ts similarity index 98% rename from src/app/modules/onboarding-module/component/signupCard/style.ts rename to src/app/modules/onboarding-module/component/card/style.ts index abdb58424..4f9602d87 100644 --- a/src/app/modules/onboarding-module/component/signupCard/style.ts +++ b/src/app/modules/onboarding-module/component/card/style.ts @@ -47,9 +47,11 @@ export const actionbuttoncss = css` background: #a1aebd; } `; + export const termsOfServiceCss = css` + width: 100%; font-family: "GothamNarrow-Book", "Helvetica Neue", sans-serif; - margin-top: -6px; + > span { font-size: 12px; } diff --git a/src/app/modules/onboarding-module/component/loginCard/index.tsx b/src/app/modules/onboarding-module/component/loginCard/index.tsx deleted file mode 100644 index b2951386e..000000000 --- a/src/app/modules/onboarding-module/component/loginCard/index.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from "react"; -import { actionbuttoncss, socialloginbuttoncss } from "./style"; -import { ReactComponent as LinkedInIcon } from "../../asset/linkedIn-img.svg"; -import { ReactComponent as GoogleIcon } from "../../asset/google-img.svg"; -import { ReactComponent as DividerImg } from "../../asset/login-divider.svg"; - -import { OnboardingTextInput } from "../textinput"; -import { Box } from "@material-ui/core"; -import { useHistory } from "react-router-dom"; - -interface Prop { - splitForm?: boolean; - setSplitForm?: React.Dispatch>; -} -export default function LoginCard(props: Prop) { - const history = useHistory(); - const [email, setEmail] = React.useState(""); - const [password, setPassword] = React.useState(""); - return ( -
- - - -
- -
-
.MuiTextField-root { - margin-bottom: 16px; - } - `} - > - - - - - - -
-
-
- ); -} diff --git a/src/app/modules/onboarding-module/component/loginCard/style.ts b/src/app/modules/onboarding-module/component/loginCard/style.ts deleted file mode 100644 index ff00e3259..000000000 --- a/src/app/modules/onboarding-module/component/loginCard/style.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { css } from "styled-components/macro"; - -export const socialloginbuttoncss = css` - gap: 15px; - width: 100%; - height: 56px; - display: flex; - color: #2e4063; - padding: 6px 0; - font-size: 14px; - cursor: pointer; - font-weight: 700; - line-height: 20px; - border: 1px solid #231d2c; - border-radius: 10px; - flex-direction: row; - margin-bottom: 16px; - align-items: center; - justify-content: center; - background: transparent; - - &:hover { - background: #a1aebd; - } -`; - -export const actionbuttoncss = (splitForm: boolean) => css` - width: 100%; - color: #fff; - padding: 6px 0; - cursor: pointer; - font-size: 14px; - font-weight: 700; - line-height: 20px; - border-style: none; - border-radius: 50px; - background: #6061e5; - height: 48px; - margin-bottom: 24px; - - :disabled { - opacity: 0.5; - pointer-events: none; - } - - &:hover { - background: #a1aebd; - } -`; diff --git a/src/app/modules/onboarding-module/component/signupCard/index.tsx b/src/app/modules/onboarding-module/component/signupCard/index.tsx deleted file mode 100644 index 6de2de128..000000000 --- a/src/app/modules/onboarding-module/component/signupCard/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from "react"; -import { - actionbuttoncss, - socialloginbuttoncss, - termsOfServiceCss, -} from "./style"; -import { ReactComponent as LinkedInIcon } from "../../asset/linkedIn-img.svg"; -import { ReactComponent as GoogleIcon } from "../../asset/google-img.svg"; -import { ReactComponent as DividerImg } from "../../asset/signup-divider.svg"; - -import { OnboardingTextInput } from "../textinput"; -import { Box, Checkbox, FormControlLabel } from "@material-ui/core"; - -export default function SignupCard() { - const [name, setName] = React.useState(""); - const [email, setEmail] = React.useState(""); - const [password, setPassword] = React.useState(""); - - return ( -
- - - -
- -
-
.MuiTextField-root { - margin-bottom: 16px; - } - `} - > - - - - - - } - label={ -

- I agree with DX's{" "} - - terms of services and privacy policy - -

- } - css={termsOfServiceCss} - /> - - -
-
-
- ); -} diff --git a/src/app/modules/onboarding-module/index.tsx b/src/app/modules/onboarding-module/index.tsx index 001b1e874..3aa3626be 100644 --- a/src/app/modules/onboarding-module/index.tsx +++ b/src/app/modules/onboarding-module/index.tsx @@ -1,23 +1,29 @@ import React from "react"; -import { Link, Route, Switch, useLocation } from "react-router-dom"; -import OnboardingRightDeco from "./asset/onboardingRight-img.svg"; -import { Box, Container, Grid, Hidden, useMediaQuery } from "@material-ui/core"; +import Box from "@material-ui/core/Box"; +import Grid from "@material-ui/core/Grid"; import SplitBar from "./component/splibar"; -import LoginCard from "./component/loginCard"; +import { useAuth0 } from "@auth0/auth0-react"; +import useMediaQuery from "@material-ui/core/useMediaQuery"; +import { Route, Switch, useHistory } from "react-router-dom"; +import AuthCard from "app/modules/onboarding-module/component/card"; +import OnboardingRightDeco from "app/modules/onboarding-module/asset/onboardingRight-img.svg"; -import { ReactComponent as Logo } from "../home-module/assets/logo.svg"; -import SignupCard from "./component/signupCard"; export default function Onboarding() { + const history = useHistory(); + const { isAuthenticated } = useAuth0(); const mobile = useMediaQuery("(max-width: 768px)"); - const location = useLocation(); + + if (isAuthenticated) { + history.replace("/"); + } + return ( @@ -33,17 +39,6 @@ export default function Onboarding() { } `} > -
- - - -
-
- {location.pathname.includes("login") + {history.location.pathname.includes("login") ? "Welcome back!" : "Create your free account."}

-
- - + - + @@ -93,13 +86,12 @@ export default function Onboarding() { lg={7} css={` right: 0; - height: 100vh; - background-image: url(${OnboardingRightDeco}); - background-repeat: no-repeat; background-size: cover; + background-repeat: no-repeat; + background-image: url(${OnboardingRightDeco}); `} - > + /> )} ); diff --git a/src/app/modules/report-module/components/chart-wrapper/index.tsx b/src/app/modules/report-module/components/chart-wrapper/index.tsx index 7c05b3776..3c76cdce7 100644 --- a/src/app/modules/report-module/components/chart-wrapper/index.tsx +++ b/src/app/modules/report-module/components/chart-wrapper/index.tsx @@ -1,15 +1,16 @@ import React from "react"; import get from "lodash/get"; +import { useSessionStorage } from "react-use"; import Skeleton from "@material-ui/lab/Skeleton"; import { useChartsRawData } from "app/hooks/useChartsRawData"; +import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline"; +import { useStoreActions, useStoreState } from "app/state/store/hooks"; +import { CommonChart } from "app/modules/chart-module/components/common-chart"; import { ChartAPIModel, ChartRenderedItem, emptyChartAPI, } from "app/modules/chart-module/data"; -import { CommonChart } from "app/modules/chart-module/components/common-chart"; -import { useStoreActions, useStoreState } from "app/state/store/hooks"; -import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline"; interface Props { id: string; @@ -17,9 +18,11 @@ interface Props { } export function ReportChartWrapper(props: Props) { + const token = useSessionStorage("authToken", "")[0]; + const containerRef = React.useRef(null); - const loadChart = useStoreActions((actions) => actions.charts.ChartGet.fetch); + const loadChart = useStoreActions((actions) => actions.charts.ChartGet.fetch); const loadedChart = useStoreState( (state) => (state.charts.ChartGet.crudData ?? emptyChartAPI) as ChartAPIModel @@ -51,8 +54,10 @@ export function ReportChartWrapper(props: Props) { }, [chartFromAPI]); React.useEffect(() => { - loadChart({ getId: props.id }); - }, [props.id]); + if (token.length > 0) { + loadChart({ token, getId: props.id }); + } + }, [props.id, token]); React.useEffect(() => { if (loadedChart && loadedChart.id !== "" && loadedChart.id === props.id) { diff --git a/src/app/modules/report-module/components/right-panel-create-view/index.tsx b/src/app/modules/report-module/components/right-panel-create-view/index.tsx index 2a244759a..9599d8bdb 100644 --- a/src/app/modules/report-module/components/right-panel-create-view/index.tsx +++ b/src/app/modules/report-module/components/right-panel-create-view/index.tsx @@ -3,11 +3,11 @@ import find from "lodash/find"; import { useDrag } from "react-dnd"; import { useRecoilState } from "recoil"; import Paper from "@material-ui/core/Paper"; +import { useSessionStorage } from "react-use"; import MuiButton from "@material-ui/core/Button"; import MenuItem from "@material-ui/core/MenuItem"; import { EditorState, convertToRaw } from "draft-js"; import { SearchIcon } from "app/assets/icons/Search"; -import IconButton from "@material-ui/core/IconButton"; import { withStyles } from "@material-ui/core/styles"; import { useHistory, useParams } from "react-router-dom"; import Menu, { MenuProps } from "@material-ui/core/Menu"; @@ -281,6 +281,8 @@ function ReportRightPanelCreateViewChartList(props: { reportName: string; handlePersistReportState: () => void; }) { + const token = useSessionStorage("authToken", "")[0]; + const [search, setSearch] = React.useState(""); const [sortBy, setSortBy] = React.useState(sortByOptions[0]); const [anchorEl, setAnchorEl] = React.useState(null); @@ -301,10 +303,11 @@ function ReportRightPanelCreateViewChartList(props: { React.useEffect(() => { loadChartList({ + token, storeInCrudData: true, filterString: `filter={"where":{"name":{"like":"${search}.*","options":"i"}},"order":"${sortBy.value}"}`, }); - }, [search, sortBy]); + }, [token, search, sortBy]); return ( diff --git a/src/app/modules/report-module/index.tsx b/src/app/modules/report-module/index.tsx index 461878ece..9d09559ac 100644 --- a/src/app/modules/report-module/index.tsx +++ b/src/app/modules/report-module/index.tsx @@ -1,30 +1,32 @@ import React from "react"; import { v4 } from "uuid"; +import get from "lodash/get"; import isEmpty from "lodash/isEmpty"; import { DndProvider } from "react-dnd"; import { useRecoilState } from "recoil"; -import Container from "@material-ui/core/Container"; +import { useAuth0 } from "@auth0/auth0-react"; +import { useSessionStorage } from "react-use"; import { HTML5Backend } from "react-dnd-html5-backend"; import { PageLoader } from "app/modules/common/page-loader"; import { NoMatchPage } from "app/modules/common/no-match-page"; import { IHeaderDetails } from "./components/right-panel/data"; +import ReportEditView from "app/modules/report-module/views/edit"; import AITemplate from "app/modules/report-module/views/ai-template"; import { EditorState, convertFromRaw, convertToRaw } from "draft-js"; -import { ReportEditView } from "app/modules/report-module/views/edit"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; -import { SubheaderToolbar } from "app/modules/common/subheader-toolbar"; +import { SubheaderToolbar } from "../common/subheader-toolbar/SubheaderToolbar"; import { ReportContentHeightsType, ReportContentWidthsType, ReportModel, emptyReport, } from "app/modules/report-module/data"; -import { ReportCreateView } from "app/modules/report-module/views/create"; +import ReportCreateView from "app/modules/report-module/views/create"; import { ReportPreviewView } from "app/modules/report-module/views/preview"; import { ReportInitialView } from "app/modules/report-module/views/initial"; +import { IFramesArray } from "app/modules/report-module/views/create/data"; import { ReportRightPanel } from "app/modules/report-module/components/right-panel"; import { ReportElementsType } from "app/modules/report-module/components/right-panel-create-view"; - import { Route, Switch, @@ -32,6 +34,10 @@ import { useParams, Redirect, } from "react-router-dom"; +import { + persistedReportStateAtom, + reportRightPanelViewAtom, +} from "app/state/recoil/atoms"; interface RowFrameProps { structure: @@ -49,13 +55,9 @@ interface RowFrameProps { contentTypes: ("text" | "divider" | "chart" | null)[]; type: "rowFrame" | "divider"; } -import { - persistedReportStateAtom, - reportRightPanelViewAtom, -} from "app/state/recoil/atoms"; -import { IFramesArray } from "app/modules/report-module/views/create/data"; export default function ReportModule() { + const { user } = useAuth0(); const history = useHistory(); const { page, view } = useParams<{ page: string; @@ -68,6 +70,8 @@ export default function ReportModule() { {} as IHeaderDetails ); + const token = useSessionStorage("authToken", "")[0]; + const setRightPanelView = useRecoilState(reportRightPanelViewAtom)[1]; const [persistedReportState, setPersistedReportState] = useRecoilState( @@ -346,6 +350,11 @@ export default function ReportModule() { (actions) => actions.reports.ReportUpdate.clear ); + const reportError401 = useStoreState( + (state) => + get(state.reports.ReportGet.errorData, "data.error.statusCode", 0) === 401 + ); + //get current value of states for handlePersistReportState function headerDetailsRef.current = headerDetails; AppliedHeaderDetailsRef.current = appliedHeaderDetails; @@ -397,12 +406,14 @@ export default function ReportModule() { setRightPanelOpen(true); }; - const onSave = () => { + const onSave = async () => { const action = page === "new" ? reportCreate : reportEdit; action({ + token, patchId: page === "new" ? undefined : page, values: { name: reportName, + authId: user?.sub, showHeader: headerDetails.showHeader, title: headerDetails.showHeader ? headerDetails.title : undefined, subTitle: convertToRaw( @@ -429,7 +440,6 @@ export default function ReportModule() { backgroundColor: appliedHeaderDetails.backgroundColor, titleColor: appliedHeaderDetails.titleColor, descriptionColor: appliedHeaderDetails.descriptionColor, - dateColor: appliedHeaderDetails.dateColor, }, }); @@ -489,7 +499,7 @@ export default function ReportModule() { return ( {(reportCreateLoading || reportEditLoading) && } - {view !== "ai-template" && view !== "initial" && ( + {!reportError401 && view !== "ai-template" && view !== "initial" && ( - - - + diff --git a/src/app/modules/report-module/views/create/index.tsx b/src/app/modules/report-module/views/create/index.tsx index c633c0bc2..a42ad33e9 100644 --- a/src/app/modules/report-module/views/create/index.tsx +++ b/src/app/modules/report-module/views/create/index.tsx @@ -24,9 +24,9 @@ import { } from "app/state/recoil/atoms"; import TourGuide from "app/components/Dialogs/TourGuide"; import { cloneDeep } from "lodash"; -import { useStoreState } from "app/state/store/hooks"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; -export function ReportCreateView(props: ReportCreateViewProps) { +function ReportCreateView(props: ReportCreateViewProps) { const { ref, width } = useResizeObserver(); const [containerWidth, setContainerWidth] = useRecoilState( @@ -190,6 +190,8 @@ export function ReportCreateView(props: ReportCreateViewProps) { ); } +export default withAuthenticationRequired(ReportCreateView); + export const PlaceHolder = (props: PlaceholderProps) => { const [{ isOver }, drop] = useDrop(() => ({ // The type (or types) to accept - strings or symbols diff --git a/src/app/modules/report-module/views/edit/index.tsx b/src/app/modules/report-module/views/edit/index.tsx index 2645396f3..917cd47bd 100644 --- a/src/app/modules/report-module/views/edit/index.tsx +++ b/src/app/modules/report-module/views/edit/index.tsx @@ -1,17 +1,20 @@ import React from "react"; import { v4 } from "uuid"; +import get from "lodash/get"; import Box from "@material-ui/core/Box"; import { useRecoilState } from "recoil"; -import { useUpdateEffect } from "react-use"; import { useParams } from "react-router-dom"; import useResizeObserver from "use-resize-observer"; import Container from "@material-ui/core/Container"; import { EditorState, convertFromRaw } from "draft-js"; +import { useSessionStorage, useUpdateEffect } from "react-use"; +import { withAuthenticationRequired } from "@auth0/auth0-react"; import { PlaceHolder } from "app/modules/report-module/views/create"; import { useStoreActions, useStoreState } from "app/state/store/hooks"; import { ReportModel, emptyReport } from "app/modules/report-module/data"; import { ReportEditViewProps } from "app/modules/report-module/views/edit/data"; import HeaderBlock from "app/modules/report-module/sub-module/components/headerBlock"; +import { NotAuthorizedMessageModule } from "app/modules/common/not-authorized-message"; import { ReportOrderContainer } from "app/modules/report-module/components/order-container"; import { ReportElementsType } from "app/modules/report-module/components/right-panel-create-view"; import AddRowFrameButton from "app/modules/report-module/sub-module/rowStructure/addRowFrameButton"; @@ -24,8 +27,9 @@ import { import { IFramesArray } from "../create/data"; import RowFrame from "../../sub-module/rowStructure/rowFrame"; -export function ReportEditView(props: ReportEditViewProps) { +function ReportEditView(props: ReportEditViewProps) { const { page } = useParams<{ page: string }>(); + const token = useSessionStorage("authToken", "")[0]; const { ref, width } = useResizeObserver(); @@ -49,6 +53,11 @@ export function ReportEditView(props: ReportEditViewProps) { (state) => (state.reports.ReportGet.crudData ?? emptyReport) as ReportModel ); + const reportError401 = useStoreState( + (state) => + get(state.reports.ReportGet.errorData, "data.error.statusCode", 0) === 401 + ); + function deleteFrame(id: string) { props.setFramesArray((prev) => { let tempPrev = prev.map((item) => ({ ...item })); @@ -65,8 +74,8 @@ export function ReportEditView(props: ReportEditViewProps) { } React.useEffect(() => { - fetchReportData({ getId: page }); - }, [page]); + fetchReportData({ token, getId: page }); + }, [page, token]); React.useEffect(() => { if (props.localPickedCharts.length === 0) { @@ -156,6 +165,10 @@ export function ReportEditView(props: ReportEditViewProps) { } }, [reportData]); + if (reportError401) { + return ; + } + return (
); } + +export default withAuthenticationRequired(ReportEditView); diff --git a/src/app/modules/report-module/views/initial/index.tsx b/src/app/modules/report-module/views/initial/index.tsx index 827756693..b53b5a25d 100644 --- a/src/app/modules/report-module/views/initial/index.tsx +++ b/src/app/modules/report-module/views/initial/index.tsx @@ -1,5 +1,6 @@ import React from "react"; import Grid from "@material-ui/core/Grid"; +import Container from "@material-ui/core/Container"; import { templates, TemplateItem, @@ -62,7 +63,7 @@ export function ReportInitialView(props: ReportInitialViewProps) { }, []); return ( - <> +

- + ); } diff --git a/src/app/modules/report-module/views/preview/index.tsx b/src/app/modules/report-module/views/preview/index.tsx index c78c43d64..204a0971c 100644 --- a/src/app/modules/report-module/views/preview/index.tsx +++ b/src/app/modules/report-module/views/preview/index.tsx @@ -1,7 +1,9 @@ import React from "react"; +import get from "lodash/get"; import { useRecoilState } from "recoil"; import Box from "@material-ui/core/Box"; import { useParams } from "react-router-dom"; +import { useSessionStorage } from "react-use"; import useResizeObserver from "use-resize-observer"; import Container from "@material-ui/core/Container"; import { EditorState, convertFromRaw } from "draft-js"; @@ -9,6 +11,7 @@ import { useStoreActions, useStoreState } from "app/state/store/hooks"; import { ReportModel, emptyReport } from "app/modules/report-module/data"; import RowFrame from "app/modules/report-module/sub-module/rowStructure/rowFrame"; import HeaderBlock from "app/modules/report-module/sub-module/components/headerBlock"; +import { NotAuthorizedMessageModule } from "app/modules/common/not-authorized-message"; import { ReportElementsType } from "app/modules/report-module/components/right-panel-create-view"; import { persistedReportStateAtom, @@ -20,6 +23,7 @@ export function ReportPreviewView(props: { setIsPreviewView: React.Dispatch>; }) { const { page } = useParams<{ page: string }>(); + const token = useSessionStorage("authToken", "")[0]; const { ref, width } = useResizeObserver(); @@ -34,6 +38,11 @@ export function ReportPreviewView(props: { (state) => (state.reports.ReportGet.crudData ?? emptyReport) as ReportModel ); + const Error401 = useStoreState( + (state) => + get(state.reports.ReportGet.errorData, "data.error.statusCode", 0) === 401 + ); + const fetchReportData = useStoreActions( (actions) => actions.reports.ReportGet.fetch ); @@ -53,8 +62,12 @@ export function ReportPreviewView(props: { const [reportPreviewData, setReportPreviewData] = React.useState(reportData); React.useEffect(() => { - fetchReportData({ getId: page }); - }, [page]); + if (token) { + fetchReportData({ token, getId: page }); + } else { + fetchReportData({ getId: page }); + } + }, [page, token]); React.useEffect(() => { if (width && width !== containerWidth) { @@ -115,6 +128,7 @@ export function ReportPreviewView(props: { /> + {Error401 && } {reportPreviewData.rows.map((rowFrame, index) => { const contentTypes = rowFrame.items.map((item) => { if (item === null) { diff --git a/src/app/modules/user-profile-module/component/tab.tsx b/src/app/modules/user-profile-module/component/tab.tsx index bf8efbf02..15cb1c3ee 100644 --- a/src/app/modules/user-profile-module/component/tab.tsx +++ b/src/app/modules/user-profile-module/component/tab.tsx @@ -9,9 +9,6 @@ interface TabProps { } export default function Tab(props: TabProps) { - React.useEffect(() => { - console.log(props.active, "active"); - }, [props.active]); return (

{props.title}

diff --git a/src/app/modules/user-profile-module/index.tsx b/src/app/modules/user-profile-module/index.tsx index 5a3fa8b27..b4ab35c8b 100644 --- a/src/app/modules/user-profile-module/index.tsx +++ b/src/app/modules/user-profile-module/index.tsx @@ -1,32 +1,42 @@ import React from "react"; +import UserProfileLayout from "./layout"; import { useHistory } from "react-router-dom"; +import { useAuth0 } from "@auth0/auth0-react"; import { LogOutIcon, RightIcon } from "./component/icons"; -import UserProfileLayout from "./layout"; + +const tabList = [ + // { + // title: "profile", + // active: true, + // component: (active: boolean) => , + // }, + // { + // title: "settings", + // active: false, + // component: (active: boolean) => , + // }, + // { + // title: "billing", + // active: false, + // component: (active: boolean) => , + // }, + { + title: "Log Out", + active: false, + component: (active: boolean) => , + }, +]; export default function UserProfileModule() { - const tabList = [ - { - title: "profile", - active: true, - component: (active: boolean) => , - }, - { - title: "settings", - active: false, - component: (active: boolean) => , - }, - { - title: "billing", - active: false, - component: (active: boolean) => , - }, - { - title: "Log Out", - active: false, - component: (active: boolean) => , - }, - ]; + const history = useHistory(); + const { isAuthenticated, isLoading } = useAuth0(); const [tabstate, setTabState] = React.useState(tabList); + React.useEffect(() => { + if (!isLoading && !isAuthenticated) { + history.push("/"); + } + }, [isLoading, isAuthenticated]); + return ; } diff --git a/src/app/modules/user-profile-module/layout.tsx b/src/app/modules/user-profile-module/layout.tsx index 6397fc03c..dcfbf2118 100644 --- a/src/app/modules/user-profile-module/layout.tsx +++ b/src/app/modules/user-profile-module/layout.tsx @@ -2,11 +2,12 @@ import React from "react"; import Tab from "./component/tab"; import Profile from "./sub-module/profile"; import Settings from "./sub-module/settings"; +import { useAuth0 } from "@auth0/auth0-react"; import { bigAvicss, layoutcss } from "./style"; import { Box, Container, Grid } from "@material-ui/core"; -import LogOutDialog from "app/components/Dialogs/logOutDialog"; -import { Route, Switch, useHistory } from "react-router-dom"; import { PageTopSpacer } from "../common/page-top-spacer"; +import { Route, Switch, useHistory } from "react-router-dom"; +import LogOutDialog from "app/components/Dialogs/logOutDialog"; interface UserProfileLayoutProps { tabstate: { @@ -24,9 +25,13 @@ interface UserProfileLayoutProps { > >; } + export default function UserProfileLayout(props: UserProfileLayoutProps) { + const { user } = useAuth0(); const history = useHistory(); + const [modalDisplay, setModalDisplay] = React.useState(false); + const handleTabClick = (index: number, title: string) => { const newTabState = props.tabstate.map((tab, i) => { if (i === index) { @@ -58,7 +63,10 @@ export default function UserProfileLayout(props: UserProfileLayoutProps) {
-

VI

+

+ {user?.given_name?.slice(0, 1)} + {user?.family_name?.slice(0, 1)} +

@@ -75,7 +83,6 @@ export default function UserProfileLayout(props: UserProfileLayoutProps) { ))}
- diff --git a/src/app/state/api/action-reducers/charts/index.ts b/src/app/state/api/action-reducers/charts/index.ts index a003c2a7e..7a05d3705 100644 --- a/src/app/state/api/action-reducers/charts/index.ts +++ b/src/app/state/api/action-reducers/charts/index.ts @@ -24,6 +24,7 @@ export const ChartDuplicate: ApiCallModel = { export const ChartGetList: ApiCallModel = { ...APIModel(`${process.env.REACT_APP_API}/charts`), }; + export const ChartsCount: ApiCallModel = { ...APIModel(`${process.env.REACT_APP_API}/charts/count`), }; diff --git a/src/app/state/api/action-reducers/reports/index.ts b/src/app/state/api/action-reducers/reports/index.ts index 4df2889fa..a0e72ff07 100644 --- a/src/app/state/api/action-reducers/reports/index.ts +++ b/src/app/state/api/action-reducers/reports/index.ts @@ -24,6 +24,7 @@ export const ReportDuplicate: ApiCallModel = { export const ReportGetList: ApiCallModel = { ...APIModel(`${process.env.REACT_APP_API}/reports`), }; + export const ReportsCount: ApiCallModel = { ...APIModel(`${process.env.REACT_APP_API}/reports/count`), }; diff --git a/src/app/state/api/index.ts b/src/app/state/api/index.ts index 2277c54ea..3d120fd73 100644 --- a/src/app/state/api/index.ts +++ b/src/app/state/api/index.ts @@ -1,4 +1,5 @@ /* eslint-disable no-param-reassign */ +import get from "lodash/get"; import { action, thunk } from "easy-peasy"; import axios, { AxiosResponse } from "axios"; import { @@ -62,6 +63,7 @@ export const APIModel = ( { headers: { "Content-Type": "application/json", + Authorization: `Bearer ${get(query, "token", undefined)}`, }, } ) @@ -116,6 +118,7 @@ export const APIModel = ( .post(url, query.values, { headers: { "Content-Type": "application/json", + Authorization: `Bearer ${get(query, "token", undefined)}`, }, }) .then( @@ -132,6 +135,7 @@ export const APIModel = ( .patch(`${url}/${query.patchId}`, query.values, { headers: { "Content-Type": "application/json", + Authorization: `Bearer ${get(query, "token", undefined)}`, }, }) .then( @@ -145,6 +149,7 @@ export const APIModel = ( .delete(`${url}/${query.deleteId}`, { headers: { "Content-Type": "application/json", + Authorization: `Bearer ${get(query, "token", undefined)}`, }, }) .then( diff --git a/src/app/state/api/interfaces/index.ts b/src/app/state/api/interfaces/index.ts index 31d726233..807bcd059 100644 --- a/src/app/state/api/interfaces/index.ts +++ b/src/app/state/api/interfaces/index.ts @@ -80,6 +80,7 @@ import { ChartsAppliedFiltersStateModel } from "../action-reducers/sync/charts/f export interface RequestValues { values?: T; + token?: string; getId?: string; patchId?: string; deleteId?: string; diff --git a/src/app/utils/RouteWithAppBar.tsx b/src/app/utils/RouteWithAppBar.tsx index 7713bb7b4..f398fb105 100644 --- a/src/app/utils/RouteWithAppBar.tsx +++ b/src/app/utils/RouteWithAppBar.tsx @@ -1,19 +1,32 @@ -import { AppBar } from "app/components/AppBar"; import React from "react"; import { Route } from "react-router-dom"; +import { useAuth0 } from "@auth0/auth0-react"; +import { AppBar } from "app/components/AppBar"; +import useSessionStorage from "react-use/lib/useSessionStorage"; interface RouteWithAppBarProps { path?: string; exact?: boolean; - - children: React.ReactNode | React.ReactNode[]; + element?: React.ReactNode; + children?: React.ReactNode | React.ReactNode[]; } export function RouteWithAppBar(props: RouteWithAppBarProps) { + const [token, setToken] = useSessionStorage("authToken", ""); + const { isAuthenticated, getAccessTokenSilently } = useAuth0(); + + React.useEffect(() => { + if (isAuthenticated && !token) { + getAccessTokenSilently().then((token) => { + setToken(token); + }); + } + }, [isAuthenticated]); + return ( - {props.children} + {props.element ?? props.children} ); } diff --git a/yarn.lock b/yarn.lock index 033b71d84..00a92126e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,6 +11,18 @@ jsonpointer "^5.0.0" leven "^3.1.0" +"@auth0/auth0-react@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-2.2.1.tgz#f1d3ff6ea9144d8bba0597e86beb5cb1d57301cb" + integrity sha512-4L4FZvSqIwzVk5mwWFbWzfJ4Zq11dgS0v4KIGKro5tL9dgOnBGq+Ino/1mzexPV1LJHBkfwXG4+IaPiQNz5CGg== + dependencies: + "@auth0/auth0-spa-js" "^2.1.2" + +"@auth0/auth0-spa-js@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-2.1.2.tgz#2217db13ce0feb480a190ed165b36681bd48633c" + integrity sha512-xdA65Z/U7++Y7L9Uwh8Q8OVOs6qgFz+fb7GAzHFjpr1icO37B//xdzLXm7ZRgA19RWrsNe1nme3h896igJSvvw== + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz"