diff options
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/FormPage.tsx | 124 | 
1 files changed, 110 insertions, 14 deletions
| diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index c49b9fd..1e331b9 100644 --- a/src/pages/FormPage.tsx +++ b/src/pages/FormPage.tsx @@ -2,8 +2,9 @@  import { jsx, css } from "@emotion/react";  import { Link } from "react-router-dom"; -import React, { SyntheticEvent, useEffect, useState } from "react"; +import React, { SyntheticEvent, useEffect, useState, createRef } from "react";  import { useParams } from "react-router"; +import { PropagateLoader } from "react-spinners";  import HeaderBar from "../components/HeaderBar";  import RenderedQuestion from "../components/Question"; @@ -13,7 +14,8 @@ import ScrollToTop from "../components/ScrollToTop";  import { Form, FormFeatures, getForm } from "../api/forms";  import colors from "../colors";  import { unselectable }  from "../commonStyles"; - +import { Question, QuestionType } from "../api/question"; +import ApiClient from "../api/client";  interface PathParams {      id: string @@ -24,13 +26,12 @@ interface NavigationProps {  }  class Navigation extends React.Component<NavigationProps> { -    containerStyles = css` +    static containerStyles = css`        margin: auto;        width: 50%;        text-align: center;        font-size: 1.5rem; -      white-space: nowrap;        > div {          display: inline-block; @@ -67,12 +68,13 @@ class Navigation extends React.Component<NavigationProps> {        }      `; -    returnStyles = css` +    static returnStyles = css`        padding: 0.5rem 2rem;        border-radius: 8px;        color: white;        text-decoration: none; +      white-space: nowrap;        background-color: ${colors.greyple};        transition: background-color 300ms; @@ -80,11 +82,11 @@ class Navigation extends React.Component<NavigationProps> {        :hover {          background-color: ${colors.darkerGreyple};        } -    }      `;      submitStyles = css`        text-align: right; +      white-space: nowrap;        button {          padding: 0.5rem 4rem; @@ -116,9 +118,9 @@ class Navigation extends React.Component<NavigationProps> {          }          return ( -            <div css={[unselectable, this.containerStyles]}> +            <div css={[unselectable, Navigation.containerStyles]}>                  <div className={ "return_button" + (this.props.form_state ? "" : " closed") }> -                    <Link to="/" css={this.returnStyles}>Return Home</Link> +                    <Link to="/" css={Navigation.returnStyles}>Return Home</Link>                  </div>                  <br css={this.separatorStyles}/>                  { submit } @@ -156,6 +158,8 @@ function FormPage(): JSX.Element {      const { id } = useParams<PathParams>();      const [form, setForm] = useState<Form>(); +    const [sending, setSending] = useState<boolean>(); +    const [sent, setSent] = useState<boolean>();      useEffect(() => {          getForm(id).then(form => { @@ -163,26 +167,118 @@ function FormPage(): JSX.Element {          });      }, []); +    if (form && sent) { +        const thanksStyle = css`font-family: "Uni Sans", "Hind", "Arial", sans-serif; margin-top: 15.5rem;`; +        const divStyle = css`width: 80%;`; +        return ( +            <div> +                <HeaderBar title={form.name} description={form.description}/> +                <div css={[unselectable, Navigation.containerStyles, divStyle]}> +                    <h3 css={thanksStyle}>{form.submitted_text ?? "Thanks for your response!"}</h3> +                    <div className={ "return_button closed" }> +                        <Link to="/" css={Navigation.returnStyles}>Return Home</Link> +                    </div> +                </div> +            </div> +        ); +    } + +    if (sending) { +        return ( +            <div> +                <HeaderBar title={"Submitting..."}/> +                <div css={{display: "flex", justifyContent: "center", paddingTop: "40px"}}> +                    <PropagateLoader color="white"/> +                </div> +            </div> +        ); +    } +      if (!form) {          return <Loading/>;      } +    const refMap: Map<string, React.RefObject<RenderedQuestion>> = new Map();      const questions = form.questions.map((question, index) => { -        return <RenderedQuestion question={question} public_state={new Map()} key={index + Date.now()}/>; +        const questionRef = createRef<RenderedQuestion>(); +        refMap.set(question.id, questionRef); +        // eslint-disable-next-line @typescript-eslint/no-explicit-any +        return <RenderedQuestion ref={questionRef} focus_ref={createRef<any>()} scroll_ref={createRef<HTMLDivElement>()} question={question} public_state={new Map()} key={index + Date.now()}/>;      }); -    function handleSubmit(event: SyntheticEvent) { +    async function handleSubmit(event: SyntheticEvent) { +        event.preventDefault(); +        // Client-side required validation +        const invalidFieldIDs: number[] = []; +        questions.forEach((prop, i) => { +            const question: Question = prop.props.question; +            if (!question.required) { +                return; +            } + +            const questionRef = refMap.get(question.id); +            if (questionRef && questionRef.current) { +                questionRef.current.validateField(); +            } +            // In case when field is invalid, add this to invalid fields list. +            if (prop.props.public_state.get("valid") === false) { +                invalidFieldIDs.push(i); +            } +        }); + +        if (invalidFieldIDs.length) { +            const firstErrored = questions[invalidFieldIDs[0]]; +            if (firstErrored && firstErrored.props.scroll_ref) { +                // If any element is already focused, unfocus it to avoid not scrolling behavior. +                if (document.activeElement && document.activeElement instanceof HTMLElement) { +                    document.activeElement.blur(); +                } + +                firstErrored.props.scroll_ref.current.scrollIntoView({ behavior: "smooth", block: "center" }); +                if (firstErrored.props.focus_ref && firstErrored.props.focus_ref.current) { +                    firstErrored.props.focus_ref.current.focus({ preventScroll: true }); +                } +            } +            return; +        } + +        setSending(true); + +        const answers: { [key: string]: unknown } = {};          questions.forEach(prop => { -            const question = prop.props.question; +            const question: Question = prop.props.question; +            const options: string | string[] = question.data["options"]; -            // TODO: Parse input from each question, and submit +            // Parse input from each question              switch (question.type) { +                case QuestionType.Section: +                    answers[question.id] = false; +                    break; + +                case QuestionType.Checkbox: { +                    if (typeof options !== "string") { +                        const keys: Map<string, string> = new Map(); +                        options.forEach((val: string, index) => { +                            keys.set(val, `${("000" + index).slice(-4)}. ${val}`); +                        }); +                        const pairs: { [key: string]: boolean } = { }; +                        keys.forEach((val, key) => { +                            pairs[key] = !!prop.props.public_state.get(val); +                        }); +                        answers[question.id] = pairs; +                    } +                    break; +                } + +                case QuestionType.Code:                  default: -                    console.log(question.id, prop.props.public_state); +                    answers[question.id] = prop.props.public_state.get("value");              }          }); -        event.preventDefault(); +        await ApiClient.post(`forms/submit/${id}`, {response: answers}); +        setSending(false); +        setSent(true);      }      const open: boolean = form.features.includes(FormFeatures.Open); | 
