diff options
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/api/client.ts | 6 | ||||
-rw-r--r-- | src/api/forms.ts | 52 | ||||
-rw-r--r-- | src/api/question.ts | 16 | ||||
-rw-r--r-- | src/components/FormListing.tsx | 10 | ||||
-rw-r--r-- | src/components/Loading.tsx | 17 | ||||
-rw-r--r-- | src/pages/FormPage.tsx | 14 | ||||
-rw-r--r-- | src/pages/LandingPage.tsx | 19 | ||||
-rw-r--r-- | src/tests/api/forms.test.ts | 6 | ||||
-rw-r--r-- | src/tests/components/FormListing.test.tsx | 31 | ||||
-rw-r--r-- | src/tests/pages/LandingPage.test.tsx | 21 | ||||
-rw-r--r-- | webpack.config.js | 3 | ||||
-rw-r--r-- | yarn.lock | 12 |
13 files changed, 140 insertions, 68 deletions
diff --git a/package.json b/package.json index ba58f62..e93d18d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@types/react-transition-group": "^4.4.0", "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", + "axios": "^0.21.0", "copy-webpack-plugin": "^6.2.1", "eslint": "^6.6.0", "eslint-config-react-app": "^5.2.1", diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..32c1993 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,6 @@ +import axios from "axios"; + + +export default axios.create({ + baseURL: process.env.BACKEND_URL +}) diff --git a/src/api/forms.ts b/src/api/forms.ts index a4a4981..724d6b7 100644 --- a/src/api/forms.ts +++ b/src/api/forms.ts @@ -1,49 +1,39 @@ import { Question, QuestionType } from "./question" +import ApiClient from "./client"; -export interface AllFormsForm { - title: string, - id: string, - description: string, - open: boolean +export enum FormFeatures { + Discoverable = "DISCOVERABLE", + RequiresLogin = "REQUIRES_LOGIN", + Open = "OPEN", + CollectEmail = "COLLECT_EMAIL", + DisableAntispam = "DISABLE_ANTISPAM" } -export interface Form extends AllFormsForm { - questions: Array<Question> +export interface Form { + id: string, + features: Array<FormFeatures>, + questions: Array<Question>, + name: string, + description: string } -export function getForms(): AllFormsForm[] { - return [ - { - title: "Ban Appeals", - id: "ban-appeals", - description: "Appealing bans from the Discord server", - open: true - }, - { - title: "Insights 2020", - id: "insights-2020", - description: "Insights about the Python Discord community", - open: false - }, - { - title: "Code Jam 2099 Sign Ups", - id: "code-jam-2099-sign-up", - description: "Signing up for Python Discord's millionth code jam!", - open: false - } - ] +export async function getForms(): Promise<Form[]> { + const resp = await ApiClient.get("forms/discoverable"); + return resp.data; } export function getForm(id: string): Promise<Form> { const data: Form = { - title: "Ban Appeals", + name: "Ban Appeals", id: "ban-appeals", description: "Appealing bans from the Discord server", - open: true, + features: [FormFeatures.Discoverable, FormFeatures.Open], questions: [ { + id: "how-spanish-are-you", name: "How Spanish are you?", - type: QuestionType.Text + type: QuestionType.ShortText, + data: {} } ] } diff --git a/src/api/question.ts b/src/api/question.ts index e051459..bdd6b99 100644 --- a/src/api/question.ts +++ b/src/api/question.ts @@ -1,11 +1,17 @@ export enum QuestionType { - Text, - Checkbox, - Radio, - Code + TextArea = "textarea", + Checkbox = "checkbox", + Radio = "radio", + Code = "code", + Select = "select", + ShortText = "short_text", + Range = "range", + Section = "section" } export interface Question { + id: string, name: string, - type: QuestionType + type: QuestionType, + data: { [key: string]: any } } diff --git a/src/components/FormListing.tsx b/src/components/FormListing.tsx index 2493608..c53cf67 100644 --- a/src/components/FormListing.tsx +++ b/src/components/FormListing.tsx @@ -9,15 +9,15 @@ import Tag from "./Tag"; import colors from "../colors"; -import { AllFormsForm } from "../api/forms"; +import { Form, FormFeatures } from "../api/forms"; interface FormListingProps { - form: AllFormsForm + form: Form } function FormListing({ form }: FormListingProps) { const listingStyle = css` - background-color: ${form.open ? colors.success : colors.darkButNotBlack}; + background-color: ${form.features.includes(FormFeatures.Open) ? colors.success : colors.darkButNotBlack}; width: 60%; padding: 20px; margin-top: 20px; @@ -39,13 +39,13 @@ function FormListing({ form }: FormListingProps) { let closedTag; - if (!form.open) { + if (!form.features.includes(FormFeatures.Open)) { closedTag = <Tag text="CLOSED" color={colors.error}/> }; return <Link to={`/form/${form.id}`} css={listingStyle}> <div> - <h3 css={{fontSize: "1.5em", marginBottom: "0"}}>{closedTag}{form.title} <FontAwesomeIcon icon={faArrowRight} css={{fontSize: "0.75em", paddingBottom: "1px"}}/></h3> + <h3 css={{fontSize: "1.5em", marginBottom: "0"}}>{closedTag}{form.name} <FontAwesomeIcon icon={faArrowRight} css={{fontSize: "0.75em", paddingBottom: "1px"}}/></h3> <p css={{marginTop: "5px"}}>{form.description}</p> </div> </Link> diff --git a/src/components/Loading.tsx b/src/components/Loading.tsx new file mode 100644 index 0000000..48dcdbc --- /dev/null +++ b/src/components/Loading.tsx @@ -0,0 +1,17 @@ +/** @jsx jsx */ +import { jsx } from "@emotion/core"; + +import { HashLoader } from "react-spinners"; + +import HeaderBar from "../components/HeaderBar"; + +function Loading() { + return <div> + <HeaderBar title={"Loading..."}/> + <div css={{display: "flex", justifyContent: "center"}}> + <HashLoader color="white"/> + </div> + </div> +} + +export default Loading; diff --git a/src/pages/FormPage.tsx b/src/pages/FormPage.tsx index a675f8e..ad746c7 100644 --- a/src/pages/FormPage.tsx +++ b/src/pages/FormPage.tsx @@ -2,26 +2,16 @@ import { jsx } from "@emotion/core"; import { Link } from "react-router-dom"; -import { HashLoader } from "react-spinners"; - import { useParams } from "react-router"; import HeaderBar from "../components/HeaderBar"; import { useEffect, useState } from "react"; import { Form, getForm } from "../api/forms"; +import Loading from "../components/Loading"; interface PathParams { id: string } -function Loading() { - return <div> - <HeaderBar title={"Loading..."}/> - <div css={{display: "flex", justifyContent: "center"}}> - <HashLoader color="white"/> - </div> - </div> -} - function FormPage() { const { id } = useParams<PathParams>(); @@ -38,7 +28,7 @@ function FormPage() { } return <div> - <HeaderBar title={form.title}/> + <HeaderBar title={form.name}/> <div css={{marginLeft: "20px"}}> <h1>{form.description}</h1> <Link to="/" css={{color: "white"}}>Return home</Link> diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 2e2bfd6..127f166 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,13 +1,28 @@ /** @jsx jsx */ import { css, jsx } from "@emotion/core"; +import { useEffect, useState } from "react"; import HeaderBar from "../components/HeaderBar"; import FormListing from "../components/FormListing"; -import { getForms } from "../api/forms"; +import { getForms, Form } from "../api/forms"; import OAuth2Button from "../components/OAuth2Button"; +import Loading from "../components/Loading"; function LandingPage() { + const [forms, setForms] = useState<Form[]>(); + + useEffect(() => { + const fetchForms = async () => { + setForms(await getForms()); + } + fetchForms(); + }); + + if (!forms) { + return <Loading/>; + } + return <div> <HeaderBar/> <div> @@ -22,7 +37,7 @@ function LandingPage() { <OAuth2Button/> - {getForms().map(form => ( + {forms.map(form => ( <FormListing key={form.id} form={form}/> ))} </div> diff --git a/src/tests/api/forms.test.ts b/src/tests/api/forms.test.ts index 7c851a7..6e63965 100644 --- a/src/tests/api/forms.test.ts +++ b/src/tests/api/forms.test.ts @@ -1,11 +1,7 @@ import { getForm, getForms } from "../../api/forms"; -test('fetch a list of all forms', () => { - expect(getForms()).toBeInstanceOf(Array); -}); - test('fetch a specific form', () => { - expect(getForm("ban-appeals")).resolves.toHaveProperty("title", "Ban Appeals") + expect(getForm("ban-appeals")).resolves.toHaveProperty("name", "Ban Appeals") }); export default null; diff --git a/src/tests/components/FormListing.test.tsx b/src/tests/components/FormListing.test.tsx index 5062a95..0afe10c 100644 --- a/src/tests/components/FormListing.test.tsx +++ b/src/tests/components/FormListing.test.tsx @@ -4,20 +4,37 @@ import '@testing-library/jest-dom/extend-expect'; import FormListing from "../../components/FormListing"; import { BrowserRouter as Router } from 'react-router-dom'; -import { AllFormsForm } from '../../api/forms'; +import { Form, FormFeatures } from '../../api/forms'; +import { QuestionType } from '../../api/question'; -const openFormListing: AllFormsForm = { - title: "Example form listing", +const openFormListing: Form = { + name: "Example form listing", id: "example-form-listing", description: "My form listing", - open: true + features: [FormFeatures.Discoverable, FormFeatures.Open], + questions: [ + { + "id": "my-question", + "name": "My question", + "type": QuestionType.ShortText, + "data": {} + } + ] } -const closedFormListing: AllFormsForm = { - title: "Example form listing", +const closedFormListing: Form = { + name: "Example form listing", id: "example-form-listing", description: "My form listing", - open: false + features: [FormFeatures.Discoverable], + questions: [ + { + "id": "what-should-i-ask", + "name": "What should I ask?", + "type": QuestionType.ShortText, + "data": {} + } + ] } test('renders form listing with specified title', () => { diff --git a/src/tests/pages/LandingPage.test.tsx b/src/tests/pages/LandingPage.test.tsx index ba32bab..23195bd 100644 --- a/src/tests/pages/LandingPage.test.tsx +++ b/src/tests/pages/LandingPage.test.tsx @@ -2,10 +2,31 @@ import React from 'react'; import { render } from '@testing-library/react'; import LandingPage from "../../pages/LandingPage"; +import * as forms from "../../api/forms"; import { BrowserRouter as Router } from "react-router-dom"; +import { QuestionType } from '../../api/question'; + +const testingForm: forms.Form = { + "id": "testing-form", + "name": "Testing Form", + "description": "Meant for testing", + "features": [forms.FormFeatures.Discoverable], + "questions": [ + { + "id": "my-question", + "name": "My Question", + "type": QuestionType.ShortText, + "data": {} + } + ] +} test('renders landing page', () => { + const setForms = jest.fn(() => [testingForm]); + Object.defineProperty(forms, "getForms", setForms); + const handleForms = jest.spyOn(React, "useState"); + handleForms.mockImplementation(_value => [[testingForm], setForms]); const { getByText } = render(<Router><LandingPage /></Router>); // If we rendered the headerbar we rendered the landing page. let headerBar = getByText(/Python Discord Forms/); diff --git a/webpack.config.js b/webpack.config.js index babea35..d2bf5aa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -88,7 +88,8 @@ module.exports = (env) => { REACT_APP_SHA: "development", REACT_APP_SENTRY_DSN: "https://[email protected]/1234", REACT_APP_BRANCH: "development", - REACT_APP_OAUTH2_CLIENT_ID: "0" + REACT_APP_OAUTH2_CLIENT_ID: "0", + BACKEND_URL: "https://forms-api.pythondiscord.com/" }), new HtmlWebpackPlugin({ inject: true, @@ -2749,6 +2749,13 @@ axios@^0.18.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +axios@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.0.tgz#26df088803a2350dff2c27f96fef99fe49442aca" + integrity sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw== + dependencies: + follow-redirects "^1.10.0" + axobject-query@^2.0.2: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -5311,6 +5318,11 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== +follow-redirects@^1.10.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" + integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== + for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" |