Skip to content
This repository has been archived by the owner on Dec 10, 2024. It is now read-only.

[DEV-050] add wizard form #70

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
17 changes: 7 additions & 10 deletions frontend/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<HeroSection />
<FeaturesList />
<button onClick={() => displaySuccessSnack("hey lol")}>Click me</button>
<button onClick={() => dispatch(displaySuccessSnack("hi lol"))}>
Click me
</button>
</>
);
}

export default connect(null, {
displaySuccessSnack,
})(Home);
export default Home;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooh TIL

151 changes: 151 additions & 0 deletions frontend/pages/signup/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<h1>{FormValue}</h1>
{hasPreviousStep ? (
<button type="button" onClick={() => previousStep()}>
{" "}
PREVIOUS STEP
</button>
) : null}
<button type="button" onClick={() => setValue("10@gmail.com")}>
change Name
</button>
{hasNextStep ? (
<button type="button" onClick={() => nextStep()}>
{" "}
NEXT STEP
</button>
) : null}
{FormValue === "password" ? <button type="submit">SUBMIT</button> : null}
</>
);
};

//Mapping
const SubForms: Array<SubForm> = [
{
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<FormValues>) => {
const [currentStep, setStep] = useState(0);
const nextStep = () => setStep((currentStep) => currentStep + 1);
const previousStep = () => setStep((currentStep) => currentStep - 1);

const { Component, key } = SubForms[currentStep];

return (
<Form onSubmit={handleSubmit}>
<Component
FormValue={key}
setValue={setValueHOC(key, setFieldValue)}
errors={errors[key]}
nextStep={nextStep}
previousStep={previousStep}
hasNextStep={currentStep !== SubForms.length - 1}
hasPreviousStep={currentStep !== 0}
hasErrors={errors[key] !== ""}
/>
<pre>{JSON.stringify(errors)}</pre>
<pre>{JSON.stringify(values)}</pre>
</Form>
);
};

const ComponentWithFormik = withFormik({
validationSchema: SignupSchema,
mapPropsToValues: () => initialValues,
validateOnMount: true,
handleSubmit: (values) => {
alert(JSON.stringify(values)); // TODO
},
})(WizardFormComponent);

export function Signup(): JSX.Element {
return <ComponentWithFormik />;
}

export default Signup;
22 changes: 16 additions & 6 deletions frontend/stores/uiSlice/actions.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -17,7 +16,11 @@ export const displaySnack = createAction<Snack>(
formatName(uiReducerName, "displaySnack")
);

export const displaySuccessSnack = (message: string) => {
export interface SnackAction extends Action<string> {
payload: Snack;
}

export const displaySuccessSnack = (message: string): SnackAction => {
return displaySnack({
key: Math.random(),
message,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -44,11 +47,18 @@ export const displayWarningSnack = (message: string) => {
});
};

export const displayInfoSnack = (message: string) => {
export const displayInfoSnack = (message: string): SnackAction => {
return displaySnack({
key: Math.random(),
message,
options: { variant: "info" },
dismissed: false,
});
};

export interface SnackActions {
displaySuccessSnack: typeof displaySuccessSnack;
displayErrorSnack: typeof displayErrorSnack;
displayWarningSnack: typeof displayWarningSnack;
displayInfoSnack: typeof displayInfoSnack;
}