diff --git a/.env b/.env-template similarity index 100% rename from .env rename to .env-template diff --git a/public/icons/download-2.svg b/public/icons/download-2.svg deleted file mode 100644 index bdfae70b..00000000 --- a/public/icons/download-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/components/layers-panel/layers-control-panel.tsx b/src/components/layers-panel/layers-control-panel.tsx index d0e86707..b6f13f6d 100644 --- a/src/components/layers-panel/layers-control-panel.tsx +++ b/src/components/layers-panel/layers-control-panel.tsx @@ -1,24 +1,20 @@ -import { Fragment, ReactEventHandler, useState, useEffect } from "react"; +import { Fragment, ReactEventHandler, useState } from "react"; import styled from "styled-components"; - import { SelectionState, LayerExample, LayerViewState, ListItemType } from "../../types"; - import { ListItem } from "./list-item/list-item"; import PlusIcon from "../../../public/icons/plus.svg"; import ImportIcon from "../../../public/icons/import.svg"; import EsriImage from "../../../public/images/esri.svg"; import { ActionIconButton } from "../action-icon-button/action-icon-button"; import { AcrGisUser } from "../arcgis-user/arcgis-user"; - import { DeleteConfirmation } from "./delete-confirmation"; import { LayerOptionsMenu } from "./layer-options-menu/layer-options-menu"; import { handleSelectAllLeafsInGroup } from "../../utils/layer-utils"; import { ButtonSize } from "../../types"; import { PanelHorizontalLine } from "../common"; - - import { arcGisLogin, arcGisLogout, selectUser } from "../../redux/slices/arcgis-auth-slice"; import { useAppDispatch, useAppSelector } from "../../redux/hooks"; +import { ModalDialog } from "../modal-dialog/modal-dialog"; type LayersControlPanelProps = { layers: LayerExample[]; @@ -86,37 +82,18 @@ export const LayersControlPanel = ({ deleteLayer, }: LayersControlPanelProps) => { - // stub { - // const dispatch = useAppDispatch(); - - // const handleArcGisLogin = () => { - // dispatch(arcGisLogin()); - // }; - - // const handleArcGisLogout = () => { - // dispatch(arcGisLogout()); - // }; - - // const username = useAppSelector(selectUser); - // const [showLogin, setShowLoginButton] = useState(!username); - // const [showLogout, setShowLogoutButton] = useState(!!username); - - // useEffect(() => { - // setShowLoginButton(!username); - // setShowLogoutButton(!!username); - // }, [username]); -// stub } + const dispatch = useAppDispatch(); + const username = useAppSelector(selectUser); + const isLoggedIn = !!username; + + const [showLogoutWarning, setShowLogoutWarning] = useState(false); const [settingsLayerId, setSettingsLayerId] = useState(""); const [showLayerSettings, setShowLayerSettings] = useState(false); const [layerToDeleteId, setLayerToDeleteId] = useState(""); - /// Stab { - const username = 'Michael-g'; - const [isLoggedIn, setIsLoggedIn] = useState(false); - const onArcGisActionClick = () => { !isLoggedIn && setIsLoggedIn(true) }; - const onArcGisLogoutClick = () => { setIsLoggedIn(false) }; - /// Stab } + const onArcGisActionClick = () => { !isLoggedIn && dispatch(arcGisLogin()); }; + const onArcGisLogoutClick = () => { setShowLogoutWarning(true); }; const isListItemSelected = ( layer: LayerExample, @@ -150,6 +127,38 @@ export const LayersControlPanel = ({ return selectedState; }; + const TextQuestion = styled.div` + font-style: normal; + font-weight: 500; + font-size: 16px; + line-height: 19px; +`; + + const TextInfo = styled.div` + font-style: normal; + font-weight: 700; + font-size: 16px; + line-height: 19px; +`; + + const renderModalDialogContent = (): JSX.Element => { + return ( + <> + + Are you sure you want to log out? + + + + You are logged in as + + + + {username} + + + ); + } + const renderLayers = ( layers: LayerExample[], parentLayer?: LayerExample, @@ -245,6 +254,22 @@ export const LayersControlPanel = ({ {username} )} + + {showLogoutWarning && ( + { + dispatch(arcGisLogout()); + setShowLogoutWarning(false); + }} + onCancel={() => { setShowLogoutWarning(false); }} + /> + )} + ); diff --git a/src/components/modal-dialog/modal-dialog.tsx b/src/components/modal-dialog/modal-dialog.tsx new file mode 100644 index 00000000..100936f9 --- /dev/null +++ b/src/components/modal-dialog/modal-dialog.tsx @@ -0,0 +1,142 @@ +import styled, { useTheme } from "styled-components"; +import { ActionButton } from "../action-button/action-button"; +import { ActionButtonVariant, LayoutProps } from "../../types"; +import CloseIcon from "../../../public/icons/close.svg"; +import { Popover } from "react-tiny-popover"; + +const Overlay = styled.div` + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + z-index: 103; + background: #00000099; + `; + +const Container = styled.div<{ width: number, height: number }>` + position: absolute; + display: flex; + flex-direction: column; + border-radius: 8px; + background: ${({ theme }) => theme.colors.mainColor}; + width: ${({ width }) => (`${width}px`)}; + height: ${({ height }) => (`${height}px`)}; + left: calc(50% - ${({ width }) => (`${width * 0.5}px`)} ); + top: calc(50% - ${({ height }) => (`${height * 0.5}px`)} ); + z-index: 104; + `; + +const IconContainer = styled.div` + position: absolute; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + width: 44px; + height: 44px; + top: 13px; + right: 14px; + `; + +const ContentContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: start; + height: 100%; + width: 100%; + margin: 32px 32px 0 32px; + row-gap: 16px; + color: ${({ theme }) => theme.colors.fontColor}; +`; + +const Title = styled.div` + font-style: normal; + font-weight: 700; + font-size: 32px; + line-height: 45px; +`; + +const ButtonsContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: center; //space-between; + margin: 32px; + column-gap: 18px; + { > * + { + width: 180px; + } + } +`; + +type LogoutPanelProps = { + title: string; + content: (() => JSX.Element) | JSX.Element; + width: number, + height: number, + onCancel: () => void; + onConfirm: () => void; +}; + +const CloseCrossButton = styled(CloseIcon)` + &:hover { + fill: ${({ theme }) => theme.colors.mainDimColorInverted}; + } + `; + +export const ModalDialog = ({ + title, + content, + width, + height, + onCancel, + onConfirm, +}: LogoutPanelProps) => { + const theme = useTheme(); + + const renderPopoverContent = (): JSX.Element => { + return ( + <> + + + + + + + {title} + {typeof content === 'function' ? content() : content} + + + + Cancel + + Log out + + + + ); + } + + const getPopoverStyle = () => { + return { + zIndex: "104", + width: "100%", + height: "100%" + }; + }; + + return ( + + <> + + ); +}; diff --git a/src/pages/arcgis-auth-popup/arcgis-auth-popup.tsx b/src/pages/arcgis-auth-popup/arcgis-auth-popup.tsx index e55891fe..28ec358c 100644 --- a/src/pages/arcgis-auth-popup/arcgis-auth-popup.tsx +++ b/src/pages/arcgis-auth-popup/arcgis-auth-popup.tsx @@ -5,8 +5,7 @@ import { useAppLayout, } from "../../utils/hooks/layout"; -import { ArcGISIdentityManager } from '@esri/arcgis-rest-request'; -import { getAuthOptions } from "../../utils/arcgis-auth"; +import { arcGisCompleteLogin } from "../../utils/arcgis-auth"; export type LayoutProps = { layout: string; @@ -37,22 +36,10 @@ const AuthContainer = styled.div` export const AuthApp = () => { const layout = useAppLayout(); - - const { redirectUrl, clientId } = getAuthOptions(); - if (!clientId) { - console.error("The ClientId is not defined in .env file."); - } else { - const options = { - clientId: clientId, - redirectUri: redirectUrl, - popup: true, - pkce: true - } - ArcGISIdentityManager.completeOAuth2(options); - } + arcGisCompleteLogin(); return ( ); - -} \ No newline at end of file + +} diff --git a/src/redux/store.ts b/src/redux/store.ts index 51c1ffe5..4b5b407d 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -22,7 +22,6 @@ const rootReducer = combineReducers({ symbolization: symbolizationSliceReducer, i3sStats: i3sStatsSliceReducer, arcGisAuth: arcGisAuthSliceReducer, - }); export const setupStore = (preloadedState?: PreloadedState) => { diff --git a/src/utils/arcgis-auth.ts b/src/utils/arcgis-auth.ts index b4cad034..a6921493 100644 --- a/src/utils/arcgis-auth.ts +++ b/src/utils/arcgis-auth.ts @@ -17,15 +17,32 @@ const updateSessionInfo = async (session?: ArcGISIdentityManager): Promise { - return { - redirectUrl: `${window.location.protocol}//${window.location.hostname}:${window.location.port}/auth`, - clientId: process.env.REACT_APP_ARCGIS_REST_CLIENT_ID +const getAuthOptions = () => { + const port = window.location.port ? `:${window.location.port}` : ''; + const options = { + redirectUri: `${window.location.protocol}//${window.location.hostname}${port}/auth`, + clientId: process.env.REACT_APP_ARCGIS_REST_CLIENT_ID || '', + popup: true, + pkce: true + }; + + if (!options.clientId) { + console.error("The ClientId is not defined in .env file."); } + return options; } /** @@ -37,40 +54,45 @@ export const getAuthenticatedUser = (): string => { } /** - * Makes a ArcGIS login request by opening a popup dialog. + * Makes an ArcGIS login request by opening a popup dialog. * @returns email of the user logged in or an empty string if the user is not logged in. */ export const arcGisRequestLogin = async () => { - const { redirectUrl, clientId } = getAuthOptions(); - - if (!clientId) { - console.error("The ClientId is not defined in .env file."); - return ''; - } - const options = { - clientId: clientId, - redirectUri: redirectUrl, - popup: true, - pkce: true - } - let email = ''; - let session: ArcGISIdentityManager | undefined; - try { - session = await ArcGISIdentityManager.beginOAuth2(options); - } - finally { - // In case of an exception the session is not set. - // So the following call will remove any session stored in the local storage. - email = await updateSessionInfo(session); + + const options = getAuthOptions(); + if (options.clientId) { + let session: ArcGISIdentityManager | undefined; + try { + session = await ArcGISIdentityManager.beginOAuth2(options); + } + finally { + // In case of an exception the session is not set. + // So the following call will remove any session stored in the local storage. + email = await updateSessionInfo(session); + } } return email; }; /** - * Makes a ArcGIS logout request. + * Completes the ArcGIS login request started by {@link arcGisRequestLogin}. + */ +export const arcGisCompleteLogin = async () => { + const options = getAuthOptions(); + if (options.clientId) { + ArcGISIdentityManager.completeOAuth2(options); + } +} + +/** + * Makes an ArcGIS logout request. * @returns empty string */ export const arcGisRequestLogout = async () => { + const session = getArcGisSession(); + if (session) { + await ArcGISIdentityManager.destroy(session); + } return await updateSessionInfo(); };