From cc7323c7ac9d72230785636830cfb85788519a38 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Sat, 14 Oct 2023 10:13:26 -0700 Subject: [PATCH] Add basic validations to Login and Sign Up forms (#451) * added react hook form and refactored components --- frontend/package-lock.json | 81 +++++++++------- frontend/package.json | 1 + .../src/pages/Authentication/LoginForm.tsx | 76 +++++++++++++++ .../src/pages/Authentication/SignupForm.tsx | 92 ++++++++++++++++++ frontend/src/pages/Authentication/page.tsx | 97 +++---------------- .../{InputGroup.tsx => TextField.tsx} | 38 ++++++-- frontend/src/tw-components/index.tsx | 2 +- frontend/tailwind.config.js | 1 + 8 files changed, 260 insertions(+), 128 deletions(-) create mode 100644 frontend/src/pages/Authentication/LoginForm.tsx create mode 100644 frontend/src/pages/Authentication/SignupForm.tsx rename frontend/src/tw-components/{InputGroup.tsx => TextField.tsx} (50%) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 988a6d10..823480e2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@popperjs/core": "^2.11.6", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", + "react-hook-form": "^7.46.1", "react-popper": "^2.3.0" }, "devDependencies": { @@ -8915,6 +8916,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8926,6 +8928,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -8939,6 +8942,21 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "node_modules/react-hook-form": { + "version": "7.46.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.46.1.tgz", + "integrity": "sha512-0GfI31LRTBd5tqbXMGXT1Rdsv3rnvy0FjEk8Gn9/4tp6+s77T7DPZuGEpBRXOauL+NhyGT5iaXzdIM2R6F/E+w==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -9354,6 +9372,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -12753,57 +12772,49 @@ "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-remove-jsx-attribute": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz", "integrity": "sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-remove-jsx-empty-expression": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz", "integrity": "sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-replace-jsx-attribute-value": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-svg-dynamic-title": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-svg-em-dimensions": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-transform-react-native-svg": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-plugin-transform-svg-component": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", - "dev": true, - "requires": {} + "dev": true }, "@svgr/babel-preset": { "version": "6.5.1", @@ -13033,8 +13044,7 @@ "version": "14.4.3", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz", "integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==", - "dev": true, - "requires": {} + "dev": true }, "@tootallnate/once": { "version": "2.0.0", @@ -13460,8 +13470,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "requires": {} + "dev": true }, "@webpack-cli/info": { "version": "1.5.0", @@ -13476,8 +13485,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "requires": {} + "dev": true }, "@xtuc/ieee754": { "version": "1.2.0", @@ -13525,8 +13533,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "7.2.0", @@ -13559,8 +13566,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-escapes": { "version": "4.3.2", @@ -14825,8 +14831,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} + "dev": true }, "immutable": { "version": "4.1.0", @@ -15889,8 +15894,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "28.0.2", @@ -17197,8 +17201,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -17355,6 +17358,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -17363,6 +17367,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -17373,6 +17378,11 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "react-hook-form": { + "version": "7.46.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.46.1.tgz", + "integrity": "sha512-0GfI31LRTBd5tqbXMGXT1Rdsv3rnvy0FjEk8Gn9/4tp6+s77T7DPZuGEpBRXOauL+NhyGT5iaXzdIM2R6F/E+w==" + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -17664,6 +17674,7 @@ "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, "requires": { "loose-envify": "^1.1.0" } @@ -17848,8 +17859,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz", "integrity": "sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw==", - "dev": true, - "requires": {} + "dev": true }, "sucrase": { "version": "3.34.0", @@ -18629,8 +18639,7 @@ "version": "8.13.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} + "dev": true }, "xml-name-validator": { "version": "4.0.0", diff --git a/frontend/package.json b/frontend/package.json index 8a7bff5a..381771d9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,7 @@ "@popperjs/core": "^2.11.6", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", + "react-hook-form": "^7.46.1", "react-popper": "^2.3.0" } } diff --git a/frontend/src/pages/Authentication/LoginForm.tsx b/frontend/src/pages/Authentication/LoginForm.tsx new file mode 100644 index 00000000..8f743c08 --- /dev/null +++ b/frontend/src/pages/Authentication/LoginForm.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { TextField } from "tw-components"; +import { useForm, SubmitHandler } from "react-hook-form"; + +type Inputs = { + email: string; + password: string; +}; + +/** Login Form Component + * @dev used on the Authentication page + * @dev noValidate on form to disable browser vaildation so we can use react-hook-form validations instead + */ +export default function LoginForm() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const onSubmit: SubmitHandler = (data) => { + console.log("Sending form data to server...", data); + }; + + return ( +
+

Log in

+
+ + +
+ +

Keep me signed in

+
+ + +
+

+ New to Civic Tech Jobs?{" "} + + Sign up + +

+
+
+ ); +} diff --git a/frontend/src/pages/Authentication/SignupForm.tsx b/frontend/src/pages/Authentication/SignupForm.tsx new file mode 100644 index 00000000..3f7d6a0d --- /dev/null +++ b/frontend/src/pages/Authentication/SignupForm.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { TextField } from "tw-components"; +import { useForm, SubmitHandler } from "react-hook-form"; + +type Inputs = { + firstName: string; + lastName: string; + email: string; + password: string; +}; + +/** Signup Form Component + * @dev used on the Authentication page + * @dev noValidate on form to disable browser vaildation so we can use react-hook-form validations instead + */ +export default function SignupForm() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const onSubmit: SubmitHandler = (data) => { + console.log("Sending form data to server...", data); + }; + + return ( +
+

Sign up

+
+
+ + +
+ + + + +
+

+ Already on Civic Tech Jobs?{" "} + + Log In + +

+
+
+ ); +} diff --git a/frontend/src/pages/Authentication/page.tsx b/frontend/src/pages/Authentication/page.tsx index 1e57a342..a56fae79 100644 --- a/frontend/src/pages/Authentication/page.tsx +++ b/frontend/src/pages/Authentication/page.tsx @@ -1,99 +1,34 @@ import React from "react"; -import { Link, useLocation } from "react-router-dom"; -import { AuthNav, InputGroup } from "tw-components"; +import { useLocation } from "react-router-dom"; +import { AuthNav } from "tw-components"; + +import LoginForm from "./LoginForm"; +import SignupForm from "./SignupForm"; /** AuthenticationPage * @dev handles both "/login" and "/signup" paths -*/ - + */ export default function AuthenticationPage() { + const { pathname } = useLocation(); + return ( <>
+
+ {/* figma art here */} +
-
-
-
- +
+
+
+ {pathname === "/login" && } + {pathname === "/signup" && }
-
-
-
- -
-
-
); } - -function AuthForm() { - const { pathname } = useLocation(); - - return ( - <> - {pathname === "/login" && } - {pathname === "/signup" && } - - ); -} - -function SignupForm() { - return ( -
-

Sign up

-
-
- - -
- - - - - -
-

- Already on Civic Tech Jobs?{" "} - - Log In - -

-
-
- ); -} - -function LoginForm() { - return ( -
-

Log in

-
- - -
- -

Keep me signed in

-
- - -
-

- New to Civic Tech Jobs?{" "} - - Sign up - -

-
-
- ); -} diff --git a/frontend/src/tw-components/InputGroup.tsx b/frontend/src/tw-components/TextField.tsx similarity index 50% rename from frontend/src/tw-components/InputGroup.tsx rename to frontend/src/tw-components/TextField.tsx index c812029d..99040b19 100644 --- a/frontend/src/tw-components/InputGroup.tsx +++ b/frontend/src/tw-components/TextField.tsx @@ -1,12 +1,14 @@ -import React, { ReactNode } from "react"; - +import React from "react"; +import { UseFormRegister, FieldError } from "react-hook-form"; import { IconEyeOpen } from "assets/images/images"; -interface InputGroupProps extends React.PropsWithChildren { +interface TextFieldProps extends React.PropsWithChildren { label: string; id: string; type: "text" | "email" | "password"; - icon?: ReactNode; + register: UseFormRegister; + validations?: object; + errors?: FieldError | undefined; } /** Reusable input group component @@ -14,14 +16,21 @@ interface InputGroupProps extends React.PropsWithChildren { * @prop label -> Label for the input * @prop id -> ID for the input which also allows label to be linked to input * @prop type -> The type of input (text, email, password) may add more later - * @prop icon -> Optional icon to be displayed on the right side of the password input - * - * @TODO The password input's "Forgot password" and toggle visibility functionality + * @prop register -> React Hook Form's register function + * @prop validations -> React Hook Form's validation object + * @prop errors -> React Hook Form's errors object */ -export default function InputGroup({ label, id, type }: InputGroupProps) { +export default function TextField({ + label, + id, + type, + register, + validations, + errors, +}: TextFieldProps) { return ( -
+
{type === "password" && ( @@ -34,7 +43,12 @@ export default function InputGroup({ label, id, type }: InputGroupProps) { {type === "password" && (
@@ -42,6 +56,10 @@ export default function InputGroup({ label, id, type }: InputGroupProps) {
)}
+ +
+ {errors && errors.message} +
); } diff --git a/frontend/src/tw-components/index.tsx b/frontend/src/tw-components/index.tsx index f839b60b..9d70fffc 100644 --- a/frontend/src/tw-components/index.tsx +++ b/frontend/src/tw-components/index.tsx @@ -1,3 +1,3 @@ export { default as AuthNav } from "./AuthNav"; export { default as HeaderNav } from "./HeaderNav"; -export { default as InputGroup } from "./InputGroup"; +export { default as TextField } from "./TextField"; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index d999c8c5..2113248b 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -29,6 +29,7 @@ module.exports = { tan: "#ffe0b9", "tan-light": "#ffefdb", green: "#13831e", + red: "#CC0023", // Neutral Colors white: "#fff", "grey-light": "#f2f2f2",