aboutsummaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
authorGravatar Hassan Abouelela <[email protected]>2021-02-20 03:50:58 +0300
committerGravatar Hassan Abouelela <[email protected]>2021-02-20 03:53:45 +0300
commit0278c8f567bfc50fcb65aaf6afe7cd82c5031023 (patch)
treea20d335623dc8b9e9038de1d5dc03e481039ecd8 /src/pages
parentRemoves Path From Auth (diff)
parentAdds 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.tsx127
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);