improved extension integration

This commit is contained in:
Myzel394 2022-12-16 22:19:39 +01:00
parent cf21360509
commit 35e4630b51
8 changed files with 67 additions and 15 deletions

View File

@ -4,6 +4,7 @@ import {useEvent} from "react-use"
import {ExtensionKleckEvent} from "~/extension-types" import {ExtensionKleckEvent} from "~/extension-types"
import {User} from "~/server-types" import {User} from "~/server-types"
import {AUTHENTICATION_PATHS} from "~/constants/values"
export interface UseExtensionHandlerResult { export interface UseExtensionHandlerResult {
sharePassword: () => void sharePassword: () => void
@ -69,7 +70,10 @@ export default function useExtensionHandler(
) )
break break
case "enter-password": case "enter-password":
if ($enterPasswordAmount.current < 1) { if (
$enterPasswordAmount.current < 1 &&
!AUTHENTICATION_PATHS.includes(location.pathname)
) {
$enterPasswordAmount.current += 1 $enterPasswordAmount.current += 1
navigate("/enter-password") navigate("/enter-password")

View File

@ -15,3 +15,4 @@ export const DEFAULT_ALIAS_NOTE: AliasNote = {
} }
export const ERROR_SNACKBAR_SHOW_DURATION = 5000 export const ERROR_SNACKBAR_SHOW_DURATION = 5000
export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000 export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000
export const AUTHENTICATION_PATHS = ["/auth/login", "/auth/signup", "/auth/complete-account"]

View File

@ -14,3 +14,5 @@ export * from "./use-error-success-snacks"
export {default as useErrorSuccessSnacks} from "./use-error-success-snacks" export {default as useErrorSuccessSnacks} from "./use-error-success-snacks"
export * from "./use-is-any-input-focused" export * from "./use-is-any-input-focused"
export {default as useIsAnyInputFocused} 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"

View File

@ -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)
}

View File

