Skip to content

Commit

Permalink
Email verification (#9)
Browse files Browse the repository at this point in the history
* add git lens

* add page

* feedback

* delete images

* merge from dev
  • Loading branch information
SKairinos committed Jul 12, 2024
1 parent 159fbe6 commit 3f71cc2
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 37 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
VITE_API_BASE_URL=http://localhost:8000/api/
VITE_SERVICE_NAME=portal

VITE_LINK_OPEN_VERIFY_EMAIL_IN_GMAIL="https://mail.google.com/mail/#search/from%3Ano-reply%40info.codeforlife.education+subject%3AEmail+Verification"
VITE_LINK_OPEN_VERIFY_EMAIL_IN_OUTLOOK=https://outlook.live.com/mail/
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"//": "🚫 Don't add `dependencies` below that are inherited from the CFL package.",
"dependencies": {
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.0.3"
"codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.1.0"
},
"//": "✅ Do add `devDependencies` below that are `peerDependencies` in the CFL package.",
"devDependencies": {
Expand Down
84 changes: 84 additions & 0 deletions src/pages/emailVerification/EmailVerification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
MailOutline as MailOutlineIcon,
Send as SendIcon,
SentimentVeryDissatisfied as SentimentVeryDissatisfiedIcon,
} from "@mui/icons-material"
import { type SvgIconProps } from "@mui/material"
import { type FC, useEffect } from "react"
import * as yup from "yup"

import * as page from "codeforlife/components/page"
import { useNavigate, useParams, useSearchParams } from "codeforlife/hooks"

import { paths } from "../../router"
import Status from "./Status"

export interface EmailVerificationProps {}

const EmailVerification: FC<EmailVerificationProps> = () => {
const navigate = useNavigate()
const params = useParams({
userType: yup
.string()
.oneOf(["teacher", "independent"] as const)
.required(),
})
const searchParams = useSearchParams({
success: yup.boolean().required().default(true),
})

useEffect(() => {
if (!params) navigate(paths.error.pageNotFound._)
}, [params, navigate])

if (!params) return <></>

const svgIconProps: SvgIconProps = {
color: "white",
style: { fontSize: "100px" },
}

return (
<page.Page>
<page.Section maxWidth="md" className="flex-center">
{searchParams?.success ? (
<Status
userType={params.userType}
header="We need to verify your email address"
body={[
"An email has been sent to you. Make sure to check your spam.",
"Please follow the link within the email to verify your details. This will expire in 1 hour.",
]}
icon={<SendIcon {...svgIconProps} />}
buttonProps={[
{
to: import.meta.env.VITE_LINK_OPEN_VERIFY_EMAIL_IN_GMAIL,
target: "_blank",
children: "Open in Gmail",
endIcon: <MailOutlineIcon />,
},
{
to: import.meta.env.VITE_LINK_OPEN_VERIFY_EMAIL_IN_OUTLOOK,
target: "_blank",
children: "Open in Outlook",
endIcon: <MailOutlineIcon />,
},
]}
/>
) : (
<Status
userType={params.userType}
header="Your email address verification failed"
body={[
"You used an invalid link, either you mistyped the URL or that link is expired.",
"When you next attempt to log in, you will be sent a new verification email.",
]}
icon={<SentimentVeryDissatisfiedIcon {...svgIconProps} />}
/>
)}
</page.Section>
</page.Page>
)
}

export default EmailVerification
54 changes: 54 additions & 0 deletions src/pages/emailVerification/Status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Stack, Typography } from "@mui/material"
import { type FC, type ReactElement } from "react"

import {
Link,
LinkButton,
type LinkButtonProps,
} from "codeforlife/components/router"
import { ThemedBox, type ThemedBoxProps } from "codeforlife/theme"

import { themeOptions } from "../../app/theme"
import { paths } from "../../router"

export interface StatusProps {
userType: ThemedBoxProps["userType"]
header: string
body: string[]
icon: ReactElement
buttonProps?: LinkButtonProps[]
}

