mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 07:55:25 +02:00
added login page; improved resend mails
This commit is contained in:
parent
bb4c58508d
commit
2083c0bdb6
10
src/App.tsx
10
src/App.tsx
@ -12,6 +12,7 @@ import AuthContextProvider from "~/AuthContext/AuthContextProvider"
|
||||
import AuthenticateRoute from "~/routes/AuthenticateRoute"
|
||||
import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
|
||||
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
|
||||
import LoginRoute from "~/routes/LoginRoute"
|
||||
import RootRoute from "~/routes/Root"
|
||||
import SignupRoute from "~/routes/SignupRoute"
|
||||
import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
|
||||
@ -28,14 +29,19 @@ const router = createBrowserRouter([
|
||||
children: [
|
||||
{
|
||||
loader: getServerSettings,
|
||||
path: "/auth/verify-email",
|
||||
element: <VerifyEmailRoute />,
|
||||
path: "/auth/login",
|
||||
element: <LoginRoute />,
|
||||
},
|
||||
{
|
||||
loader: getServerSettings,
|
||||
path: "/auth/signup",
|
||||
element: <SignupRoute />,
|
||||
},
|
||||
{
|
||||
loader: getServerSettings,
|
||||
path: "/auth/verify-email",
|
||||
element: <VerifyEmailRoute />,
|
||||
},
|
||||
{
|
||||
path: "/auth/complete-account",
|
||||
element: <CompleteAccountRoute />,
|
||||
|
@ -18,3 +18,9 @@ export * from "./create-alias"
|
||||
export {default as createAlias} from "./create-alias"
|
||||
export * from "./update-account"
|
||||
export {default as updateAccount} from "./update-account"
|
||||
export * from "./login-with-email"
|
||||
export {default as loginWithEmail} from "./login-with-email"
|
||||
export * from "./verify-login-with-email"
|
||||
export {default as verifyLoginWithEmail} from "./verify-login-with-email"
|
||||
export * from "./resend-email-login-code"
|
||||
export {default as resendEmailLoginCode} from "./resend-email-login-code"
|
||||
|
19
src/apis/login-with-email.ts
Normal file
19
src/apis/login-with-email.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {client} from "~/constants/axios-client"
|
||||
|
||||
export interface LoginWithEmailResult {
|
||||
detail: string
|
||||
sameRequestToken: string
|
||||
}
|
||||
|
||||
export default async function loginWithEmail(
|
||||
email: string,
|
||||
): Promise<LoginWithEmailResult> {
|
||||
const {data} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/auth/login/email-token`,
|
||||
{
|
||||
email,
|
||||
},
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
@ -1 +0,0 @@
|
||||
export default function login() {}
|
@ -1,7 +1,7 @@
|
||||
import {MinimumServerResponse} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
|
||||
export default async function logout(): Promise<MinimumServerResponse> {
|
||||
export default async function logout(): Promise<SimpleDetailResponse> {
|
||||
const {data} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/auth/logout`,
|
||||
)
|
||||
|
24
src/apis/resend-email-login-code.ts
Normal file
24
src/apis/resend-email-login-code.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
|
||||
export interface ResendEmailLoginCodeData {
|
||||
email: string
|
||||
sameRequestToken: string
|
||||
}
|
||||
|
||||
export default async function resendEmailLoginCode({
|
||||
email,
|
||||
sameRequestToken,
|
||||
}: ResendEmailLoginCodeData): Promise<SimpleDetailResponse> {
|
||||
const {data} = await client.post(
|
||||
`${
|
||||
import.meta.env.VITE_SERVER_BASE_URL
|
||||
}/auth/login/email-token/resend-email`,
|
||||
{
|
||||
email,
|
||||
sameRequestToken,
|
||||
},
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import {MinimumServerResponse} from "~/server-types"
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
|
||||
export default async function resendEmailVerificationCode(
|
||||
email: string,
|
||||
): Promise<MinimumServerResponse> {
|
||||
): Promise<SimpleDetailResponse> {
|
||||
const {data} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/auth/resend-email`,
|
||||
{
|
||||
|
32
src/apis/verify-login-with-email.ts
Normal file
32
src/apis/verify-login-with-email.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {AuthenticationDetails} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
import parseUser from "~/apis/helpers/parse-user"
|
||||
|
||||
export interface VerifyLoginWithEmailData {
|
||||
email: string
|
||||
token: string
|
||||
sameRequestToken: string
|
||||
}
|
||||
|
||||
export default async function verifyLoginWithEmail({
|
||||
email,
|
||||
token,
|
||||
sameRequestToken,
|
||||
}: VerifyLoginWithEmailData): Promise<AuthenticationDetails> {
|
||||
const {data} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/auth/login/email-token/verify`,
|
||||
{
|
||||
email,
|
||||
token,
|
||||
sameRequestToken,
|
||||
},
|
||||
{
|
||||
withCredentials: true,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
...data,
|
||||
user: parseUser(data.user),
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import {UseMutationResult} from "@tanstack/react-query"
|
||||
import {Alert, AlertProps, Snackbar} from "@mui/material"
|
||||
|
||||
import {FastAPIError} from "~/utils"
|
||||
import {MinimumServerResponse} from "~/server-types"
|
||||
import {SimpleDetailResponse} from "~/server-types"
|
||||
import getErrorMessage from "~/utils/get-error-message"
|
||||
|
||||
export interface MutationStatusSnackbarProps<
|
||||
@ -21,7 +21,7 @@ export interface MutationStatusSnackbarProps<
|
||||
}
|
||||
|
||||
export default function MutationStatusSnackbar<
|
||||
TData extends MinimumServerResponse = MinimumServerResponse,
|
||||
TData extends SimpleDetailResponse = SimpleDetailResponse,
|
||||
TError extends AxiosError = AxiosError<FastAPIError>,
|
||||
TVariables = unknown,
|
||||
TContext = unknown,
|
||||
|
37
src/components/TimedButton.tsx
Normal file
37
src/components/TimedButton.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import {ReactElement} from "react"
|
||||
import differenceInSeconds from "date-fns/differenceInSeconds"
|
||||
|
||||
import {LoadingButton, LoadingButtonProps} from "@mui/lab"
|
||||
|
||||
import {useIntervalUpdate} from "~/hooks"
|
||||
import {isDev} from "~/constants/development"
|
||||
|
||||
export interface TimedButtonProps extends LoadingButtonProps {
|
||||
interval: number
|
||||
}
|
||||
|
||||
export default function TimedButton({
|
||||
interval,
|
||||
children,
|
||||
onClick,
|
||||
disabled: parentDisabled = false,
|
||||
...props
|
||||
}: TimedButtonProps): ReactElement {
|
||||
const [startDate, resetInterval] = useIntervalUpdate(1000)
|
||||
const secondsPassed = differenceInSeconds(new Date(), startDate)
|
||||
const secondsLeft = (isDev ? 3 : interval) - secondsPassed
|
||||
|
||||
return (
|
||||
<LoadingButton
|
||||
{...props}
|
||||
disabled={parentDisabled || secondsLeft > 0}
|
||||
onClick={event => {
|
||||
resetInterval()
|
||||
onClick?.(event)
|
||||
}}
|
||||
>
|
||||
<span>{children}</span>
|
||||
{secondsLeft > 0 && <span> ({secondsLeft})</span>}
|
||||
</LoadingButton>
|
||||
)
|
||||
}
|
@ -10,3 +10,5 @@ export * from "./SimpleForm"
|
||||
export {default as SimpleForm} from "./SimpleForm"
|
||||
export * from "./MutationStatusSnackbar"
|
||||
export {default as MutationStatusSnackbar} from "./MutationStatusSnackbar"
|
||||
export * from "./TimedButton"
|
||||
export {default as TimedButton} from "./TimedButton"
|
||||
|
147
src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx
Normal file
147
src/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import {AxiosError} from "axios"
|
||||
import {ReactElement} from "react"
|
||||
import {useFormik} from "formik"
|
||||
import {FaHashtag} from "react-icons/fa"
|
||||
import {MdChevronRight, MdMail} from "react-icons/md"
|
||||
|
||||
import {useMutation} from "@tanstack/react-query"
|
||||
import {Box, Grid, InputAdornment, TextField, Typography} from "@mui/material"
|
||||
import {LoadingButton} from "@mui/lab"
|
||||
|
||||
import {AuthenticationDetails, ServerUser} from "~/server-types"
|
||||
import {VerifyLoginWithEmailData, verifyLoginWithEmail} from "~/apis"
|
||||
import {MultiStepFormElement} from "~/components"
|
||||
import {parseFastapiError} from "~/utils"
|
||||
|
||||
import ResendMailButton from "./ResendMailButton"
|
||||
import useSchema from "./use-schema"
|
||||
|
||||
export interface ConfirmCodeFormProps {
|
||||
onConfirm: (user: ServerUser) => void
|
||||
email: string
|
||||
sameRequestToken: string
|
||||
}
|
||||
|
||||
interface Form {
|
||||
code: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
export default function ConfirmCodeForm({
|
||||
onConfirm,
|
||||
email,
|
||||
sameRequestToken,
|
||||
}: ConfirmCodeFormProps): ReactElement {
|
||||
const schema = useSchema()
|
||||
const {mutateAsync} = useMutation<
|
||||
AuthenticationDetails,
|
||||
AxiosError,
|
||||
VerifyLoginWithEmailData
|
||||
>(verifyLoginWithEmail, {
|
||||
onSuccess: ({user}) => onConfirm(user),
|
||||
})
|
||||
const formik = useFormik<Form>({
|
||||
validationSchema: schema,
|
||||
initialValues: {
|
||||
code: "",
|
||||
detail: "",
|
||||
},
|
||||
onSubmit: async (values, {setErrors}) => {
|
||||
try {
|
||||
await mutateAsync({
|
||||
email,
|
||||
sameRequestToken,
|
||||
token: values.code,
|
||||
})
|
||||
} catch (error) {
|
||||
setErrors(parseFastapiError(error as AxiosError))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<MultiStepFormElement>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Grid
|
||||
container
|
||||
spacing={4}
|
||||
padding={4}
|
||||
justifyContent="center"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Grid item>
|
||||
<Typography variant="h6" component="h1" align="center">
|
||||
You got mail!
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box display="flex" justifyContent="center">
|
||||
<MdMail size={64} />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="p"
|
||||
align="center"
|
||||
>
|
||||
We sent you a code to your email. Enter it below to
|
||||
login.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
key="code"
|
||||
fullWidth
|
||||
name="code"
|
||||
id="code"
|
||||
label="code"
|
||||
value={formik.values.code}
|
||||
onChange={formik.handleChange}
|
||||
disabled={formik.isSubmitting}
|
||||
error={
|
||||
formik.touched.code &&
|
||||
Boolean(formik.errors.code)
|
||||
}
|
||||
helperText={
|
||||
formik.touched.code && formik.errors.code
|
||||
}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<FaHashtag />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid
|
||||
width="100%"
|
||||
container
|
||||
display="flex"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Grid item>
|
||||
<ResendMailButton
|
||||
email={email}
|
||||
sameRequestToken={sameRequestToken}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<LoadingButton
|
||||
loading={formik.isSubmitting}
|
||||
variant="contained"
|
||||
type="submit"
|
||||
startIcon={<MdChevronRight />}
|
||||
>
|
||||
Login
|
||||
</LoadingButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</MultiStepFormElement>
|
||||
)
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import {AxiosError} from "axios"
|
||||
import {useLoaderData} from "react-router-dom"
|
||||
import React, {ReactElement} from "react"
|
||||
|
||||
import {useMutation} from "@tanstack/react-query"
|
||||
|
||||
import {resendEmailLoginCode} from "~/apis"
|
||||
import {MutationStatusSnackbar, TimedButton} from "~/components"
|
||||
import {ServerSettings, SimpleDetailResponse} from "~/server-types"
|
||||
import {MdMail} from "react-icons/md"
|
||||
|
||||
export interface ResendMailButtonProps {
|
||||
email: string
|
||||
sameRequestToken: string
|
||||
}
|
||||
|
||||
export default function ResendMailButton({
|
||||
email,
|
||||
sameRequestToken,
|
||||
}: ResendMailButtonProps): ReactElement {
|
||||
const settings = useLoaderData() as ServerSettings
|
||||
|
||||
const mutation = useMutation<SimpleDetailResponse, AxiosError, void>(() =>
|
||||
resendEmailLoginCode({
|
||||
email,
|
||||
sameRequestToken,
|
||||
}),
|
||||
)
|
||||
const {mutate} = mutation
|
||||
|
||||
return (
|
||||
<>
|
||||
<TimedButton
|
||||
interval={settings.emailResendWaitTime}
|
||||
startIcon={<MdMail />}
|
||||
onClick={() => mutate()}
|
||||
>
|
||||
Resend Mail
|
||||
</TimedButton>
|
||||
<MutationStatusSnackbar mutation={mutation} />
|
||||
</>
|
||||
)
|
||||
}
|
25
src/route-widgets/LoginRoute/ConfirmCodeForm/use-schema.ts
Normal file
25
src/route-widgets/LoginRoute/ConfirmCodeForm/use-schema.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import * as yup from "yup"
|
||||
import {useLoaderData} from "react-router-dom"
|
||||
|
||||
import {ServerSettings} from "~/server-types"
|
||||
|
||||
export default function useSchema(): yup.ObjectSchema<any> {
|
||||
const settings = useLoaderData() as ServerSettings
|
||||
|
||||
return yup.object().shape({
|
||||
code: yup
|
||||
.string()
|
||||
.required()
|
||||
.min(settings.emailLoginTokenLength)
|
||||
.max(settings.emailLoginTokenLength)
|
||||
.test("chars", "This code is not valid.", code => {
|
||||
if (!code) {
|
||||
return false
|
||||
}
|
||||
|
||||
const chars = settings.emailLoginTokenChars.split("")
|
||||
|
||||
return code.split("").every(char => chars.includes(char))
|
||||
}),
|
||||
})
|
||||
}
|
91
src/route-widgets/LoginRoute/EmailForm.tsx
Normal file
91
src/route-widgets/LoginRoute/EmailForm.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import * as yup from "yup"
|
||||
import {ReactElement} from "react"
|
||||
import {AxiosError} from "axios"
|
||||
import {useFormik} from "formik"
|
||||
import {MdEmail} from "react-icons/md"
|
||||
|
||||
import {useMutation} from "@tanstack/react-query"
|
||||
import {InputAdornment, TextField} from "@mui/material"
|
||||
|
||||
import {LoginWithEmailResult, loginWithEmail} from "~/apis"
|
||||
import {parseFastapiError} from "~/utils"
|
||||
import {MultiStepFormElement, SimpleForm} from "~/components"
|
||||
|
||||
export interface EmailFormProps {
|
||||
onLogin: (email: string, sameRequestToken: string) => void
|
||||
}
|
||||
|
||||
interface Form {
|
||||
email: string
|
||||
detail: string
|
||||
}
|
||||
|
||||
const SCHEMA = yup.object().shape({
|
||||
email: yup.string().email().required(),
|
||||
})
|
||||
|
||||
export default function EmailForm({onLogin}: EmailFormProps): ReactElement {
|
||||
const {mutateAsync} = useMutation<LoginWithEmailResult, AxiosError, string>(
|
||||
loginWithEmail,
|
||||
{
|
||||
onSuccess: ({sameRequestToken}) =>
|
||||
onLogin(formik.values.email, sameRequestToken),
|
||||
},
|
||||
)
|
||||
const formik = useFormik<Form>({
|
||||
validationSchema: SCHEMA,
|
||||
initialValues: {
|
||||
email: "",
|
||||
detail: "",
|
||||
},
|
||||
onSubmit: async (values, {setErrors}) => {
|
||||
try {
|
||||
await mutateAsync(values.email)
|
||||
} catch (error) {
|
||||
setErrors(parseFastapiError(error as AxiosError))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<MultiStepFormElement>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<SimpleForm
|
||||
title="Sign in"
|
||||
description="We'll send you a verification code to your email."
|
||||
continueActionLabel="Send code"
|
||||
nonFieldError={formik.errors.detail}
|
||||
isSubmitting={formik.isSubmitting}
|
||||
>
|
||||
{[
|
||||
<TextField
|
||||
key="email"
|
||||
fullWidth
|
||||
name="email"
|
||||
id="email"
|
||||
label="Email"
|
||||
inputMode="email"
|
||||
value={formik.values.email}
|
||||
onChange={formik.handleChange}
|
||||
disabled={formik.isSubmitting}
|
||||
error={
|
||||
formik.touched.email &&
|
||||
Boolean(formik.errors.email)
|
||||
}
|
||||
helperText={
|
||||
formik.touched.email && formik.errors.email
|
||||
}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<MdEmail />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>,
|
||||
]}
|
||||
</SimpleForm>
|
||||
</form>
|
||||
</MultiStepFormElement>
|
||||
)
|
||||
}
|
@ -1,52 +1,37 @@
|
||||
import {MdEmail} from "react-icons/md"
|
||||
import {AxiosError} from "axios"
|
||||
import {useLoaderData} from "react-router-dom"
|
||||
import {MdMail} from "react-icons/md"
|
||||
import React, {ReactElement} from "react"
|
||||
import differenceInSeconds from "date-fns/differenceInSeconds"
|
||||
|
||||
import {useMutation} from "@tanstack/react-query"
|
||||
import {LoadingButton} from "@mui/lab"
|
||||
|
||||
import {useIntervalUpdate} from "~/hooks"
|
||||
import {resendEmailVerificationCode} from "~/apis"
|
||||
import {isDev} from "~/constants/development"
|
||||
import {MutationStatusSnackbar} from "~/components"
|
||||
import {MinimumServerResponse} from "~/server-types"
|
||||
import {MutationStatusSnackbar, TimedButton} from "~/components"
|
||||
import {ServerSettings, SimpleDetailResponse} from "~/server-types"
|
||||
|
||||
export interface ResendMailButtonProps {
|
||||
email: string
|
||||
}
|
||||
|
||||
const RESEND_INTERVAL = isDev ? 3 : 60
|
||||
|
||||
export default function ResendMailButton({
|
||||
email,
|
||||
}: ResendMailButtonProps): ReactElement {
|
||||
const [startDate, resetInterval] = useIntervalUpdate(1000)
|
||||
const secondsPassed = differenceInSeconds(new Date(), startDate)
|
||||
const secondsLeft = RESEND_INTERVAL - secondsPassed
|
||||
const settings = useLoaderData() as ServerSettings
|
||||
|
||||
const mutation = useMutation<MinimumServerResponse, AxiosError, string>(
|
||||
resendEmailVerificationCode,
|
||||
{
|
||||
onSettled: resetInterval,
|
||||
},
|
||||
const mutation = useMutation<SimpleDetailResponse, AxiosError, void>(() =>
|
||||
resendEmailVerificationCode(email),
|
||||
)
|
||||
const {mutate: resendEmail, isLoading} = mutation
|
||||
const {mutate} = mutation
|
||||
|
||||
return (
|
||||
<>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
startIcon={<MdEmail />}
|
||||
onClick={() => resendEmail(email)}
|
||||
loading={isLoading}
|
||||
disabled={secondsLeft > 0 || isLoading}
|
||||
<TimedButton
|
||||
interval={settings.emailResendWaitTime}
|
||||
startIcon={<MdMail />}
|
||||
onClick={() => mutate()}
|
||||
>
|
||||
<span>
|
||||
<span>Resend Email</span>
|
||||
{secondsLeft > 0 && <span> ({secondsLeft})</span>}
|
||||
</span>
|
||||
</LoadingButton>
|
||||
Resend Mail
|
||||
</TimedButton>
|
||||
<MutationStatusSnackbar mutation={mutation} />
|
||||
</>
|
||||
)
|
||||
|
36
src/routes/LoginRoute.tsx
Normal file
36
src/routes/LoginRoute.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import {ReactElement, useContext, useState} from "react"
|
||||
import {useNavigate} from "react-router-dom"
|
||||
|
||||
import {MultiStepForm} from "~/components"
|
||||
import AuthContext from "~/AuthContext/AuthContext"
|
||||
import ConfirmCodeForm from "~/route-widgets/LoginRoute/ConfirmCodeForm/ConfirmCodeForm"
|
||||
import EmailForm from "~/route-widgets/LoginRoute/EmailForm"
|
||||
|
||||
export default function LoginRoute(): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const {login} = useContext(AuthContext)
|
||||
|
||||
const [email, setEmail] = useState<string>("")
|
||||
const [sameRequestToken, setSameRequestToken] = useState<string>("")
|
||||
|
||||
return (
|
||||
<MultiStepForm
|
||||
steps={[
|
||||
<EmailForm
|
||||
key="email_form"
|
||||
onLogin={(email, sameRequestToken) => {
|
||||
setEmail(email)
|
||||
setSameRequestToken(sameRequestToken)
|
||||
}}
|
||||
/>,
|
||||
<ConfirmCodeForm
|
||||
key="confirm_code_form"
|
||||
onConfirm={user => login(user, () => navigate("/"))}
|
||||
email={email}
|
||||
sameRequestToken={sameRequestToken}
|
||||
/>,
|
||||
]}
|
||||
index={email === "" ? 0 : 1}
|
||||
/>
|
||||
)
|
||||
}
|
@ -52,11 +52,14 @@ export interface ServerUser {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AuthenticationDetails {
|
||||
user: ServerUser
|
||||
export interface SimpleDetailResponse {
|
||||
detail: string
|
||||
}
|
||||
|
||||
export interface AuthenticationDetails extends SimpleDetailResponse {
|
||||
user: ServerUser
|
||||
}
|
||||
|
||||
export interface ServerSettings {
|
||||
mailDomain: string
|
||||
randomEmailIdMinLength: number
|
||||
@ -68,10 +71,9 @@ export interface ServerSettings {
|
||||
otherRelayDomains: Array<string>
|
||||
emailVerificationChars: string
|
||||
emailVerificationLength: number
|
||||
}
|
||||
|
||||
export interface MinimumServerResponse {
|
||||
detail?: string
|
||||
emailLoginTokenChars: string
|
||||
emailLoginTokenLength: number
|
||||
emailResendWaitTime: number
|
||||
}
|
||||
|
||||
export interface Alias {
|
||||
|
Loading…
x
Reference in New Issue
Block a user