aboutsummaryrefslogtreecommitdiffstats
path: root/src/pages/FormPage
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages/FormPage')
-rw-r--r--src/pages/FormPage/ErrorPage.tsx29
-rw-r--r--src/pages/FormPage/FormPage.tsx41
-rw-r--r--src/pages/FormPage/Navigation.tsx111
-rw-r--r--src/pages/FormPage/SuccessPage.tsx10
4 files changed, 72 insertions, 119 deletions
diff --git a/src/pages/FormPage/ErrorPage.tsx b/src/pages/FormPage/ErrorPage.tsx
index da336cf..351170b 100644
--- a/src/pages/FormPage/ErrorPage.tsx
+++ b/src/pages/FormPage/ErrorPage.tsx
@@ -7,9 +7,7 @@ import HeaderBar from "../../components/HeaderBar";
import {Form} from "../../api/forms";
import {clearAuth} from "../../api/auth";
-import {selectable, submitStyles, unselectable} from "../../commonStyles";
-
-import Navigation from "./Navigation";
+import * as styles from "../../commonStyles";
interface ErrorProps {
@@ -28,19 +26,18 @@ export default function ErrorPage(props: ErrorProps): JSX.Element {
return (
<div>
<HeaderBar title={props.form.name} description={props.form.description}/>
- <div css={[unselectable, Navigation.containerStyles]}>
- <h3 css={selectable}>{props.message}</h3>
- <div className={ "return_button" }>
- <Link to="/" css={Navigation.returnStyles}>Return Home</Link>
- </div>
- <br css={Navigation.separatorStyles}/>
- <div css={submitStyles}>
- <button
- type="button" css={refreshStyles}
- onClick={location.reload.bind(window.location)} // TODO: State should probably be saved here
- >
- Refresh
- </button>
+ <div css={[styles.unselectable, styles.mainTextStyles]}>
+ <h3 css={styles.selectable}>{props.message}</h3>
+ <div css={styles.navigationStyles}>
+ <Link css={styles.returnButtonStyles} to="/">Return Home</Link>
+ <div css={styles.actionButtonStyles}>
+ <button
+ type="button" css={refreshStyles}
+ onClick={location.reload.bind(window.location)} // TODO: State should probably be saved here
+ >
+ Refresh
+ </button>
+ </div>
</div>
</div>
</div>
diff --git a/src/pages/FormPage/FormPage.tsx b/src/pages/FormPage/FormPage.tsx
index 9f74410..05b51c0 100644
--- a/src/pages/FormPage/FormPage.tsx
+++ b/src/pages/FormPage/FormPage.tsx
@@ -1,9 +1,11 @@
/** @jsx jsx */
+/** @jsxFrag */
import {jsx, css} from "@emotion/react";
import React, {createRef, SyntheticEvent, useEffect, useState} from "react";
import {useParams} from "react-router";
import {PropagateLoader} from "react-spinners";
+import {AxiosError} from "axios";
import HeaderBar from "../../components/HeaderBar";
import RenderedQuestion from "../../components/Question";
@@ -19,6 +21,7 @@ import handleSubmit, {FormState} from "./submit";
import Navigation from "./Navigation";
import Success from "./SuccessPage";
import ErrorPage from "./ErrorPage";
+import NotFound from "../NotFound";
export type RefMapType = Map<string, React.RefObject<RenderedQuestion>>;
@@ -49,24 +52,47 @@ const closedHeaderStyles = css`
}
`;
+enum LoadingState {
+ Pending,
+ Found,
+ Missing
+}
+
function FormPage(): JSX.Element {
const {id} = useParams<{id: string}>();
const [form, setForm] = useState<Form>();
+ const [formLoading, setFormLoading] = useState<LoadingState>(LoadingState.Pending);
const [state, setState] = useState<string>(FormState.INITIAL);
const OAuthRef = createRef<HTMLDivElement>();
useEffect(() => {
- // This can't be null due to the routing to get here
+ // ID can't be null due to the routing to get here
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getForm(id!).then(form => {
setForm(form);
+ setFormLoading(LoadingState.Found);
+ }).catch((error: AxiosError) => {
+ if (error.response?.status === 404) {
+ setFormLoading(LoadingState.Missing);
+ return;
+ }
+
+ throw error;
});
}, []);
+ switch (formLoading) {
+ case LoadingState.Pending:
+ return <Loading/>;
+ case LoadingState.Missing:
+ return <NotFound message={"Could not find a matching form. Make sure the requested form exists and is open."}/>;
+ }
+
if (!form) {
- return <Loading/>;
+ // This should be an impossible state
+ throw Error("Form was not set despite loading state being set to found.");
}
const refMap: RefMapType = new Map();
@@ -88,9 +114,6 @@ function FormPage(): JSX.Element {
<div css={closedHeaderStyles}>This form is now closed. You will not be able to submit your response.</div>;
}
- // FIXME: Remove this ignore
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
const questions: RenderedQuestion[] = form.questions.map((question, index) => {
const questionRef = createRef<RenderedQuestion>();
refMap.set(question.id, questionRef);
@@ -102,7 +125,7 @@ function FormPage(): JSX.Element {
key={index + Date.now()}
selfRef={questionRef}
ref={questionRef}
- />;
+ /> as unknown as RenderedQuestion; // Annotations for JSX elements resolve to a generic ReactElement
});
switch (state) {
@@ -133,8 +156,10 @@ function FormPage(): JSX.Element {
<div>
<form id="form" onSubmit={handler} css={[formStyles, unselectable]}>
- {closed_header}
- {questions}
+ <>
+ {closed_header}
+ {questions}
+ </>
</form>
<Navigation form_state={open} scopes={scopes}/>
</div>
diff --git a/src/pages/FormPage/Navigation.tsx b/src/pages/FormPage/Navigation.tsx
index 52cd47e..20c7dce 100644
--- a/src/pages/FormPage/Navigation.tsx
+++ b/src/pages/FormPage/Navigation.tsx
@@ -1,12 +1,9 @@
/** @jsx jsx */
-import {jsx, css} from "@emotion/react";
-
-import React from "react";
+import {jsx} from "@emotion/react";
+import React, {useState} from "react";
import {Link} from "react-router-dom";
-import colors from "../../colors";
-import {submitStyles, unselectable} from "../../commonStyles";
-
+import * as styles from "../../commonStyles";
import {checkScopes, OAuthScopes} from "../../api/auth";
import OAuth2Button from "../../components/OAuth2Button";
@@ -16,92 +13,28 @@ interface NavigationProps {
scopes: OAuthScopes[]
}
-export default class Navigation extends React.Component<NavigationProps> {
- static containerStyles = css`
- margin: auto;
- width: 50%;
-
- text-align: center;
- font-size: 1.5rem;
-
- > div {
- display: inline-block;
- margin: 2rem auto;
- width: 50%;
- }
-
- @media (max-width: 870px) {
- width: 100%;
-
- > div {
- display: flex;
- justify-content: center;
-
- margin: 0 auto;
+export default function Navigation(props: NavigationProps): JSX.Element {
+ const [authorized, setAuth] = useState<boolean>(!(
+ props.scopes.includes(OAuthScopes.Identify) && !checkScopes(props.scopes)
+ ));
+
+ let submit = null;
+ if (props.form_state) {
+ let innerElement;
+ if (!authorized) {
+ innerElement = <OAuth2Button rerender={() => setAuth(true)} scopes={props.scopes}/>;
+ } else {
+ innerElement = <button form="form" type="submit">Submit</button>;
}
- }
-
- .return_button {
- text-align: left;
- }
-
- .return_button.closed {
- text-align: center;
- }
- `;
-
- static separatorStyles = css`
- height: 0;
- display: none;
-
- @media (max-width: 870px) {
- display: block;
- }
- `;
-
- 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;
-
- :hover {
- background-color: ${colors.darkerGreyple};
- }
- `;
-
- constructor(props: NavigationProps) {
- super(props);
- this.state = {"logged_in": false};
+ submit = <div css={styles.actionButtonStyles}>{innerElement}</div>;
}
- render(): JSX.Element {
- let submit = null;
-
- if (this.props.form_state) {
- let inner_submit;
- if (this.props.scopes.includes(OAuthScopes.Identify) && !checkScopes(this.props.scopes)) {
- // Render OAuth button if login is required, and the scopes needed are not available
- inner_submit = <OAuth2Button scopes={this.props.scopes} rerender={() => this.setState({"logged_in": true})}/>;
- } else {
- inner_submit = <button form="form" type="submit">Submit</button>;
- }
- submit = <div css={submitStyles}>{ inner_submit }</div>;
- }
-
- return (
- <div css={[unselectable, Navigation.containerStyles]}>
- <div className={ "return_button" + (this.props.form_state ? "" : " closed") }>
- <Link to="/" css={Navigation.returnStyles}>Return Home</Link>
- </div>
- <br css={Navigation.separatorStyles}/>
+ return (
+ <div css={[styles.unselectable, styles.mainTextStyles]}>
+ <div css={styles.navigationStyles}>
+ <Link to="/" css={styles.returnButtonStyles}>Return Home</Link>
{ submit }
</div>
- );
- }
+ </div>
+ );
}
diff --git a/src/pages/FormPage/SuccessPage.tsx b/src/pages/FormPage/SuccessPage.tsx
index e35bd4d..e83ca0d 100644
--- a/src/pages/FormPage/SuccessPage.tsx
+++ b/src/pages/FormPage/SuccessPage.tsx
@@ -4,9 +4,7 @@ import {Link} from "react-router-dom";
import {Form} from "../../api/forms";
import HeaderBar from "../../components/HeaderBar";
-import {unselectable} from "../../commonStyles";
-
-import Navigation from "./Navigation";
+import {returnButtonStyles, navigationStyles, unselectable, mainTextStyles} from "../../commonStyles";
interface SuccessProps {
@@ -34,10 +32,10 @@ export default function Success(props: SuccessProps): JSX.Element {
return (
<div>
<HeaderBar title={props.form.name} description={props.form.description}/>
- <div css={[unselectable, Navigation.containerStyles, divStyle]}>
+ <div css={[unselectable, mainTextStyles, divStyle]}>
<h3 css={thanksStyle}>{submitted_text}</h3>
- <div className={"return_button closed"}>
- <Link to="/" css={Navigation.returnStyles}>Return Home</Link>
+ <div css={navigationStyles}>
+ <Link to="/" css={returnButtonStyles}>Return Home</Link>
</div>
</div>
</div>