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); |