mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 07:55:25 +02:00
improvements & bugfixes
This commit is contained in:
parent
457ef7bc8c
commit
30c52cdaeb
@ -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!
|
||||||
|
@ -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
|
||||||
|
@ -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 (
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
@ -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 (
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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()
|
||||||
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user