diff --git a/src/AuthContext/use-extension-handler.ts b/src/AuthContext/use-extension-handler.ts index f242691..18ad402 100644 --- a/src/AuthContext/use-extension-handler.ts +++ b/src/AuthContext/use-extension-handler.ts @@ -4,6 +4,7 @@ import {useEvent} from "react-use" import {ExtensionKleckEvent} from "~/extension-types" import {User} from "~/server-types" +import {AUTHENTICATION_PATHS} from "~/constants/values" export interface UseExtensionHandlerResult { sharePassword: () => void @@ -69,7 +70,10 @@ export default function useExtensionHandler( ) break case "enter-password": - if ($enterPasswordAmount.current < 1) { + if ( + $enterPasswordAmount.current < 1 && + !AUTHENTICATION_PATHS.includes(location.pathname) + ) { $enterPasswordAmount.current += 1 navigate("/enter-password") diff --git a/src/constants/values.ts b/src/constants/values.ts index c9234dd..dcde7d1 100644 --- a/src/constants/values.ts +++ b/src/constants/values.ts @@ -15,3 +15,4 @@ export const DEFAULT_ALIAS_NOTE: AliasNote = { } export const ERROR_SNACKBAR_SHOW_DURATION = 5000 export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000 +export const AUTHENTICATION_PATHS = ["/auth/login", "/auth/signup", "/auth/complete-account"] diff --git a/src/hooks/index.ts b/src/hooks/index.ts index b5d3ebc..6861b1c 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -14,3 +14,5 @@ export * from "./use-error-success-snacks" export {default as useErrorSuccessSnacks} from "./use-error-success-snacks" export * from "./use-is-any-input-focused" export {default as useIsAnyInputFocused} from "./use-is-any-input-focused" +export * from "./use-extension-handler" +export {default as useExtensionHandler} from "./use-extension-handler" diff --git a/src/hooks/use-extension-handler.ts b/src/hooks/use-extension-handler.ts new file mode 100644 index 0000000..5bcb253 --- /dev/null +++ b/src/hooks/use-extension-handler.ts @@ -0,0 +1,23 @@ +import {useCallback} from "react" +import {useEvent} from "react-use" + +import {ExtensionKleckEvent} from "~/extension-types" + +export interface UseExtensionHandlerData { + onEnterPassword?: () => void +} + +export default function useExtensionHandler({onEnterPassword}: UseExtensionHandlerData): void { + const handleExtensionEvent = useCallback( + (event: ExtensionKleckEvent) => { + switch (event.detail.type) { + case "enter-password": + onEnterPassword?.() + break + } + }, + [onEnterPassword], + ) + + useEvent("kleckrelay-kleck", handleExtensionEvent) +} diff --git a/src/hooks/use-user.ts b/src/hooks/use-user.ts index ef62835..443d506 100644 --- a/src/hooks/use-user.ts +++ b/src/hooks/use-user.ts @@ -2,14 +2,9 @@ import {useLocation, useNavigate} from "react-router-dom" import {useContext, useLayoutEffect} from "react" import {ServerUser, User} from "~/server-types" +import {AUTHENTICATION_PATHS} from "~/constants/values" import AuthContext from "~/AuthContext/AuthContext" -const AUTHENTICATION_PATHS = [ - "/auth/login", - "/auth/signup", - "/auth/complete-account", -] - /// Returns the currently authenticated user. // If the user is not authenticated, it will automatically redirect to the login page. export default function useUser(): ServerUser | User { @@ -18,10 +13,7 @@ export default function useUser(): ServerUser | User { const {user, isAuthenticated} = useContext(AuthContext) useLayoutEffect(() => { - if ( - !isAuthenticated && - !AUTHENTICATION_PATHS.includes(location.pathname) - ) { + if (!isAuthenticated && !AUTHENTICATION_PATHS.includes(location.pathname)) { navigate("/auth/login") } }, [isAuthenticated, navigate]) diff --git a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx index f2aaaed..7ff1845 100644 --- a/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx +++ b/src/route-widgets/CompleteAccountRoute/PasswordForm.tsx @@ -6,13 +6,13 @@ import {AxiosError} from "axios" import {useTranslation} from "react-i18next" import {Box, InputAdornment} from "@mui/material" import {useMutation} from "@tanstack/react-query" -import React, {ReactElement, useContext, useMemo} from "react" +import React, {ReactElement, useCallback, useContext, useMemo, useRef} from "react" import passwordGenerator from "secure-random-password" import {PasswordField, SimpleForm} from "~/components" import {buildEncryptionPassword, encryptString} from "~/utils" import {isDev} from "~/constants/development" -import {useSystemPreferredTheme, useUser} from "~/hooks" +import {useExtensionHandler, useSystemPreferredTheme, useUser} from "~/hooks" import {MASTER_PASSWORD_LENGTH} from "~/constants/values" import {AuthenticationDetails, UserNote} from "~/server-types" import {UpdateAccountData, updateAccount} from "~/apis" @@ -34,6 +34,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement const user = useUser() const theme = useSystemPreferredTheme() + const $password = useRef(null) + const $passwordConfirmation = useRef(null) const schema = yup.object().shape({ password: yup.string().required(), passwordConfirmation: yup @@ -113,6 +115,17 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement } }, }) + const focusPassword = useCallback(() => { + if ($password.current?.value !== "") { + $passwordConfirmation.current?.focus() + } else { + $password.current?.focus() + } + }, []) + + useExtensionHandler({ + onEnterPassword: focusPassword, + }) return ( diff --git a/src/route-widgets/LoginRoute/EmailForm.tsx b/src/route-widgets/LoginRoute/EmailForm.tsx index 18ac830..4159961 100644 --- a/src/route-widgets/LoginRoute/EmailForm.tsx +++ b/src/route-widgets/LoginRoute/EmailForm.tsx @@ -1,5 +1,5 @@ import * as yup from "yup" -import {ReactElement} from "react" +import {ReactElement, useCallback, useRef} from "react" import {AxiosError} from "axios" import {useFormik} from "formik" import {MdEmail} from "react-icons/md" @@ -11,6 +11,7 @@ import {InputAdornment, TextField} from "@mui/material" import {LoginWithEmailResult, loginWithEmail} from "~/apis" import {parseFastAPIError} from "~/utils" import {MultiStepFormElement, SimpleForm} from "~/components" +import {useExtensionHandler} from "~/hooks" export interface EmailFormProps { onLogin: (email: string, sameRequestToken: string) => void @@ -24,6 +25,7 @@ interface Form { export default function EmailForm({onLogin}: EmailFormProps): ReactElement { const {t} = useTranslation() + const $password = useRef(null) const schema = yup.object().shape({ email: yup .string() @@ -50,6 +52,12 @@ export default function EmailForm({onLogin}: EmailFormProps): ReactElement { }, }) + const focusPassword = useCallback(() => $password.current?.focus(), []) + + useExtensionHandler({ + onEnterPassword: focusPassword, + }) + return (
@@ -68,6 +76,7 @@ export default function EmailForm({onLogin}: EmailFormProps): ReactElement { id="email" label="Email" placeholder={t("routes.LoginRoute.forms.email.form.email.placeholder")} + inputRef={$password} inputMode="email" value={formik.values.email} onChange={formik.handleChange} diff --git a/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx b/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx index 06dd345..7db35d6 100644 --- a/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx +++ b/src/route-widgets/SignupRoute/EmailForm/EmailForm.tsx @@ -3,7 +3,7 @@ import {useFormik} from "formik" import {MdEmail} from "react-icons/md" import {AxiosError} from "axios" import {useTranslation} from "react-i18next" -import React, {ReactElement} from "react" +import React, {ReactElement, useCallback, useRef} from "react" import {InputAdornment, TextField} from "@mui/material" import {useMutation} from "@tanstack/react-query" @@ -13,6 +13,7 @@ import {SignupResult, checkIsDomainDisposable, signup} from "~/apis" import {parseFastAPIError} from "~/utils" import {ServerSettings} from "~/server-types" +import {useExtensionHandler} from "~/hooks" import DetectEmailAutofillService from "./DetectEmailAutofillService" export interface EmailFormProps { @@ -28,6 +29,7 @@ interface Form { export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): ReactElement { const {t} = useTranslation() + const $password = useRef(null) const schema = yup.object().shape({ email: yup .string() @@ -69,6 +71,11 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R } }, }) + const focusPassword = useCallback(() => $password.current?.focus(), []) + + useExtensionHandler({ + onEnterPassword: focusPassword, + }) return ( <> @@ -92,6 +99,7 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R "routes.SignupRoute.forms.email.form.email.placeholder", )} inputMode="email" + inputRef={$password} value={formik.values.email} onChange={formik.handleChange} disabled={formik.isSubmitting}