adding password form

This commit is contained in:
Myzel394 2022-10-16 16:50:49 +02:00
parent 2b20d129ae
commit 455ecf2a37
18 changed files with 89 additions and 32 deletions

View File

@ -11,6 +11,8 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.4", "@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4", "@emotion/styled": "^11.10.4",
"@mdi/js": "^7.0.96",
"@mdi/react": "^1.6.1",
"@mui/lab": "^5.0.0-alpha.103", "@mui/lab": "^5.0.0-alpha.103",
"@mui/material": "^5.10.9", "@mui/material": "^5.10.9",
"@tanstack/react-query": "^4.12.0", "@tanstack/react-query": "^4.12.0",

View File

@ -11,6 +11,7 @@ import AliasesRoute from "~/routes/AliasesRoute"
import AuthContextProvider from "~/AuthContext/AuthContextProvider" import AuthContextProvider from "~/AuthContext/AuthContextProvider"
import AuthenticateRoute from "~/routes/AuthenticateRoute" import AuthenticateRoute from "~/routes/AuthenticateRoute"
import AuthenticatedRoute from "~/routes/AuthenticatedRoute" import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
import RootRoute from "~/routes/Root" import RootRoute from "~/routes/Root"
import SignupRoute from "~/routes/SignupRoute" import SignupRoute from "~/routes/SignupRoute"
import VerifyEmailRoute from "~/routes/VerifyEmailRoute" import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
@ -35,6 +36,10 @@ const router = createBrowserRouter([
path: "/auth/signup", path: "/auth/signup",
element: <SignupRoute />, element: <SignupRoute />,
}, },
{
path: "/auth/complete-account",
element: <CompleteAccountRoute />,
},
], ],
}, },
{ {

View File

@ -5,7 +5,7 @@ import {User} from "~/server-types"
interface AuthContextTypeBase { interface AuthContextTypeBase {
user: User | null user: User | null
isAuthenticated: boolean isAuthenticated: boolean
login: (user: User) => Promise<void> login: (user: User, callback: () => void) => Promise<void>
logout: () => void logout: () => void
} }

View File

@ -34,8 +34,10 @@ export default function AuthContextProvider({
} }
}, []) }, [])
const login = useCallback(async (user: User) => { const login = useCallback(async (user: User, callback?: () => void) => {
setUser(user) setUser(user)
callback?.()
}, []) }, [])
const {mutateAsync: refresh} = useMutation< const {mutateAsync: refresh} = useMutation<

View File

