diff options
| author | 2021-02-20 03:50:58 +0300 | |
|---|---|---|
| committer | 2021-02-20 03:53:45 +0300 | |
| commit | 0278c8f567bfc50fcb65aaf6afe7cd82c5031023 (patch) | |
| tree | a20d335623dc8b9e9038de1d5dc03e481039ecd8 /src/pages | |
| parent | Removes Path From Auth (diff) | |
| parent | Adds Missing Fields To Test Models (diff) | |
Merge branch 'main' into discord-oauth
Signed-off-by: Hassan Abouelela <[email protected]>
# Conflicts:
#	package.json
#	src/commonStyles.tsx
#	src/pages/FormPage.tsx
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/FormPage.tsx | 127 | 
1 files changed, 111 insertions, 16 deletions
| diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index 4e13b8a..fa63282 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"; @@ -14,8 +15,9 @@ import OAuth2Button  from "../components/OAuth2Button";  import { Form, FormFeatures, getForm } from "../api/forms";  import { OAuthScopes, checkScopes } from "../api/auth";  import colors from "../colors"; -import { submitStyles, unselectable } from "../commonStyles"; - +import { submitStyles, unselectable }  from "../commonStyles"; +import { Question, QuestionType } from "../api/question"; +import ApiClient from "../api/client";  interface PathParams {      id: string @@ -27,13 +29,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; @@ -70,12 +71,13 @@ class Navigation extends React.Component<NavigationProps> {        }      `; -    returnStyles = css` -      padding: 0.55rem 2.2rem; +    static returnStyles = css` +      padding: 0.5rem 2.2rem;        border-radius: 8px;        color: white;        text-decoration: none; +      white-space: nowrap;        background-color: ${colors.greyple};        transition: background-color 300ms; @@ -83,7 +85,6 @@ class Navigation extends React.Component<NavigationProps> {        :hover {          background-color: ${colors.darkerGreyple};        } -    }      `;      constructor(props: NavigationProps) { @@ -104,9 +105,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}/>                  <div css={submitStyles}>{ submit }</div> @@ -144,6 +145,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 => { @@ -151,26 +154,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); | 
