From 3f71cc2148638827310dd5ba82e0f5d3a954b7e2 Mon Sep 17 00:00:00 2001 From: Stefan Kairinos Date: Fri, 12 Jul 2024 14:16:34 +0100 Subject: [PATCH] Email verification (#9) * add git lens * add page * feedback * delete images * merge from dev --- .env | 3 + package.json | 2 +- .../emailVerification/EmailVerification.tsx | 84 +++++++++++++++++ src/pages/emailVerification/Status.tsx | 54 +++++++++++ src/pages/login/Login.tsx | 13 +-- src/pages/login/studentForms/Class.tsx | 15 ++-- src/router/paths.ts | 7 +- src/router/routes/authentication.tsx | 11 ++- vite.config.ts | 10 ++- yarn.lock | 90 +++++++++++++++++-- 10 files changed, 252 insertions(+), 37 deletions(-) create mode 100644 src/pages/emailVerification/EmailVerification.tsx create mode 100644 src/pages/emailVerification/Status.tsx diff --git a/.env b/.env index a868567..2fbff44 100644 --- a/.env +++ b/.env @@ -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/ diff --git a/package.json b/package.json index 831bdc3..b8dc1b3 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/pages/emailVerification/EmailVerification.tsx b/src/pages/emailVerification/EmailVerification.tsx new file mode 100644 index 0000000..ca678be --- /dev/null +++ b/src/pages/emailVerification/EmailVerification.tsx @@ -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 = () => { + 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 ( + + + {searchParams?.success ? ( + } + buttonProps={[ + { + to: import.meta.env.VITE_LINK_OPEN_VERIFY_EMAIL_IN_GMAIL, + target: "_blank", + children: "Open in Gmail", + endIcon: , + }, + { + to: import.meta.env.VITE_LINK_OPEN_VERIFY_EMAIL_IN_OUTLOOK, + target: "_blank", + children: "Open in Outlook", + endIcon: , + }, + ]} + /> + ) : ( + } + /> + )} + + + ) +} + +export default EmailVerification diff --git a/src/pages/emailVerification/Status.tsx b/src/pages/emailVerification/Status.tsx new file mode 100644 index 0000000..a04b8ed --- /dev/null +++ b/src/pages/emailVerification/Status.tsx @@ -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 = ({ + userType, + header, + body, + icon, + buttonProps, +}) => ( + + + + {header} + + {icon} + + {body.map((text, index) => ( + {text} + ))} + + {buttonProps && ( + + {buttonProps.map((props, index) => ( + + ))} + + )} + + + homepage + + +) + +export default Status diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx index 0bafb8b..ea03d1c 100644 --- a/src/pages/login/Login.tsx +++ b/src/pages/login/Login.tsx @@ -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" @@ -27,13 +26,9 @@ export interface LoginProps { const Login: FC = ({ 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) { diff --git a/src/pages/login/studentForms/Class.tsx b/src/pages/login/studentForms/Class.tsx index 9ff4e4e..1583249 100644 --- a/src/pages/login/studentForms/Class.tsx +++ b/src/pages/login/studentForms/Class.tsx @@ -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" @@ -18,14 +17,10 @@ export interface ClassProps {} const Class: FC = () => { 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) { diff --git a/src/router/paths.ts b/src/router/paths.ts index 327104d..b5b903a 100644 --- a/src/router/paths.ts +++ b/src/router/paths.ts @@ -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"), diff --git a/src/router/routes/authentication.tsx b/src/router/routes/authentication.tsx index df70e3b..f39086b 100644 --- a/src/router/routes/authentication.tsx +++ b/src/router/routes/authentication.tsx @@ -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' @@ -15,10 +14,6 @@ import paths from "../paths" // path={paths.register._} // element={} // /> -// } -// /> const authentication = ( <> @@ -43,6 +38,10 @@ const authentication = ( element={} /> } /> + } + /> ) diff --git a/vite.config.ts b/vite.config.ts index ca274c8..01e6878 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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", + ], }, }) diff --git a/yarn.lock b/yarn.lock index 7809a22..9a63987 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1125,6 +1125,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.7": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" @@ -1503,7 +1510,7 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@mui/base@5.0.0-beta.40": +"@mui/base@5.0.0-beta.40", "@mui/base@^5.0.0-beta.40": version "5.0.0-beta.40" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== @@ -1555,6 +1562,15 @@ "@mui/utils" "^5.15.20" prop-types "^15.8.1" +"@mui/private-theming@^5.16.1": + version "5.16.1" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.1.tgz#e7f1b0cfd9b238231fca9a0b13a5b2a9d9592b35" + integrity sha512-2EGCKnAlq9vRIFj61jNWNXlKAxXp56577OVvsts7fAqRx+G1y6F+N7Q198SBaz8jYQeGKSz8ZMXK/M3FqjdEyw== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.16.1" + prop-types "^15.8.1" + "@mui/styled-engine@^5.15.14": version "5.15.14" resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" @@ -1565,6 +1581,16 @@ csstype "^3.1.3" prop-types "^15.8.1" +"@mui/styled-engine@^5.16.1": + version "5.16.1" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.1.tgz#7518f64f66edd6e09f129289cf6ece502520947f" + integrity sha512-JwWUBaYR8HHCFefSeos0z6JoTbu0MnjAuNHu4QoDgPxl2EE70XH38CsKay66Iy0QkNWmGTRXVU2sVFgUOPL/Dw== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + "@mui/system@^5.15.20": version "5.15.20" resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.20.tgz#f1933aabc4c10f8580c7a951ca3b88542ef0f76b" @@ -1579,11 +1605,30 @@ csstype "^3.1.3" prop-types "^15.8.1" +"@mui/system@^5.16.0": + version "5.16.1" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.1.tgz#c08fddba592511d6916c6a70da292a7658551ccb" + integrity sha512-VaFcClC+uhvIEzhzcNmh9FRBvrG9IPjsOokhj6U1HPZsFnLzHV7AD7dJcT6LxWoiIZj9Ej0GK+MGh/b8+BtSlQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.16.1" + "@mui/styled-engine" "^5.16.1" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.1" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + "@mui/types@^7.2.14": version "7.2.14" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== +"@mui/types@^7.2.15": + version "7.2.15" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53" + integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q== + "@mui/utils@^5.15.14", "@mui/utils@^5.15.20": version "5.15.20" resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.20.tgz#92778d749ce5ded1598639b4e684aaedb1146e08" @@ -1594,6 +1639,30 @@ prop-types "^15.8.1" react-is "^18.2.0" +"@mui/utils@^5.16.0", "@mui/utils@^5.16.1": + version "5.16.1" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.1.tgz#068ddc236c10f71768133c144b3286d2cc815f5f" + integrity sha512-4UQzK46tAEYs2xZv79hRiIc3GxZScd00kGPDadNrGztAEZlmSaUY8cb9ITd2xCiTfzsx5AN6DH8aaQ8QEKJQeQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.12" + prop-types "^15.8.1" + react-is "^18.3.1" + +"@mui/x-date-pickers@^7.7.1": + version "7.10.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-7.10.0.tgz#d9a2745299016b4da3ed6fe29adca09423d4b97e" + integrity sha512-mfJuKOdrrdlH5FskXl0aypRmZuVctNRwn5Xw0aMgE3n1ORCpzDSGCXd5El1/PdH3/3olT+vPFmxXKMQju5UMow== + dependencies: + "@babel/runtime" "^7.24.7" + "@mui/base" "^5.0.0-beta.40" + "@mui/system" "^5.16.0" + "@mui/utils" "^5.16.0" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -1862,7 +1931,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== -"@types/prop-types@*", "@types/prop-types@^15.7.11": +"@types/prop-types@*", "@types/prop-types@^15.7.11", "@types/prop-types@^15.7.12": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== @@ -2571,20 +2640,22 @@ clipboardy@3.0.0: execa "^5.1.1" is-wsl "^2.2.0" -clsx@^2.1.0: +clsx@^2.1.0, clsx@^2.1.1: version "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.0.3": - version "2.0.3" - resolved "https://codeload.github.com/ocadotechnology/codeforlife-package-javascript/tar.gz/de5f0defa524aa2e8c6543771718045ae649821a" +"codeforlife@github:ocadotechnology/codeforlife-package-javascript#v2.1.0": + version "2.1.0" + resolved "https://codeload.github.com/ocadotechnology/codeforlife-package-javascript/tar.gz/384fcda4abaaa3bde360e170993b5c145773c9b3" dependencies: "@emotion/react" "^11.10.6" "@emotion/styled" "^11.10.6" "@mui/icons-material" "^5.11.11" "@mui/material" "^5.11.12" + "@mui/x-date-pickers" "^7.7.1" "@reduxjs/toolkit" "^2.0.1" + dayjs "^1.11.11" formik "^2.2.9" js-cookie "^3.0.5" qs "^6.11.2" @@ -2768,6 +2839,11 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +dayjs@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4835,7 +4911,7 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0, react-is@^18.2.0: +react-is@^18.0.0, react-is@^18.2.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==