const Status: FC<StatusProps> = ({
userType,
header,
body,
icon,
buttonProps,
}) => (
<ThemedBox withShapes options={themeOptions} userType={userType}>
<Stack alignItems="center" marginBottom={2.5}>
<Typography variant="h4" paddingY={1} textAlign="center">
{header}
</Typography>
{icon}
<Stack>
{body.map((text, index) => (
<Typography key={index}>{text}</Typography>
))}
</Stack>
{buttonProps && (
<Stack direction="row" spacing={5}>
{buttonProps.map((props, index) => (
<LinkButton key={index} {...props} />
))}
</Stack>
)}
</Stack>
<Link to={paths._} className="back-to">
homepage
</Link>
</ThemedBox>
)

export default Status
13 changes: 4 additions & 9 deletions src/pages/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import * as yup from "yup"
import * as page from "codeforlife/components/page"
import {
useNavigate,
useSearchParamEntries,
useSearchParams,
useSessionMetadata,
} from "codeforlife/hooks"
import { tryValidateSync } from "codeforlife/utils/schema"

import { paths } from "../../router"
import IndyForm from "./IndyForm"
Expand All @@ -27,13 +26,9 @@ export interface LoginProps {
const Login: FC<LoginProps> = ({ form }) => {
const sessionMetadata = useSessionMetadata()
const navigate = useNavigate()

const searchParams = tryValidateSync(
useSearchParamEntries(),
yup.object({
verifyEmail: yup.boolean().default(false),
}),
)
const searchParams = useSearchParams({
verifyEmail: yup.boolean().default(false),
})

useEffect(() => {
if (sessionMetadata) {
Expand Down
15 changes: 5 additions & 10 deletions src/pages/login/studentForms/Class.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { generatePath } from "react-router-dom"
import * as yup from "yup"

import * as form from "codeforlife/components/form"
import { useNavigate, useSearchParamEntries } from "codeforlife/hooks"
import { tryValidateSync } from "codeforlife/utils/schema"
import { useNavigate, useSearchParams } from "codeforlife/hooks"

import { useAutoLoginAsStudentMutation } from "../../../api/sso"
import { classIdSchema } from "../../../app/schemas"
Expand All @@ -18,14 +17,10 @@ export interface ClassProps {}
const Class: FC<ClassProps> = () => {
const [autoLoginAsStudent] = useAutoLoginAsStudentMutation()
const navigate = useNavigate()

const searchParams = tryValidateSync(
useSearchParamEntries(),
yup.object({
id: yup.number().required(),
agp: yup.string().required(),
}),
)
const searchParams = useSearchParams({
id: yup.number().required(),
agp: yup.string().required(),
})

useEffect(() => {
if (searchParams) {
Expand Down
7 changes: 4 additions & 3 deletions src/router/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ const paths = _("", {
}),
register: _("/register", {
emailVerification: _("/email-verification", {
teacher: _("/teacher"),
student: _("/student"),
indy: _("/independent"),
userType: _("/:userType", {
teacher: _({ userType: "teacher" }),
indy: _({ userType: "independent" }),
}),
}),
}),
aboutUs: _("/about-us"),
Expand Down
11 changes: 5 additions & 6 deletions src/router/routes/authentication.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Route } from "react-router-dom"

// eslint-disable-next-line max-len
// import EmailVerification from '../../../pages/emailVerification/EmailVerification'
import EmailVerification from "../../pages/emailVerification/EmailVerification"
import Login from "../../pages/login/Login"
// import Register from '../../../pages/register/Register'
// import ResetPassword from '../../../pages/resetPassword/ResetPassword'
Expand All @@ -15,10 +14,6 @@ import paths from "../paths"
// path={paths.register._}
// element={<Register />}
// />
// <Route
// path={`${paths.register.emailVerification._}/:userType`}
// element={<EmailVerification />}
// />

const authentication = (
<>
Expand All @@ -43,6 +38,10 @@ const authentication = (
element={<Login form="student-first-name" />}
/>
<Route path={paths.login.independent._} element={<Login form="indy" />} />
<Route
path={paths.register.emailVerification.userType._}
element={<EmailVerification />}
/>
</>
)

Expand Down
10 changes: 9 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ export default defineConfig({
},
optimizeDeps: {
// TODO: investigate which of these are needed
include: ["@mui/icons-material", "yup", "formik"],
include: [
"@mui/x-date-pickers",
"@mui/x-date-pickers/AdapterDayjs",
"dayjs",
"dayjs/locale/en-gb",
"@mui/icons-material",
"yup",
"formik",
],
},
})
Loading

0 comments on commit 3f71cc2

Please sign in to comment.