mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 07:55:25 +02:00
improvements; developed verify email page
This commit is contained in:
parent
f35a07efc1
commit
c8c774de54
26
src/App.tsx
26
src/App.tsx
@ -6,13 +6,35 @@ import {CssBaseline, ThemeProvider} from "@mui/material"
|
||||
|
||||
import {queryClient} from "~/constants/react-query"
|
||||
import {lightTheme} from "~/constants/themes"
|
||||
import LoadCriticalContent from "~/LoadCriticalContent"
|
||||
import {getServerSettings} from "~/apis"
|
||||
import RootRoute from "~/routes/Root"
|
||||
import SignupRoute from "~/routes/SignupRoute"
|
||||
import SingleElementRoute from "~/routes/SingleElementRoute"
|
||||
import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <RootRoute />,
|
||||
errorElement: <div></div>,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
element: <SingleElementRoute />,
|
||||
children: [
|
||||
{
|
||||
loader: getServerSettings,
|
||||
path: "/verify-email",
|
||||
element: <VerifyEmailRoute />,
|
||||
},
|
||||
{
|
||||
loader: getServerSettings,
|
||||
path: "/signup",
|
||||
element: <SignupRoute />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
@ -22,9 +44,7 @@ export default function App(): ReactElement {
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<CssBaseline />
|
||||
<LoadCriticalContent>
|
||||
<RouterProvider router={router} />
|
||||
</LoadCriticalContent>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
|
@ -1,44 +0,0 @@
|
||||
import * as inSeconds from "in-seconds"
|
||||
import {AxiosError} from "axios"
|
||||
import React, {ReactElement} from "react"
|
||||
|
||||
import {useQuery} from "@tanstack/react-query"
|
||||
|
||||
import {getServerSettings} from "~/apis"
|
||||
import LoadingScreen from "~/LoadingScreen"
|
||||
|
||||
import ServerSettingsContext, {
|
||||
ServerSettingsContextType,
|
||||
} from "./ServerSettingsContext"
|
||||
|
||||
export interface LoadCriticalContentProps {
|
||||
children: ReactElement
|
||||
}
|
||||
|
||||
export default function LoadCriticalContent({
|
||||
children,
|
||||
}: LoadCriticalContentProps): ReactElement {
|
||||
const {data} = useQuery<ServerSettingsContextType, AxiosError>(
|
||||
["server-settings"],
|
||||
async () => {
|
||||
const settings = await getServerSettings()
|
||||
|
||||
return {
|
||||
serverSettings: settings,
|
||||
}
|
||||
},
|
||||
{
|
||||
staleTime: inSeconds.days(20),
|
||||
},
|
||||
)
|
||||
|
||||
if (!data) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
return (
|
||||
<ServerSettingsContext.Provider value={data}>
|
||||
{children}
|
||||
</ServerSettingsContext.Provider>
|
||||
)
|
||||
}
|
@ -2,14 +2,10 @@ import React, {ReactElement} from "react"
|
||||
|
||||
import {Typography} from "@mui/material"
|
||||
|
||||
import {SingleElementWrapper} from "~/components"
|
||||
|
||||
export default function LoadingScreen(): ReactElement {
|
||||
return (
|
||||
<SingleElementWrapper>
|
||||
<Typography variant="caption" component="p">
|
||||
Loading...
|
||||
</Typography>
|
||||
</SingleElementWrapper>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
import {createContext} from "react"
|
||||
|
||||
import {ServerSettings} from "~/types"
|
||||
|
||||
export interface ServerSettingsContextType {
|
||||
serverSettings: ServerSettings
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const ServerSettingsContext = createContext<ServerSettingsContextType>()
|
||||
|
||||
export default ServerSettingsContext
|
@ -4,3 +4,5 @@ export * from "./get-server-settings"
|
||||
export {default as getServerSettings} from "./get-server-settings"
|
||||
export * from "./signup"
|
||||
export {default as signup} from "./signup"
|
||||
export * from "./validate-email"
|
||||
export {default as validateEmail} from "./validate-email"
|
||||
|
@ -2,15 +2,15 @@ import axios from "axios"
|
||||
|
||||
import {AuthenticationDetails} from "~/types"
|
||||
|
||||
export interface ValidateTokenData {
|
||||
export interface ValidateEmailData {
|
||||
email: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export default async function validateToken({
|
||||
export default async function validateEmail({
|
||||
email,
|
||||
token,
|
||||
}: ValidateTokenData): Promise<AuthenticationDetails> {
|
||||
}: ValidateEmailData): Promise<AuthenticationDetails> {
|
||||
const {data} = await axios.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/auth/verify-email`,
|
||||
{
|
@ -1,27 +0,0 @@
|
||||
import React, {ReactElement} from "react"
|
||||
|
||||
import {Box} from "@mui/material"
|
||||
|
||||
export interface SingleElementWrapperProps {
|
||||
children: ReactElement
|
||||
}
|
||||
|
||||
const style = {
|
||||
minHeight: "100vh",
|
||||
}
|
||||
|
||||
export default function SingleElementWrapper({
|
||||
children,
|
||||
}: SingleElementWrapperProps): ReactElement {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
sx={style}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -8,5 +8,3 @@ export * from "./PasswordField"
|
||||
export {default as PasswordField} from "./PasswordField"
|
||||
export * from "./SimpleForm"
|
||||
export {default as SimpleForm} from "./SimpleForm"
|
||||
export * from "./SingleElementWrapper"
|
||||
export { default as SingleElementWrapper } from "./SingleElementWrapper"
|
||||
|
@ -1,2 +0,0 @@
|
||||
export * from "./use-server-settings"
|
||||
export {default as useServerSettings} from "./use-server-settings"
|
@ -1,10 +0,0 @@
|
||||
import {useContext} from "react"
|
||||
|
||||
import {ServerSettings} from "~/types"
|
||||
import ServerSettingsContext from "~/ServerSettingsContext"
|
||||
|
||||
export default function useServerSettings(): ServerSettings {
|
||||
const {serverSettings} = useContext(ServerSettingsContext)
|
||||
|
||||
return serverSettings
|
||||
}
|
@ -7,17 +7,20 @@ import {InputAdornment, TextField} from "@mui/material"
|
||||
import {MultiStepFormElement, SimpleForm} from "~/components"
|
||||
import {signup} from "~/apis"
|
||||
import {handleErrors} from "~/utils"
|
||||
import {useServerSettings} from "~/hooks"
|
||||
import {ServerSettings} from "~/types"
|
||||
|
||||
import DetectEmailAutofillService from "./DetectEmailAutofillService"
|
||||
import useSchema, {Form} from "./use-schema"
|
||||
|
||||
interface EmailFormProps {
|
||||
serverSettings: ServerSettings
|
||||
onSignUp: (email: string) => void
|
||||
}
|
||||
|
||||
export default function EmailForm({onSignUp}: EmailFormProps): ReactElement {
|
||||
const serverSettings = useServerSettings()
|
||||
export default function EmailForm({
|
||||
onSignUp,
|
||||
serverSettings,
|
||||
}: EmailFormProps): ReactElement {
|
||||
const schema = useSchema(serverSettings)
|
||||
const formik = useFormik<Form>({
|
||||
validationSchema: schema,
|
||||
|
@ -1,30 +1,6 @@
|
||||
import {useLocalStorage} from "react-use"
|
||||
import {Outlet} from "react-router-dom"
|
||||
import React, {ReactElement} from "react"
|
||||
|
||||
import {MultiStepForm, SingleElementWrapper} from "~/components"
|
||||
import EmailForm from "~/route-widgets/root/EmailForm"
|
||||
import YouGotMail from "~/route-widgets/root/YouGotMail"
|
||||
|
||||
export default function RootRoute(): ReactElement {
|
||||
const [email, setEmail] = useLocalStorage<string>(
|
||||
"signup-form-state-email",
|
||||
"",
|
||||
)
|
||||
|
||||
const index = email ? 1 : 0
|
||||
|
||||
return (
|
||||
<SingleElementWrapper>
|
||||
<MultiStepForm
|
||||
steps={[
|
||||
<EmailForm onSignUp={setEmail} key="email" />,
|
||||
<YouGotMail
|
||||
domain={(email || "").split("@")[1]}
|
||||
key="you_got_mail"
|
||||
/>,
|
||||
]}
|
||||
index={index}
|
||||
/>
|
||||
</SingleElementWrapper>
|
||||
)
|
||||
return <Outlet />
|
||||
}
|
||||
|
35
src/routes/SignupRoute.tsx
Normal file
35
src/routes/SignupRoute.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import {ReactElement} from "react"
|
||||
import {useLocalStorage} from "react-use"
|
||||
import {useLoaderData} from "react-router-dom"
|
||||
|
||||
import {MultiStepForm} from "~/components"
|
||||
import {ServerSettings} from "~/types"
|
||||
import EmailForm from "~/route-widgets/root/EmailForm"
|
||||
import YouGotMail from "~/route-widgets/root/YouGotMail"
|
||||
|
||||
export default function SignupRoute(): ReactElement {
|
||||
const serverSettings = useLoaderData() as ServerSettings
|
||||
const [email, setEmail] = useLocalStorage<string>(
|
||||
"signup-form-state-email",
|
||||
"",
|
||||
)
|
||||
|
||||
const index = email ? 1 : 0
|
||||
|
||||
return (
|
||||
<MultiStepForm
|
||||
steps={[
|
||||
<EmailForm
|
||||
serverSettings={serverSettings}
|
||||
onSignUp={setEmail}
|
||||
key="email"
|
||||
/>,
|
||||
<YouGotMail
|
||||
domain={(email || "").split("@")[1]}
|
||||
key="you_got_mail"
|
||||
/>,
|
||||
]}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
}
|
49
src/routes/SingleElementRoute.tsx
Normal file
49
src/routes/SingleElementRoute.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import {ReactElement} from "react"
|
||||
import {Link as RouterLink, Outlet} from "react-router-dom"
|
||||
import {MdAdd, MdLogin} from "react-icons/md"
|
||||
|
||||
import {Box, Button, Grid} from "@mui/material"
|
||||
|
||||
export default function SingleElementRoute(): ReactElement {
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
height="100vh"
|
||||
>
|
||||
<div />
|
||||
<Outlet />
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
justifyContent="center"
|
||||
marginBottom={2}
|
||||
>
|
||||
<Grid item>
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="/signup"
|
||||
color="inherit"
|
||||
size="small"
|
||||
startIcon={<MdAdd />}
|
||||
>
|
||||
Sign Up
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
component={RouterLink}
|
||||
to="/login"
|
||||
color="inherit"
|
||||
size="small"
|
||||
startIcon={<MdLogin />}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
}
|
93
src/routes/VerifyEmailRoute.tsx
Normal file
93
src/routes/VerifyEmailRoute.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import * as yup from "yup"
|
||||
import {useLoaderData, useParams} from "react-router-dom"
|
||||
import {useAsync} from "react-use"
|
||||
import {MdCancel} from "react-icons/md"
|
||||
import React, {ReactElement} from "react"
|
||||
|
||||
import {Grid, Paper, Typography, useTheme} from "@mui/material"
|
||||
|
||||
import {ServerSettings} from "~/types"
|
||||
import {validateEmail} from "~/apis"
|
||||
|
||||
const emailSchema = yup.string().email()
|
||||
|
||||
export default function VerifyEmailRoute(): ReactElement {
|
||||
const theme = useTheme()
|
||||
const {email, token} = useParams<{
|
||||
email: string
|
||||
token: string
|
||||
}>()
|
||||
const serverSettings = useLoaderData() as ServerSettings
|
||||
|
||||
const tokenSchema = yup
|
||||
.string()
|
||||
.length(serverSettings.email_verification_length)
|
||||
.test("token", "Invalid token", token => {
|
||||
if (!token) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check token only contains chars from `serverSettings.email_verification_chars`
|
||||
const chars = serverSettings.email_verification_chars.split("")
|
||||
return token.split("").every(chars.includes)
|
||||
})
|
||||
const {error, loading} = useAsync(async () => {
|
||||
await emailSchema.validate(email)
|
||||
await tokenSchema.validate(token)
|
||||
|
||||
await validateEmail({
|
||||
token: token as string,
|
||||
email: email as string,
|
||||
})
|
||||
|
||||
return true
|
||||
}, [email, token])
|
||||
|
||||
return (
|
||||
<Paper>
|
||||
<Grid
|
||||
container
|
||||
spacing={4}
|
||||
padding={4}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
flexDirection="column"
|
||||
>
|
||||
<Grid item>
|
||||
<Typography variant="h5" component="h1" align="center">
|
||||
Verify your email
|
||||
</Typography>
|
||||
</Grid>
|
||||
{loading ? (
|
||||
<Grid item>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="p"
|
||||
align="center"
|
||||
>
|
||||
Verifying your email...
|
||||
</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
<>
|
||||
<Grid item>
|
||||
<MdCancel
|
||||
size={100}
|
||||
color={theme.palette.error.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="p"
|
||||
align="center"
|
||||
>
|
||||
Sorry, but this token is invalid.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user