@ -2,14 +2,9 @@ import {useLocation, useNavigate} from "react-router-dom"
import {useContext, useLayoutEffect} from "react" import {useContext, useLayoutEffect} from "react"
import {ServerUser, User} from "~/server-types" import {ServerUser, User} from "~/server-types"
import {AUTHENTICATION_PATHS} from "~/constants/values"
import AuthContext from "~/AuthContext/AuthContext" import AuthContext from "~/AuthContext/AuthContext"
const AUTHENTICATION_PATHS = [
"/auth/login",
"/auth/signup",
"/auth/complete-account",
]
/// Returns the currently authenticated user. /// Returns the currently authenticated user.
// If the user is not authenticated, it will automatically redirect to the login page. // If the user is not authenticated, it will automatically redirect to the login page.
export default function useUser(): ServerUser | User { export default function useUser(): ServerUser | User {
@ -18,10 +13,7 @@ export default function useUser(): ServerUser | User {
const {user, isAuthenticated} = useContext(AuthContext) const {user, isAuthenticated} = useContext(AuthContext)
useLayoutEffect(() => { useLayoutEffect(() => {
if ( if (!isAuthenticated && !AUTHENTICATION_PATHS.includes(location.pathname)) {
!isAuthenticated &&
!AUTHENTICATION_PATHS.includes(location.pathname)
) {
navigate("/auth/login") navigate("/auth/login")
} }
}, [isAuthenticated, navigate]) }, [isAuthenticated, navigate])

View File

@ -6,13 +6,13 @@ import {AxiosError} from "axios"
import {useTranslation} from "react-i18next" import {useTranslation} from "react-i18next"
import {Box, InputAdornment} from "@mui/material" import {Box, InputAdornment} from "@mui/material"
import {useMutation} from "@tanstack/react-query" 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 passwordGenerator from "secure-random-password"
import {PasswordField, SimpleForm} from "~/components" import {PasswordField, SimpleForm} from "~/components"
import {buildEncryptionPassword, encryptString} from "~/utils" import {buildEncryptionPassword, encryptString} from "~/utils"
import {isDev} from "~/constants/development" import {isDev} from "~/constants/development"
import {useSystemPreferredTheme, useUser} from "~/hooks" import {useExtensionHandler, useSystemPreferredTheme, useUser} from "~/hooks"
import {MASTER_PASSWORD_LENGTH} from "~/constants/values" import {MASTER_PASSWORD_LENGTH} from "~/constants/values"
import {AuthenticationDetails, UserNote} from "~/server-types" import {AuthenticationDetails, UserNote} from "~/server-types"
import {UpdateAccountData, updateAccount} from "~/apis" import {UpdateAccountData, updateAccount} from "~/apis"
@ -34,6 +34,8 @@ export default function PasswordForm({onDone}: PasswordFormProps): ReactElement
const user = useUser() const user = useUser()
const theme = useSystemPreferredTheme() const theme = useSystemPreferredTheme()
const $password = useRef<HTMLInputElement | null>(null)
const $passwordConfirmation = useRef<HTMLInputElement | null>(null)
const schema = yup.object().shape({ const schema = yup.object().shape({
password: yup.string().required(), password: yup.string().required(),
passwordConfirmation: yup 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 ( return (
<Box maxWidth="80vw"> <Box maxWidth="80vw">

View File

@ -1,5 +1,5 @@
import * as yup from "yup" import * as yup from "yup"
import {ReactElement} from "react" import {ReactElement, useCallback, useRef} from "react"
import {AxiosError} from "axios" import {AxiosError} from "axios"
import {useFormik} from "formik" import {useFormik} from "formik"
import {MdEmail} from "react-icons/md" import {MdEmail} from "react-icons/md"
@ -11,6 +11,7 @@ import {InputAdornment, TextField} from "@mui/material"
import {LoginWithEmailResult, loginWithEmail} from "~/apis" import {LoginWithEmailResult, loginWithEmail} from "~/apis"
import {parseFastAPIError} from "~/utils" import {parseFastAPIError} from "~/utils"
import {MultiStepFormElement, SimpleForm} from "~/components" import {MultiStepFormElement, SimpleForm} from "~/components"
import {useExtensionHandler} from "~/hooks"
export interface EmailFormProps { export interface EmailFormProps {
onLogin: (email: string, sameRequestToken: string) => void onLogin: (email: string, sameRequestToken: string) => void
@ -24,6 +25,7 @@ interface Form {
export default function EmailForm({onLogin}: EmailFormProps): ReactElement { export default function EmailForm({onLogin}: EmailFormProps): ReactElement {
const {t} = useTranslation() const {t} = useTranslation()
const $password = useRef<HTMLInputElement | null>(null)
const schema = yup.object().shape({ const schema = yup.object().shape({
email: yup email: yup
.string() .string()
@ -50,6 +52,12 @@ export default function EmailForm({onLogin}: EmailFormProps): ReactElement {
}, },
}) })
const focusPassword = useCallback(() => $password.current?.focus(), [])
useExtensionHandler({
onEnterPassword: focusPassword,
})
return ( return (
<MultiStepFormElement> <MultiStepFormElement>
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
@ -68,6 +76,7 @@ export default function EmailForm({onLogin}: EmailFormProps): ReactElement {
id="email" id="email"
label="Email" label="Email"
placeholder={t("routes.LoginRoute.forms.email.form.email.placeholder")} placeholder={t("routes.LoginRoute.forms.email.form.email.placeholder")}
inputRef={$password}
inputMode="email" inputMode="email"
value={formik.values.email} value={formik.values.email}
onChange={formik.handleChange} onChange={formik.handleChange}

View File

@ -3,7 +3,7 @@ import {useFormik} from "formik"
import {MdEmail} from "react-icons/md" import {MdEmail} from "react-icons/md"
import {AxiosError} from "axios" import {AxiosError} from "axios"
import {useTranslation} from "react-i18next" import {useTranslation} from "react-i18next"
import React, {ReactElement} from "react" import React, {ReactElement, useCallback, useRef} from "react"
import {InputAdornment, TextField} from "@mui/material" import {InputAdornment, TextField} from "@mui/material"
import {useMutation} from "@tanstack/react-query" import {useMutation} from "@tanstack/react-query"
@ -13,6 +13,7 @@ import {SignupResult, checkIsDomainDisposable, signup} from "~/apis"
import {parseFastAPIError} from "~/utils" import {parseFastAPIError} from "~/utils"
import {ServerSettings} from "~/server-types" import {ServerSettings} from "~/server-types"
import {useExtensionHandler} from "~/hooks"
import DetectEmailAutofillService from "./DetectEmailAutofillService" import DetectEmailAutofillService from "./DetectEmailAutofillService"
export interface EmailFormProps { export interface EmailFormProps {
@ -28,6 +29,7 @@ interface Form {
export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): ReactElement { export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): ReactElement {
const {t} = useTranslation() const {t} = useTranslation()
const $password = useRef<HTMLInputElement | null>(null)
const schema = yup.object().shape({ const schema = yup.object().shape({
email: yup email: yup
.string() .string()
@ -69,6 +71,11 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R
} }
}, },
}) })
const focusPassword = useCallback(() => $password.current?.focus(), [])
useExtensionHandler({
onEnterPassword: focusPassword,
})
return ( return (
<> <>
@ -92,6 +99,7 @@ export default function EmailForm({onSignUp, serverSettings}: EmailFormProps): R
"routes.SignupRoute.forms.email.form.email.placeholder", "routes.SignupRoute.forms.email.form.email.placeholder",
)} )}
inputMode="email" inputMode="email"
inputRef={$password}
value={formik.values.email} value={formik.values.email}
onChange={formik.handleChange} onChange={formik.handleChange}
disabled={formik.isSubmitting} disabled={formik.isSubmitting}