diff --git a/package.json b/package.json index ca7346d..715b6ce 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "✅ Do add `devDependencies` below that are `peerDependencies` in the CFL package." ], "dependencies": { - "codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.1.2", + "codeforlife": "github:ocadotechnology/codeforlife-package-javascript#v2.1.3", "crypto-js": "^4.2.0" }, "devDependencies": { diff --git a/src/api/user.ts b/src/api/user.ts index 9a946b5..a392a51 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -88,7 +88,7 @@ const userApi = api.injectEndpoints({ createIndependentUser: build.mutation< CreateResult, CreateArg & { - date_of_birth: Date + date_of_birth: string add_to_newsletter: boolean } >({ diff --git a/src/components/form/LastNameField.tsx b/src/components/form/LastNameField.tsx new file mode 100644 index 0000000..d93d313 --- /dev/null +++ b/src/components/form/LastNameField.tsx @@ -0,0 +1,27 @@ +import { type FC } from "react" +import { string as YupString } from "yup" + +import { TextField, type TextFieldProps } from "codeforlife/components/form" + +export type LastNameFieldProps = Omit< + TextFieldProps, + "type" | "name" | "schema" +> & + Partial> + +const LastNameField: FC = ({ + name = "last_name", + label = "Last name", + placeholder = "Enter your last name", + ...otherTextFieldProps +}) => ( + +) + +export default LastNameField diff --git a/src/components/NewPasswordField.tsx b/src/components/form/NewPasswordField.tsx similarity index 81% rename from src/components/NewPasswordField.tsx rename to src/components/form/NewPasswordField.tsx index bc665d7..8ae46ce 100644 --- a/src/components/NewPasswordField.tsx +++ b/src/components/form/NewPasswordField.tsx @@ -1,20 +1,30 @@ import { Button, Dialog, Typography } from "@mui/material" import { type FC, useState } from "react" -import { PasswordField } from "codeforlife/components/form" +import { + PasswordField, + type PasswordFieldProps, +} from "codeforlife/components/form" import { indyPasswordSchema, pwnedPasswordSchema, studentPasswordSchema, teacherPasswordSchema, -} from "../app/schemas" +} from "../../app/schemas" -export interface NewPasswordFieldProps { +export interface NewPasswordFieldProps + extends Omit< + PasswordFieldProps, + "required" | "withRepeatField" | "schema" | "validateOptions" + > { userType: "teacher" | "independent" | "student" } -const NewPasswordField: FC = ({ userType }) => { +const NewPasswordField: FC = ({ + userType, + ...passwordFieldProps +}) => { const [pwnedPasswords, setPwnedPasswords] = useState<{ online: boolean dialogOpen: boolean @@ -46,6 +56,7 @@ const NewPasswordField: FC = ({ userType }) => { withRepeatField schema={schema} validateOptions={{ abortEarly: false }} + {...passwordFieldProps} /> diff --git a/src/components/form/index.tsx b/src/components/form/index.tsx new file mode 100644 index 0000000..8f674ee --- /dev/null +++ b/src/components/form/index.tsx @@ -0,0 +1,11 @@ +import LastNameField, { type LastNameFieldProps } from "./LastNameField" +import NewPasswordField, { + type NewPasswordFieldProps, +} from "./NewPasswordField" + +export { + LastNameField, + NewPasswordField, + type LastNameFieldProps, + type NewPasswordFieldProps, +} diff --git a/src/components/index.tsx b/src/components/index.tsx index ca19af8..8ca8746 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -1,13 +1,5 @@ -import NewPasswordField, { - type NewPasswordFieldProps, -} from "./NewPasswordField" import OpenInEmailButtons, { type OpenInEmailButtonsProps, } from "./OpenInEmailButtons" -export { - NewPasswordField, - OpenInEmailButtons, - type NewPasswordFieldProps, - type OpenInEmailButtonsProps, -} +export { OpenInEmailButtons, type OpenInEmailButtonsProps } diff --git a/src/pages/register/BaseForm.tsx b/src/pages/register/BaseForm.tsx new file mode 100644 index 0000000..205ee75 --- /dev/null +++ b/src/pages/register/BaseForm.tsx @@ -0,0 +1,33 @@ +import { FormHelperText, Typography } from "@mui/material" +import { type FC } from "react" + +import { ThemedBox, type ThemedBoxProps } from "codeforlife/theme" + +import { themeOptions } from "../../app/theme" + +export interface BaseFormProps extends ThemedBoxProps { + header: string + subheader: string + description: string +} + +const BaseForm: FC = ({ + header, + subheader, + description, + userType, + children, +}) => ( + + + {header} + + {subheader} + ({ marginBottom: spacing(3.5) })}> + {description} + + {children} + +) + +export default BaseForm diff --git a/src/pages/register/IndyForm.tsx b/src/pages/register/IndyForm.tsx new file mode 100644 index 0000000..0aec760 --- /dev/null +++ b/src/pages/register/IndyForm.tsx @@ -0,0 +1,146 @@ +import { ChevronRight as ChevronRightIcon } from "@mui/icons-material" +import { FormHelperText, Stack } from "@mui/material" +import dayjs from "dayjs" +import { type FC } from "react" +import { useNavigate } from "react-router-dom" + +import * as forms from "codeforlife/components/form" +import { Link } from "codeforlife/components/router" +import { submitForm } from "codeforlife/utils/form" + +import { useCreateIndependentUserMutation } from "../../api/user" +import { LastNameField, NewPasswordField } from "../../components/form" +import { paths } from "../../router" +import BaseForm from "./BaseForm" + +export interface IndyFormProps {} + +const IndyForm: FC = () => { + const navigate = useNavigate() + const [createIndependentUser] = useCreateIndependentUserMutation() + + const EmailApplicableAge = 13 + const ReceiveUpdateAge = 18 + + return ( + + { + navigate(paths.register.emailVerification.userType.indy._) + }, + })} + > + {form => { + const yearsOfAge = form.values.date_of_birth + ? Math.floor( + (new Date().getTime() - + new Date(form.values.date_of_birth).getTime()) / + (1000 * 60 * 60 * 24 * 365.25), + ) + : undefined + + return ( + <> + + Please enter your date of birth (we do not store this + information). + + + {yearsOfAge !== undefined && ( + <> + + + = EmailApplicableAge + ? "Email address" + : "Parent's email address" + } + placeholder={ + yearsOfAge >= EmailApplicableAge + ? "Enter your email address" + : "Enter your parent's email address" + } + /> + {yearsOfAge < EmailApplicableAge && ( + + We will send your parent/guardian an email to ask them to + activate the account for you. Once they've done this + you'll be able to log in using your name and + password. + + )} + {yearsOfAge >= EmailApplicableAge && ( + + I have read and understood the   + + Terms of use + +  and the  + + Privacy notice + + . + + ), + }} + /> + )} + {yearsOfAge >= ReceiveUpdateAge && ( + + )} + + + }> + Register + + + + )} + + ) + }} + + + ) +} + +export default IndyForm diff --git a/src/pages/register/Register.tsx b/src/pages/register/Register.tsx new file mode 100644 index 0000000..60a6cec --- /dev/null +++ b/src/pages/register/Register.tsx @@ -0,0 +1,53 @@ +import { Unstable_Grid2 as Grid } from "@mui/material" +import { type FC, useEffect } from "react" + +import * as page from "codeforlife/components/page" +import { useNavigate, useSessionMetadata } from "codeforlife/hooks" + +import { paths } from "../../router" +import IndyForm from "./IndyForm" +import TeacherForm from "./TeacherForm" + +export interface RegisterProps {} + +const Register: FC = () => { + const navigate = useNavigate() + const sessionMetadata = useSessionMetadata() + + useEffect(() => { + if (sessionMetadata) { + navigate( + sessionMetadata.auth_factors.length + ? // Send user to login page if they need to finish authenticating. + { + teacher: paths.login.teacher._, + student: paths.login.student._, + indy: paths.login.indy._, + }[sessionMetadata.user_type] + : // Send user to dashboard page if they finished authenticating. + { + teacher: paths.teacher.dashboard.tab.school._, + student: paths.student.dashboard._, + indy: paths.indy.dashboard._, + }[sessionMetadata.user_type], + ) + } + }, [sessionMetadata, navigate]) + + return ( + + + + + + + + + + + + + ) +} + +export default Register diff --git a/src/pages/register/TeacherForm.tsx b/src/pages/register/TeacherForm.tsx new file mode 100644 index 0000000..60bfa80 --- /dev/null +++ b/src/pages/register/TeacherForm.tsx @@ -0,0 +1,89 @@ +import { ChevronRight as ChevronRightIcon } from "@mui/icons-material" +import { Stack } from "@mui/material" +import { type FC } from "react" + +import * as form from "codeforlife/components/form" +import { Link } from "codeforlife/components/router" +import { useNavigate } from "codeforlife/hooks" +import { submitForm } from "codeforlife/utils/form" + +import { useCreateTeacherMutation } from "../../api/teacher" +import { LastNameField, NewPasswordField } from "../../components/form" +import { paths } from "../../router" +import BaseForm from "./BaseForm" + +export interface TeacherFormProps {} + +const TeacherForm: FC = () => { + const navigate = useNavigate() + const [createTeacher] = useCreateTeacherMutation() + + return ( + + { + navigate(paths.register.emailVerification.userType.teacher._) + }, + })} + > + + + + + I am over 18 years old have read and understood the  + + Terms of use + +  and the  + + Privacy notice + + . + + ), + }} + /> + + + + }> + Register + + + + + ) +} + +export default TeacherForm diff --git a/src/pages/resetPassword/PasswordForm.tsx b/src/pages/resetPassword/PasswordForm.tsx index 9fbec4d..6cc9127 100644 --- a/src/pages/resetPassword/PasswordForm.tsx +++ b/src/pages/resetPassword/PasswordForm.tsx @@ -6,7 +6,7 @@ import * as form from "codeforlife/components/form" import { LinkButton } from "codeforlife/components/router" import { useResetPasswordMutation } from "../../api/user" -import { NewPasswordField } from "../../components" +import { NewPasswordField } from "../../components/form" import { paths } from "../../router" export interface PasswordFormProps { @@ -46,7 +46,7 @@ const PasswordForm: FC = ({ userType, userId, token }) => { resetPassword([userId, { token, password }]) diff --git a/src/router/routes/authentication.tsx b/src/router/routes/authentication.tsx index e38fe3d..2188206 100644 --- a/src/router/routes/authentication.tsx +++ b/src/router/routes/authentication.tsx @@ -2,7 +2,7 @@ import { Route } from "react-router-dom" import EmailVerification from "../../pages/emailVerification/EmailVerification" import Login from "../../pages/login/Login" -// import Register from '../../pages/register/Register' +import Register from "../../pages/register/Register" import ResetPassword from "../../pages/resetPassword/ResetPassword" import paths from "../paths" @@ -34,7 +34,7 @@ const authentication = ( element={} /> } /> - {/* } /> */} + } /> ) diff --git a/yarn.lock b/yarn.lock index 2a3d186..0c68a38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2650,9 +2650,9 @@ clsx@^2.1.0, clsx@^2.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== -"codeforlife@github:ocadotechnology/codeforlife-package-javascript#v2.1.2": - version "2.1.2" - resolved "https://codeload.github.com/ocadotechnology/codeforlife-package-javascript/tar.gz/c8d3ae8eb8dee688276fe78a648be321dfdb23d9" +"codeforlife@github:ocadotechnology/codeforlife-package-javascript#v2.1.3": + version "2.1.3" + resolved "https://codeload.github.com/ocadotechnology/codeforlife-package-javascript/tar.gz/f4b88435300bc17a6f4f7555f537150a20d7e779" dependencies: "@emotion/react" "^11.10.6" "@emotion/styled" "^11.10.6"