@ -24,12 +24,12 @@ interface CreateAliasDataBase extends CreateAliasDataOther {
} }
interface CreateAliasDataRandomType extends CreateAliasDataBase { interface CreateAliasDataRandomType extends CreateAliasDataBase {
type: AliasType.Random type: AliasType.RANDOM
local?: undefined local?: undefined
} }
interface CreateAliasDataCustomType extends CreateAliasDataBase { interface CreateAliasDataCustomType extends CreateAliasDataBase {
type: AliasType.Custom type: AliasType.CUSTOM
local: string local: string
} }
@ -42,7 +42,7 @@ export default async function createAlias(
): Promise<Alias> { ): Promise<Alias> {
const {data} = await axios.post( const {data} = await axios.post(
`${import.meta.env.VITE_SERVER_BASE_URL}/alias`, `${import.meta.env.VITE_SERVER_BASE_URL}/alias`,
aliasData, {},
) )
return parseAlias(data) return parseAlias(data)

View File

@ -0,0 +1,4 @@
export interface UpdateAccountData {
password: string
publicKey: string
}

View File

@ -10,7 +10,7 @@ export default function MultiStepFormElement({
children, children,
}: MultiStepFormElementProps): ReactElement { }: MultiStepFormElementProps): ReactElement {
return ( return (
<Box width="90vw" justifyContent="center" alignItems="center"> <Box maxWidth="90vw" justifyContent="center" alignItems="center">
<Container maxWidth="xs">{children}</Container> <Container maxWidth="xs">{children}</Container>
</Box> </Box>
) )

View File

@ -5,7 +5,7 @@ import {AxiosError} from "axios"
import {Button} from "@mui/material" import {Button} from "@mui/material"
import {useMutation} from "@tanstack/react-query" import {useMutation} from "@tanstack/react-query"
import {createAlias, CreateAliasData} from "~/apis" import {CreateAliasData, createAlias} from "~/apis"
import {Alias, AliasType} from "~/server-types" import {Alias, AliasType} from "~/server-types"
export interface CreateRandomAliasButtonProps { export interface CreateRandomAliasButtonProps {
@ -27,7 +27,7 @@ export default function CreateRandomAliasButton({
startIcon={<BsArrowClockwise />} startIcon={<BsArrowClockwise />}
onClick={() => onClick={() =>
mutate({ mutate({
type: AliasType.Random, type: AliasType.RANDOM,
}) })
} }
> >

View File

@ -1,10 +1,11 @@
import {ReactElement} from "react" import {ReactElement} from "react"
import {BiStats} from "react-icons/bi" import {BiStats} from "react-icons/bi"
import {MdMail, MdSettings} from "react-icons/md" import {MdMail, MdSettings} from "react-icons/md"
import {IoMdDocument} from "react-icons/io"
import {Link as RouterLink, useLocation} from "react-router-dom" import {Link as RouterLink, useLocation} from "react-router-dom"
import {Button} from "@mui/material" import {Button} from "@mui/material"
import {mdiTextBoxMultiple} from "@mdi/js/commonjs/mdi"
import Icon from "@mdi/react"
export enum NavigationSection { export enum NavigationSection {
Overview, Overview,
@ -20,7 +21,7 @@ export interface NavigationButtonProps {
const SECTION_ICON_MAP: Record<NavigationSection, ReactElement> = { const SECTION_ICON_MAP: Record<NavigationSection, ReactElement> = {
[NavigationSection.Overview]: <BiStats />, [NavigationSection.Overview]: <BiStats />,
[NavigationSection.Aliases]: <MdMail />, [NavigationSection.Aliases]: <MdMail />,
[NavigationSection.Reports]: <IoMdDocument />, [NavigationSection.Reports]: <Icon path={mdiTextBoxMultiple} size={0.8} />,
[NavigationSection.Settings]: <MdSettings />, [NavigationSection.Settings]: <MdSettings />,
} }

View File

@ -3,6 +3,9 @@ import {TiCancel} from "react-icons/ti"
import React, {ReactElement} from "react" import React, {ReactElement} from "react"
import {Box, Button, Grid, Typography} from "@mui/material" import {Box, Button, Grid, Typography} from "@mui/material"
import {MultiStepFormElement} from "~/components"
import {mdiTextBoxMultiple} from "@mdi/js/commonjs/mdi"
import Icon from "@mdi/react"
export interface GenerateEmailReportsFormProps { export interface GenerateEmailReportsFormProps {
onYes: () => void onYes: () => void
@ -14,7 +17,7 @@ export default function GenerateEmailReportsForm({
onYes, onYes,
}: GenerateEmailReportsFormProps): ReactElement { }: GenerateEmailReportsFormProps): ReactElement {
return ( return (
<Box width="80vw"> <MultiStepFormElement>
<Grid <Grid
container container
direction="column" direction="column"
@ -33,7 +36,7 @@ export default function GenerateEmailReportsForm({
alignItems="center" alignItems="center"
> >
<Grid item> <Grid item>
<Grid container spacing={2} direction="column"> <Grid container spacing={4} direction="column">
<Grid item> <Grid item>
<Typography <Typography
variant="h6" variant="h6"
@ -43,6 +46,14 @@ export default function GenerateEmailReportsForm({
Generate Email Reports? Generate Email Reports?
</Typography> </Typography>
</Grid> </Grid>
<Grid item>
<Box display="flex" justifyContent="center">
<Icon
path={mdiTextBoxMultiple}
size={2}
/>
</Box>
</Grid>
<Grid item> <Grid item>
<Typography <Typography
variant="subtitle1" variant="subtitle1"
@ -81,6 +92,6 @@ export default function GenerateEmailReportsForm({
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
</Box> </MultiStepFormElement>
) )
} }

View File

@ -10,10 +10,8 @@ import {Box, Grid, InputAdornment, Typography} from "@mui/material"
import {PasswordField} from "~/components" import {PasswordField} from "~/components"
import {encryptString} from "~/utils" import {encryptString} from "~/utils"
import {isDev} from "~/constants/development" import {isDev} from "~/constants/development"
import {useUser} from "~/hooks"
export interface PasswordFormProps { import {useMutation} from "@tanstack/react-query"
email: string
}
interface Form { interface Form {
password: string password: string
@ -29,7 +27,9 @@ const schema = yup.object().shape({
.oneOf([yup.ref("password"), null], "Passwords must match"), .oneOf([yup.ref("password"), null], "Passwords must match"),
}) })
export default function PasswordForm({email}: PasswordFormProps): ReactElement { export default function PasswordForm(): ReactElement {
const user = useUser()
const {} = useMutation()
const awaitGenerateKey = useMemo( const awaitGenerateKey = useMemo(
() => () =>
generateKey({ generateKey({
@ -54,7 +54,7 @@ export default function PasswordForm({email}: PasswordFormProps): ReactElement {
const encryptedPrivateKey = encryptString( const encryptedPrivateKey = encryptString(
keyPair.privateKey, keyPair.privateKey,
`${values.password}-${email}`, `${values.password}-${user.email.address}`,
) )
console.log(encryptedPrivateKey) console.log(encryptedPrivateKey)

View File

@ -6,13 +6,14 @@ import React, {ReactElement} from "react"
import {InputAdornment, TextField} from "@mui/material" import {InputAdornment, TextField} from "@mui/material"
import {useMutation} from "@tanstack/react-query"
import DetectEmailAutofillService from "./DetectEmailAutofillService"
import {MultiStepFormElement, SimpleForm} from "~/components" import {MultiStepFormElement, SimpleForm} from "~/components"
import {checkIsDomainDisposable, signup} from "~/apis" 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 DetectEmailAutofillService from "./DetectEmailAutofillService"
export interface EmailFormProps { export interface EmailFormProps {
serverSettings: ServerSettings serverSettings: ServerSettings
onSignUp: (email: string) => void onSignUp: (email: string) => void
@ -31,6 +32,12 @@ export default function EmailForm({
onSignUp, onSignUp,
serverSettings, serverSettings,
}: EmailFormProps): ReactElement { }: EmailFormProps): ReactElement {
const {mutateAsync} = useMutation<SignupResult, AxiosError, string>(
signup,
{
onSuccess: ({normalized_email}) => onSignUp(normalized_email),
},
)
const formik = useFormik<Form>({ const formik = useFormik<Form>({
validationSchema: SCHEMA, validationSchema: SCHEMA,
initialValues: { initialValues: {
@ -57,8 +64,7 @@ export default function EmailForm({
} }
try { try {
await signup(values.email) await mutateAsync(values.email)
onSignUp(values.email)
} catch (error) { } catch (error) {
setErrors(parseFastapiError(error as AxiosError)) setErrors(parseFastapiError(error as AxiosError))
} }

View File

@ -14,7 +14,7 @@ import {
} from "@mui/material" } 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/SignupRoute/YouGotMail/ResendMailButton"
export interface YouGotMailProps { export interface YouGotMailProps {
email: string email: string

View File

@ -24,7 +24,7 @@ export default function AuthenticateRoute(): ReactElement {
<Grid item> <Grid item>
<Button <Button
component={RouterLink} component={RouterLink}
to="/signup" to="/auth/signup"
color="inherit" color="inherit"
size="small" size="small"
startIcon={<MdAdd />} startIcon={<MdAdd />}
@ -35,7 +35,7 @@ export default function AuthenticateRoute(): ReactElement {
<Grid item> <Grid item>
<Button <Button
component={RouterLink} component={RouterLink}
to="/login" to="/auth/login"
color="inherit" color="inherit"
size="small" size="small"
startIcon={<MdLogin />} startIcon={<MdLogin />}

View File

@ -25,7 +25,7 @@ export default function AuthenticatedRoute(): ReactElement {
justifyContent="center" justifyContent="center"
height="100vh" height="100vh"
> >
<Box width="90vw" justifyContent="center" alignItems="center"> <Box maxWidth="90vw" justifyContent="center" alignItems="center">
<Container <Container
maxWidth="md" maxWidth="md"
style={{ style={{

View File

@ -0,0 +1,27 @@
import {ReactElement, useState} from "react"
import {useNavigate} from "react-router-dom"
import {MultiStepForm} from "~/components"
import GenerateEmailReportsForm from "~/route-widgets/CompleteAccountRoute/GenerateEmailReportsForm"
import PasswordForm from "~/route-widgets/CompleteAccountRoute/PasswordForm"
export default function CompleteAccountRoute(): ReactElement {
const navigate = useNavigate()
const [showGenerationReportForm, setShowGenerationReportForm] =
useState(false)
return (
<MultiStepForm
steps={[
<GenerateEmailReportsForm
key="generate_email_reports"
onYes={() => setShowGenerationReportForm(true)}
onNo={() => navigate("/")}
/>,
<PasswordForm key="password_form" />,
]}
index={showGenerationReportForm ? 1 : 0}
/>
)
}

View File

@ -46,8 +46,7 @@ export default function VerifyEmailRoute(): ReactElement {
>(validateEmail, { >(validateEmail, {
onSuccess: async ({user}) => { onSuccess: async ({user}) => {
setEmail("") setEmail("")
await login(user) await login(user, () => navigate("/auth/complete-account"))
navigate("/")
}, },
}) })
const {loading} = useAsync(async () => { const {loading} = useAsync(async () => {

View File

@ -14,8 +14,8 @@ export enum ProxyUserAgentType {
} }
export enum AliasType { export enum AliasType {
Random = "random", RANDOM = "random",
Custom = "custom", CUSTOM = "custom",
} }
export interface User { export interface User {