diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a92978f..8ec43d2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5520,6 +5520,11 @@ "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", "dev": true }, + "@types/yup": { + "version": "0.29.11", + "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.29.11.tgz", + "integrity": "sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g==" + }, "@typescript-eslint/eslint-plugin": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.11.1.tgz", @@ -10641,6 +10646,37 @@ "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=", "dev": true }, + "formik": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.6.tgz", + "integrity": "sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA==", + "requires": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.14", + "lodash-es": "^4.17.14", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^1.10.0" + }, + "dependencies": { + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + }, + "react-fast-compare": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -14206,8 +14242,7 @@ "lodash-es": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.20.tgz", - "integrity": "sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==", - "dev": true + "integrity": "sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==" }, "lodash.merge": { "version": "4.6.2", @@ -14793,6 +14828,11 @@ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "optional": true }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "nanoid": { "version": "3.1.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", @@ -16423,6 +16463,11 @@ "react-is": "^16.8.1" } }, + "property-expr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz", + "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==" + }, "property-information": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", @@ -19645,6 +19690,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=" + }, "tough-cookie": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", @@ -21022,6 +21072,20 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "yup": { + "version": "0.32.8", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.8.tgz", + "integrity": "sha512-SZulv5FIZ9d5H99EN5tRCRPXL0eyoYxWIP1AacCrjC9d4DfP13J1dROdKGfpfRHT3eQB6/ikBl5jG21smAfCkA==", + "requires": { + "@babel/runtime": "^7.10.5", + "@types/lodash": "^4.14.165", + "lodash": "^4.17.20", + "lodash-es": "^4.17.11", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + } + }, "zwitch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index da2a05f..abb1528 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,9 @@ "@emotion/styled": "^11.0.0", "@material-ui/core": "^4.11.2", "@reduxjs/toolkit": "^1.5.0", + "@types/yup": "^0.29.11", "axios": "^0.21.0", + "formik": "^2.2.6", "framer-motion": "^2.9.5", "next": "10.0.3", "notistack": "^1.0.3", @@ -26,7 +28,8 @@ "react-intl": "^5.10.7", "react-redux": "^7.2.2", "redux": "^4.0.5", - "redux-devtools-extension": "^2.13.8" + "redux-devtools-extension": "^2.13.8", + "yup": "^0.32.8" }, "devDependencies": { "@babel/core": "^7.12.10", diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index 1dfbfad..a94aea5 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -1,24 +1,21 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable react/prop-types */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ import React from "react"; -import { connect } from "react-redux"; +import { useDispatch } from "react-redux"; import { FeaturesList } from "../components/FeaturesList"; import { HeroSection } from "../components/HeroSection"; import { displaySuccessSnack } from "../stores/uiSlice/actions"; -export function Home({ displaySuccessSnack }): JSX.Element { +export function Home(): JSX.Element { + const dispatch = useDispatch(); return ( <> - + ); } -export default connect(null, { - displaySuccessSnack, -})(Home); +export default Home; diff --git a/frontend/pages/signup/index.tsx b/frontend/pages/signup/index.tsx new file mode 100644 index 0000000..f049758 --- /dev/null +++ b/frontend/pages/signup/index.tsx @@ -0,0 +1,151 @@ +import { Form, FormikProps, withFormik } from "formik"; +import React, { useState } from "react"; +import * as Yup from "yup"; + +//Validation Schema +const SignupSchema = Yup.object().shape({ + Name: Yup.string().optional(), + School: Yup.string().optional(), + email: Yup.string().email("Invalid email").required("Required"), + password: Yup.string().required("Required!"), //Update this with correct validation +}); + +//Initial Values +const initialValues = { + name: "", + school: "", + email: "", + password: "", +}; + +//Key types +type FormFields = keyof typeof initialValues; + +//Form interface +interface FormValues { + name: string; + school: string; + email: string; + password: string; +} + +//Props for each subcomponent +interface SubFormProps { + errors?: string; + previousStep?: () => void; + nextStep?: () => void; + setValue: (value: string) => void; + FormValue: FormFields; + hasErrors: boolean; + hasPreviousStep: boolean; + hasNextStep: boolean; +} + +//Mapping type +interface SubForm { + Component: (props: SubFormProps) => JSX.Element; + key: FormFields; +} + +const sampleComponent = ({ + setValue, + FormValue, + hasPreviousStep, + hasNextStep, + nextStep, + previousStep, +}: SubFormProps) => { + return ( + <> +

{FormValue}

+ {hasPreviousStep ? ( + + ) : null} + + {hasNextStep ? ( + + ) : null} + {FormValue === "password" ? : null} + + ); +}; + +//Mapping +const SubForms: Array = [ + { + Component: sampleComponent, + key: "name", + }, + { + Component: sampleComponent, + key: "school", + }, + { + Component: sampleComponent, + key: "email", + }, + { + Component: sampleComponent, + key: "password", + }, +]; + +// Form Helpers +const setValueHOC = ( + key: string, + setterFunction: (key: string, value: string) => void +) => (value: string) => setterFunction(key, value); + +//Form +const WizardFormComponent = ({ + handleSubmit, + setFieldValue, + errors, + values, +}: FormikProps) => { + const [currentStep, setStep] = useState(0); + const nextStep = () => setStep((currentStep) => currentStep + 1); + const previousStep = () => setStep((currentStep) => currentStep - 1); + + const { Component, key } = SubForms[currentStep]; + + return ( +
+ +
{JSON.stringify(errors)}
+
{JSON.stringify(values)}
+ + ); +}; + +const ComponentWithFormik = withFormik({ + validationSchema: SignupSchema, + mapPropsToValues: () => initialValues, + validateOnMount: true, + handleSubmit: (values) => { + alert(JSON.stringify(values)); // TODO + }, +})(WizardFormComponent); + +export function Signup(): JSX.Element { + return ; +} + +export default Signup; diff --git a/frontend/stores/uiSlice/actions.tsx b/frontend/stores/uiSlice/actions.tsx index 457f935..a50cf9f 100644 --- a/frontend/stores/uiSlice/actions.tsx +++ b/frontend/stores/uiSlice/actions.tsx @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { createAction } from "@reduxjs/toolkit"; +import { Action, createAction } from "@reduxjs/toolkit"; import { formatName } from "../helpers"; import { uiReducerName } from "./adapter"; @@ -17,7 +16,11 @@ export const displaySnack = createAction( formatName(uiReducerName, "displaySnack") ); -export const displaySuccessSnack = (message: string) => { +export interface SnackAction extends Action { + payload: Snack; +} + +export const displaySuccessSnack = (message: string): SnackAction => { return displaySnack({ key: Math.random(), message, @@ -26,7 +29,7 @@ export const displaySuccessSnack = (message: string) => { }); }; -export const displayErrorSnack = (message: string) => { +export const displayErrorSnack = (message: string): SnackAction => { return displaySnack({ key: Math.random(), message, @@ -35,7 +38,7 @@ export const displayErrorSnack = (message: string) => { }); }; -export const displayWarningSnack = (message: string) => { +export const displayWarningSnack = (message: string): SnackAction => { return displaySnack({ key: Math.random(), message, @@ -44,7 +47,7 @@ export const displayWarningSnack = (message: string) => { }); }; -export const displayInfoSnack = (message: string) => { +export const displayInfoSnack = (message: string): SnackAction => { return displaySnack({ key: Math.random(), message, @@ -52,3 +55,10 @@ export const displayInfoSnack = (message: string) => { dismissed: false, }); }; + +export interface SnackActions { + displaySuccessSnack: typeof displaySuccessSnack; + displayErrorSnack: typeof displayErrorSnack; + displayWarningSnack: typeof displayWarningSnack; + displayInfoSnack: typeof displayInfoSnack; +}