improvements & bugfixes

This commit is contained in:
Myzel394 2022-10-15 10:36:17 +02:00
parent 457ef7bc8c
commit 30c52cdaeb
10 changed files with 153 additions and 121 deletions

View File

@ -26,15 +26,20 @@ function MultiStepForm({
const [currentSize, setCurrentSize] = useState<any>(null) const [currentSize, setCurrentSize] = useState<any>(null)
const [nextSize, setNextSize] = useState<any>(null) const [nextSize, setNextSize] = useState<any>(null)
const isTransitioning = currentIndex !== index const isTransitioning = currentIndex < index
useEffect(() => { useEffect(() => {
if (index !== currentIndex) { if (index > currentIndex) {
$timeout.current = setTimeout(() => { $timeout.current = setTimeout(() => {
setCurrentSize(null) setCurrentSize(null)
setNextSize(null) setNextSize(null)
setCurrentIndex(index) setCurrentIndex(index)
}, duration) }, duration)
} else if (index < currentIndex) {
// "Going-back" animation is not supported
setCurrentIndex(index)
setCurrentSize(null)
setNextSize(null)
} }
return $timeout.current?.cancel! return $timeout.current?.cancel!

View File

@ -19,7 +19,7 @@ export default function OpenMailButton({
return ( return (
<Button <Button
startIcon={<IoMdMailOpen />} startIcon={<IoMdMailOpen />}
variant="contained" variant="text"
href={APP_LINK_MAP[domain].android} href={APP_LINK_MAP[domain].android}
> >
Open Mail Open Mail

View File

@ -1,37 +1,66 @@
import * as yup from "yup"
import {useFormik} from "formik" import {useFormik} from "formik"
import {MdEmail} from "react-icons/md" import {MdEmail} from "react-icons/md"
import {AxiosError} from "axios"
import React, {ReactElement} from "react" import React, {ReactElement} from "react"
import {InputAdornment, TextField} from "@mui/material" import {InputAdornment, TextField} from "@mui/material"
import {MultiStepFormElement, SimpleForm} from "~/components" import {MultiStepFormElement, SimpleForm} from "~/components"
import {signup} from "~/apis" import {checkIsDomainDisposable, signup} from "~/apis"
import {handleErrors} from "~/utils" import {parseFastapiError} from "~/utils"
import {ServerSettings} from "~/types" import {ServerSettings} from "~/server-types"
import DetectEmailAutofillService from "./DetectEmailAutofillService" import DetectEmailAutofillService from "./DetectEmailAutofillService"
import useSchema, {Form} from "./use-schema"
interface EmailFormProps { export interface EmailFormProps {
serverSettings: ServerSettings serverSettings: ServerSettings
onSignUp: (email: string) => void onSignUp: (email: string) => void
} }
interface Form {
email: string
detail?: string
}
const SCHEMA = yup.object().shape({
email: yup.string().email().required(),
})
export default function EmailForm({ export default function EmailForm({
onSignUp, onSignUp,
serverSettings, serverSettings,
}: EmailFormProps): ReactElement { }: EmailFormProps): ReactElement {
const schema = useSchema(serverSettings)
const formik = useFormik<Form>({ const formik = useFormik<Form>({
validationSchema: schema, validationSchema: SCHEMA,
initialValues: { initialValues: {
email: "", email: "",
}, },
onSubmit: (values, {setErrors}) => onSubmit: async (values, {setErrors}) => {
handleErrors( // Check is email disposable
values.email, try {
setErrors, const isDisposable = await checkIsDomainDisposable(values.email)
)(signup).then(({normalized_email}) => onSignUp(normalized_email)),
if (isDisposable) {
setErrors({
email: "Disposable email addresses are not allowed",
})
return
}
} catch {
setErrors({
detail: "An error occurred",
})
return
}
try {
await signup(values.email)
onSignUp(values.email)
} catch (error) {
setErrors(parseFastapiError(error as AxiosError))
}
},
}) })
return ( return (

View File

@ -1,44 +0,0 @@
import * as yup from "yup"
import {checkIsDomainDisposable} from "~/apis"
import {ServerSettings} from "~/types"
export interface Form {
email: string
detail?: string
}
export default function useSchema(
serverSettings: ServerSettings,
): yup.BaseSchema {
return yup.object().shape({
email: yup
.string()
.email()
.required()
.test(
"notDisposable",
"Disposable email addresses are not allowed",
async (value, context) => {
if (serverSettings.disposable_emails_enabled) {
return true
}
try {
await yup.string().email().validate(value, {
strict: true,
})
const isDisposable = await checkIsDomainDisposable(
value!.split("@")[1],
)
return !isDisposable
} catch ({message}) {
// @ts-ignore
context.createError({message})
return false
}
},
),
})
}

View File

@ -8,7 +8,7 @@ import {LoadingButton} from "@mui/lab"
import {Box, Grid, InputAdornment, Typography} from "@mui/material" import {Box, Grid, InputAdornment, Typography} from "@mui/material"
import {PasswordField} from "~/components" import {PasswordField} from "~/components"
import {encryptString, handleErrors} from "~/utils" import {encryptString} from "~/utils"
import {isDev} from "~/constants/development" import {isDev} from "~/constants/development"
export interface PasswordFormProps { export interface PasswordFormProps {
@ -18,6 +18,7 @@ export interface PasswordFormProps {
interface Form { interface Form {
password: string password: string
passwordConfirmation: string passwordConfirmation: string
detail?: string
} }
const schema = yup.object().shape({ const schema = yup.object().shape({
@ -47,11 +48,8 @@ export default function PasswordForm({email}: PasswordFormProps): ReactElement {
password: "", password: "",
passwordConfirmation: "", passwordConfirmation: "",
}, },
onSubmit: (values, {setErrors}) => onSubmit: async (values, {setErrors}) => {
handleErrors( try {
values,
setErrors,
)(async () => {
const keyPair = await awaitGenerateKey const keyPair = await awaitGenerateKey
const encryptedPrivateKey = encryptString( const encryptedPrivateKey = encryptString(
@ -60,7 +58,10 @@ export default function PasswordForm({email}: PasswordFormProps): ReactElement {
) )
console.log(encryptedPrivateKey) console.log(encryptedPrivateKey)
}), } catch (error) {
setErrors({detail: "An error occurred"})
}
},
}) })
return ( return (

View File

@ -1,18 +1,36 @@
import React, {ReactElement} from "react" import {MdCancel, MdEdit} from "react-icons/md"
import React, {ReactElement, useState} from "react"
import {Grid, Typography} from "@mui/material" import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Grid,
IconButton,
Typography,
} from "@mui/material"
import {MultiStepFormElement, OpenMailButton} from "~/components" import {MultiStepFormElement, OpenMailButton} from "~/components"
import ResendMailButton from "~/route-widgets/root/YouGotMail/ResendMailButton" import ResendMailButton from "~/route-widgets/root/YouGotMail/ResendMailButton"
export interface YouGotMailProps { export interface YouGotMailProps {
email: string email: string
onGoBack: () => void
} }
export default function YouGotMail({email}: YouGotMailProps): ReactElement { export default function YouGotMail({
email,
onGoBack,
}: YouGotMailProps): ReactElement {
const [askToEditEmail, setAskToEditEmail] = useState<boolean>(false)
const domain = email.split("@")[1] const domain = email.split("@")[1]
return ( return (
<>
<MultiStepFormElement> <MultiStepFormElement>
<Grid <Grid
container container
@ -30,11 +48,30 @@ export default function YouGotMail({email}: YouGotMailProps): ReactElement {
</Grid> </Grid>
<Grid item> <Grid item>
<Typography variant="subtitle1" component="p"> <Typography variant="subtitle1" component="p">
We sent you an email with a link to confirm your email We sent you an email with a link to confirm your
address. Please check your inbox and click on the link email address. Please check your inbox and click on
to continue. the link to continue.
</Typography> </Typography>
</Grid> </Grid>
<Grid item>
<Grid
container
alignItems="center"
direction="row"
spacing={2}
>
<Grid item>
<code>{email}</code>
</Grid>
<Grid item>
<IconButton
onClick={() => setAskToEditEmail(true)}
>
<MdEdit />
</IconButton>
</Grid>
</Grid>
</Grid>
<Grid item> <Grid item>
<OpenMailButton domain={domain} /> <OpenMailButton domain={domain} />
</Grid> </Grid>
@ -43,5 +80,24 @@ export default function YouGotMail({email}: YouGotMailProps): ReactElement {
</Grid> </Grid>
</Grid> </Grid>
</MultiStepFormElement> </MultiStepFormElement>
<Dialog open={askToEditEmail}>
<DialogTitle>Edit email address?</DialogTitle>
<DialogContent>
<DialogContentText>
Would you like to return to the previous step and edit
your email address?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
startIcon={<MdCancel />}
onClick={() => setAskToEditEmail(false)}
>
Cancel
</Button>
<Button onClick={onGoBack}>Yes, edit email</Button>
</DialogActions>
</Dialog>
</>
) )
} }

View File

@ -3,7 +3,7 @@ import {useLocalStorage} from "react-use"
import {useLoaderData} from "react-router-dom" import {useLoaderData} from "react-router-dom"
import {MultiStepForm} from "~/components" import {MultiStepForm} from "~/components"
import {ServerSettings} from "~/types" import {ServerSettings} from "~/server-types"
import EmailForm from "~/route-widgets/root/EmailForm" import EmailForm from "~/route-widgets/root/EmailForm"
import YouGotMail from "~/route-widgets/root/YouGotMail" import YouGotMail from "~/route-widgets/root/YouGotMail"
@ -24,7 +24,11 @@ export default function SignupRoute(): ReactElement {
onSignUp={setEmail} onSignUp={setEmail}
key="email" key="email"
/>, />,
<YouGotMail email={email || ""} key="you_got_mail" />, <YouGotMail
onGoBack={() => setEmail("")}
email={email || ""}
key="you_got_mail"
/>,
]} ]}
index={index} index={index}
/> />

View File

@ -6,7 +6,7 @@ import React, {ReactElement} from "react"
import {Grid, Paper, Typography, useTheme} from "@mui/material" import {Grid, Paper, Typography, useTheme} from "@mui/material"
import {ServerSettings} from "~/types" import {ServerSettings} from "~/server-types"
import {validateEmail} from "~/apis" import {validateEmail} from "~/apis"
const emailSchema = yup.string().email() const emailSchema = yup.string().email()

View File

@ -1,17 +0,0 @@
import {FormikHelpers} from "formik"
import parseFastAPIError from "./parse-fastapi-error"
import {AxiosError} from "axios"
export default function handleErrors<T = any, Result = any, Values = any>(
values: T,
setErrors: FormikHelpers<Values>["setErrors"],
) {
return async (callback: (values: T) => Promise<Result>) => {
try {
const result = await callback(values)
return result
} catch (error) {
setErrors(parseFastAPIError(error as AxiosError))
}
}
}

View File

@ -1,9 +1,7 @@
export * from "./app-url-links" export * from "./app-url-links"
export {default as APP_LINK_MAP} from "./app-url-links" export {default as APP_LINK_MAP} from "./app-url-links"
export * from "./encrypt-string" export * from "./encrypt-string"
export { default as encryptString } from "./encrypt-string" export {default as encryptString} from "./encrypt-string"
export * from "./handle-errors"
export {default as handleErrors} from "./handle-errors"
export * from "./parse-fastapi-error" export * from "./parse-fastapi-error"
export {default as parseFastapiError} from "./parse-fastapi-error" export {default as parseFastapiError} from "./parse-fastapi-error"
export * from "./when-element-has-bounds" export * from "./when-element-has-bounds"