diff --git a/client/src/actions/core.js b/client/src/actions/core.js index 85182ffd..2ae879ba 100644 --- a/client/src/actions/core.js +++ b/client/src/actions/core.js @@ -98,10 +98,24 @@ logout.revokeAccessToken = () => ({ payload: {}, }); +const showAutoLogoutWarning = (expiresAt) => ({ + type: ActionTypes.AUTO_LOGOUT_WARNING_SHOW, + payload: { + expiresAt, + }, +}); + +const dismissAutoLogoutWarning = () => ({ + type: ActionTypes.AUTO_LOGOUT_WARNING_DISMISS, + payload: {}, +}); + export default { initializeCore, toggleFavorites, toggleEditMode, updateHomeView, logout, + showAutoLogoutWarning, + dismissAutoLogoutWarning, }; diff --git a/client/src/components/common/AutoLogoutWarningModal/AutoLogoutWarningModal.jsx b/client/src/components/common/AutoLogoutWarningModal/AutoLogoutWarningModal.jsx new file mode 100644 index 00000000..a9807cd7 --- /dev/null +++ b/client/src/components/common/AutoLogoutWarningModal/AutoLogoutWarningModal.jsx @@ -0,0 +1,71 @@ +/*! + * Copyright (c) 2024 PLANKA Software GmbH + * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md + */ + +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTranslation } from 'react-i18next'; +import { Button, Modal } from 'semantic-ui-react'; + +import selectors from '../../../selectors'; +import entryActions from '../../../entry-actions'; + +const AutoLogoutWarningModal = React.memo(() => { + const warning = useSelector(selectors.selectAutoLogoutWarning); + const dispatch = useDispatch(); + const [t] = useTranslation(); + + const computeRemaining = useCallback( + () => (warning ? Math.max(0, Math.ceil((warning.expiresAt - Date.now()) / 1000)) : 0), + [warning], + ); + + const [remainingSeconds, setRemainingSeconds] = useState(computeRemaining); + + useEffect(() => { + if (!warning) { + return undefined; + } + + setRemainingSeconds(computeRemaining()); + + const interval = setInterval(() => { + setRemainingSeconds(computeRemaining()); + }, 500); + + return () => clearInterval(interval); + }, [warning, computeRemaining]); + + const handleStayClick = useCallback(() => { + dispatch(entryActions.dismissAutoLogoutWarning()); + }, [dispatch]); + + const handleLogoutClick = useCallback(() => { + dispatch(entryActions.logout()); + }, [dispatch]); + + if (!warning) { + return null; + } + + return ( + + {t('common.autoLogoutWarning_title')} + +

{t('common.autoLogoutWarning_body', { seconds: remainingSeconds })}

+
+ + +