From a7eece79383aa9a7778a589df5b6b9491b2a5de5 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:36:54 +0100 Subject: [PATCH] bugfixes --- public/locales/en/translation.json | 7 + src/App.tsx | 5 + src/AuthContext/AuthContextProvider.tsx | 41 ++--- .../AliasesRoute/AliasesDetails.tsx | 6 +- .../CompleteAccountRoute/PasswordForm.tsx | 164 +++++++----------- .../ConfirmCodeForm/ConfirmCodeForm.tsx | 3 +- .../ReportsRoute/EmptyStateScreen.tsx | 2 +- src/routes/AuthenticatedRoute.tsx | 71 +++++--- src/routes/CompleteAccountRoute.tsx | 5 +- src/routes/EnterDecryptionPassword.tsx | 32 +--- src/routes/LoginRoute.tsx | 27 +-- src/routes/LogoutRoute.tsx | 41 +++++ 12 files changed, 214 insertions(+), 190 deletions(-) create mode 100644 src/routes/LogoutRoute.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 8c9334c..e127752 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -226,6 +226,10 @@ "saveAction": "Save preferences" } } + }, + "LogoutRoute": { + "title": "Log out", + "description": "We are logging you out..." } }, @@ -240,6 +244,9 @@ "signup": "Sign up", "login": "Log in" }, + "AuthenticatedRoute": { + "logout": "Logout" + }, "EnterDecryptionPassword": { "title": "Decrypt Reports", "description": "Please enter your password so that your reports can de decrypted.", diff --git a/src/App.tsx b/src/App.tsx index 64853e0..e195c5d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,6 +16,7 @@ import AuthenticatedRoute from "~/routes/AuthenticatedRoute" import CompleteAccountRoute from "~/routes/CompleteAccountRoute" import EnterDecryptionPassword from "~/routes/EnterDecryptionPassword" import LoginRoute from "~/routes/LoginRoute" +import LogoutRoute from "~/routes/LogoutRoute" import ReportDetailRoute from "~/routes/ReportDetailRoute" import ReportsRoute from "~/routes/ReportsRoute" import RootRoute from "~/routes/Root" @@ -54,6 +55,10 @@ const router = createBrowserRouter([ path: "/auth/complete-account", element: , }, + { + path: "/auth/logout", + element: , + }, ], }, { diff --git a/src/AuthContext/AuthContextProvider.tsx b/src/AuthContext/AuthContextProvider.tsx index d2d421f..69d9b9c 100644 --- a/src/AuthContext/AuthContextProvider.tsx +++ b/src/AuthContext/AuthContextProvider.tsx @@ -2,16 +2,12 @@ import {ReactElement, ReactNode, useCallback, useEffect, useMemo} from "react" import {useLocalStorage} from "react-use" import {AxiosError} from "axios" import {decrypt, readMessage, readPrivateKey} from "openpgp" +import {useNavigate} from "react-router-dom" import {useMutation} from "@tanstack/react-query" import {ServerUser, User} from "~/server-types" -import { - REFRESH_TOKEN_URL, - RefreshTokenResult, - logout as logoutUser, - refreshToken, -} from "~/apis" +import {REFRESH_TOKEN_URL, RefreshTokenResult, logout as logoutUser, refreshToken} from "~/apis" import {client} from "~/constants/axios-client" import {decryptString, encryptString} from "~/utils" @@ -21,20 +17,15 @@ export interface AuthContextProviderProps { children: ReactNode } -export default function AuthContextProvider({ - children, -}: AuthContextProviderProps): ReactElement { - const {mutateAsync: refresh} = useMutation< - RefreshTokenResult, - AxiosError, - void - >(refreshToken, { +export default function AuthContextProvider({children}: AuthContextProviderProps): ReactElement { + const {mutateAsync: refresh} = useMutation(refreshToken, { onError: () => logout(false), }) - const [decryptionPassword, setDecryptionPassword] = useLocalStorage< - string | null - >("_global-context-auth-decryption-password", null) + const [decryptionPassword, setDecryptionPassword] = useLocalStorage( + "_global-context-auth-decryption-password", + null, + ) const [user, setUser] = useLocalStorage( "_global-context-auth-user", null, @@ -115,10 +106,7 @@ export default function AuthContextProvider({ try { // Check if the password is correct - const masterPassword = decryptString( - user.encryptedPassword, - password, - ) + const masterPassword = decryptString(user.encryptedPassword, password) JSON.parse(decryptString(user.encryptedNotes, masterPassword)) } catch { return false @@ -162,15 +150,8 @@ export default function AuthContextProvider({ // Decrypt user notes useEffect(() => { - if ( - user && - !user.isDecrypted && - user.encryptedPassword && - masterPassword - ) { - const note = JSON.parse( - decryptUsingMasterPassword(user.encryptedNotes!), - ) + if (user && !user.isDecrypted && user.encryptedPassword && masterPassword) { + const note = JSON.parse(decryptUsingMasterPassword(user.encryptedNotes!)) const newUser: User = { ...user, diff --git a/src/route-widgets/AliasesRoute/AliasesDetails.tsx b/src/route-widgets/AliasesRoute/AliasesDetails.tsx index 3c389c1..d7cd522 100644 --- a/src/route-widgets/AliasesRoute/AliasesDetails.tsx +++ b/src/route-widgets/AliasesRoute/AliasesDetails.tsx @@ -2,6 +2,8 @@ import {ReactElement} from "react" import {useTranslation} from "react-i18next" import {useKeyPress} from "react-use" import {MdContentCopy} from "react-icons/md" +import {useSnackbar} from "notistack" +import {Link as RouterLink} from "react-router-dom" import copy from "copy-to-clipboard" import { @@ -16,7 +18,6 @@ import { import {AliasTypeIndicator} from "~/components" import {AliasList} from "~/server-types" import {useUIState} from "~/hooks" -import {useSnackbar} from "notistack" import {SUCCESS_SNACKBAR_SHOW_DURATION} from "~/constants/values" import CreateAliasButton from "~/route-widgets/AliasesRoute/CreateAliasButton" import EmptyStateScreen from "~/route-widgets/AliasesRoute/EmptyStateScreen" @@ -41,6 +42,7 @@ export default function AliasesDetails({aliases}: AliasesDetailsProps): ReactEle {aliasesUIState.map(alias => ( { if (isInCopyAddressMode) { @@ -60,7 +62,7 @@ export default function AliasesDetails({aliases}: AliasesDetailsProps): ReactEle ) } }} - href={`/aliases/${btoa(getAddress(alias))}`} + to={`/aliases/${btoa(getAddress(alias))}`} > diff --git a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx index cdb54fa..36beb01 100644 --- a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx @@ -11,7 +11,7 @@ import {LoadingButton} from "@mui/lab" import {Box, Grid, InputAdornment, Typography} from "@mui/material" import {useMutation} from "@tanstack/react-query" -import {PasswordField} from "~/components" +import {PasswordField, SimpleForm} from "~/components" import {buildEncryptionPassword, encryptString} from "~/utils" import {isDev} from "~/constants/development" import {useSystemPreferredTheme, useUser} from "~/hooks" @@ -68,7 +68,7 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement { onSuccess: ({user}) => { login(user) - onDone() + setTimeout(onDone, 0) }, }, ) @@ -119,102 +119,72 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement return (
- - - - - - {t("routes.CompleteAccountRoute.forms.password.title")} - - - - - {t("routes.CompleteAccountRoute.forms.password.description")} - - - - - - - - - - - ), - }} - /> - - - - - - ), - }} - /> - - - - - } - > - {t("routes.CompleteAccountRoute.forms.password.continueAction")} - - - + {[ + + + + ), + }} + />, + + + + ), + }} + />, + ]} +
) diff --git a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx index f1073c6..a9d55c4 100644 --- a/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx +++ b/src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx @@ -54,7 +54,8 @@ export default function ConfirmCodeForm({ return code.split("").every(char => chars.includes(char)) }, - ), + ) + .label(t("routes.LoginRoute.forms.confirmCode.form.code.label")), }) const {mutateAsync} = useMutation( verifyLoginWithEmail, diff --git a/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx b/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx index 93c1e71..10c28a9 100644 --- a/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx +++ b/src/route-widgets/ReportsRoute/EmptyStateScreen.tsx @@ -16,7 +16,7 @@ export default function EmptyStateScreen(): ReactElement { - , + diff --git a/src/routes/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute.tsx index 0fdbe45..70daf21 100644 --- a/src/routes/AuthenticatedRoute.tsx +++ b/src/routes/AuthenticatedRoute.tsx @@ -1,7 +1,10 @@ import {ReactElement} from "react" import {Outlet} from "react-router-dom" +import {Link as RouterLink} from "react-router-dom" +import {useTranslation} from "react-i18next" +import {MdLogout} from "react-icons/md" -import {Box, Grid, List, ListItem, Paper, useTheme} from "@mui/material" +import {Box, Button, Grid, List, ListItem, Paper, useTheme} from "@mui/material" import {useUser} from "~/hooks" import LockNavigationContextProvider from "~/LockNavigationContext/LockNavigationContextProvider" @@ -9,11 +12,12 @@ import NavigationButton, { NavigationSection, } from "~/route-widgets/AuthenticateRoute/NavigationButton" -const sections = ( - Object.keys(NavigationSection) as Array -).filter(value => isNaN(Number(value))) +const sections = (Object.keys(NavigationSection) as Array).filter( + value => isNaN(Number(value)), +) export default function AuthenticatedRoute(): ReactElement { + const {t} = useTranslation() const theme = useTheme() useUser() @@ -31,41 +35,64 @@ export default function AuthenticatedRoute(): ReactElement { display="flex" maxWidth="90vw" width="100%" + height="100%" justifyContent="center" alignItems="center" > - - + + {sections.map(key => ( - + ))} - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/routes/CompleteAccountRoute.tsx b/src/routes/CompleteAccountRoute.tsx index e18cb10..89700ea 100644 --- a/src/routes/CompleteAccountRoute.tsx +++ b/src/routes/CompleteAccountRoute.tsx @@ -29,7 +29,10 @@ export default function CompleteAccountRoute(): ReactElement { onYes={() => setShowGenerationReportForm(true)} onNo={navigateToNext} />, - , + setTimeout(navigateToNext, 0)} + key="password_form" + />, ]} index={showGenerationReportForm ? 1 : 0} /> diff --git a/src/routes/EnterDecryptionPassword.tsx b/src/routes/EnterDecryptionPassword.tsx index 43b38b1..8fe0429 100644 --- a/src/routes/EnterDecryptionPassword.tsx +++ b/src/routes/EnterDecryptionPassword.tsx @@ -31,10 +31,7 @@ export default function EnterDecryptionPassword(): ReactElement { password: "", }, onSubmit: async ({password}, {setErrors}) => { - const decryptionPassword = buildEncryptionPassword( - password, - user.email.address, - ) + const decryptionPassword = buildEncryptionPassword(password, user.email.address) if (!_setDecryptionPassword(decryptionPassword)) { setErrors({ @@ -43,7 +40,7 @@ export default function EnterDecryptionPassword(): ReactElement { ), }) } else { - navigateToNext() + setTimeout(navigateToNext, 0) } }, }) @@ -52,15 +49,9 @@ export default function EnterDecryptionPassword(): ReactElement {
{[ @@ -69,22 +60,15 @@ export default function EnterDecryptionPassword(): ReactElement { fullWidth name="password" id="password" - label={t( - "components.EnterDecryptionPassword.form.password.label", - )} + label={t("components.EnterDecryptionPassword.form.password.label")} placeholder={t( "components.EnterDecryptionPassword.form.password.placeholder", )} value={formik.values.password} onChange={formik.handleChange} disabled={formik.isSubmitting} - error={ - formik.touched.password && - Boolean(formik.errors.password) - } - helperText={ - formik.touched.password && formik.errors.password - } + error={formik.touched.password && Boolean(formik.errors.password)} + helperText={formik.touched.password && formik.errors.password} InputProps={{ startAdornment: ( diff --git a/src/routes/LoginRoute.tsx b/src/routes/LoginRoute.tsx index 27379ce..193cfe5 100644 --- a/src/routes/LoginRoute.tsx +++ b/src/routes/LoginRoute.tsx @@ -1,5 +1,6 @@ import {ReactElement, useContext, useState} from "react" import {useNavigate} from "react-router-dom" +import {useUpdateEffect} from "react-use" import {MultiStepForm} from "~/components" import AuthContext from "~/AuthContext/AuthContext" @@ -8,11 +9,23 @@ import EmailForm from "~/route-widgets/LoginRoute/EmailForm" export default function LoginRoute(): ReactElement { const navigate = useNavigate() - const {login} = useContext(AuthContext) + const {login, user} = useContext(AuthContext) const [email, setEmail] = useState("") const [sameRequestToken, setSameRequestToken] = useState("") + useUpdateEffect(() => { + if (!user) { + return + } + + if (user?.encryptedPassword) { + navigate("/enter-password") + } else { + navigate("/") + } + }, [user?.encryptedPassword]) + return ( , { - login(user) - - setTimeout(() => { - if (user.encryptedPassword) { - navigate("/enter-password") - } else { - navigate("/") - } - }, 0) - }} + onConfirm={login} email={email} sameRequestToken={sameRequestToken} />, diff --git a/src/routes/LogoutRoute.tsx b/src/routes/LogoutRoute.tsx new file mode 100644 index 0000000..8cb67f7 --- /dev/null +++ b/src/routes/LogoutRoute.tsx @@ -0,0 +1,41 @@ +import {ReactElement, useContext} from "react" +import {useTranslation} from "react-i18next" +import {useEffectOnce} from "react-use" + +import {Box, CircularProgress, Grid, Paper, Typography} from "@mui/material" + +import {useNavigateToNext} from "~/hooks" +import AuthContext from "~/AuthContext/AuthContext" + +export default function LogoutRoute(): ReactElement { + const {t} = useTranslation() + const navigateToNext = useNavigateToNext("/auth/login") + const {logout} = useContext(AuthContext) + + useEffectOnce(() => { + logout() + navigateToNext() + }) + + return ( + + + + + + {t("routes.LogoutRoute.title")} + + + + + + + + {t("routes.LogoutRoute.description")} + + + + + + ) +}