Massive code refactor, added initial states to form submission component (named DoForm)
This commit is contained in:
parent
1060ea4e41
commit
eaba1665ce
@ -40,6 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^1.17.10",
|
"@graphql-codegen/cli": "^1.17.10",
|
||||||
"@graphql-codegen/typescript": "^1.17.10",
|
"@graphql-codegen/typescript": "^1.17.10",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"typescript-plugin-css-modules": "^2.7.0"
|
"typescript-plugin-css-modules": "^2.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,33 +10,13 @@
|
|||||||
content="QuestionForm is an open source alternative to Google Forms"
|
content="QuestionForm is an open source alternative to Google Forms"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="index.css" />
|
<link rel="stylesheet" href="/index.css" />
|
||||||
<title>QuestionForm</title>
|
<title>QuestionForm</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
|
||||||
-->
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -3,40 +3,30 @@ import React from 'react'
|
|||||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
|
||||||
|
|
||||||
import client from '../apollo'
|
import client from '../apollo'
|
||||||
import Context from '../context'
|
import Authorize from '../views/Authorize'
|
||||||
import { useUser } from '../hooks'
|
import CreateForm from '../views/CreateForm'
|
||||||
import Authorize from './Authorize'
|
import DoForm from '../views/DoForm'
|
||||||
import CreateForm from './CreateForm'
|
import Home from '../views/Home'
|
||||||
import DoForm from './DoForm'
|
import Login from '../views/Login'
|
||||||
import Home from './Home'
|
|
||||||
import Login from './Login'
|
|
||||||
import Navbar from './Navbar'
|
import Navbar from './Navbar'
|
||||||
import Register from './Register'
|
import Register from '../views/Register'
|
||||||
import UserPage from './UserPage'
|
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => (
|
||||||
const userContext = useUser()
|
<div className="App">
|
||||||
|
<ApolloProvider client={client}>
|
||||||
return (
|
<Router>
|
||||||
<div className="App">
|
<Navbar />
|
||||||
<ApolloProvider client={client}>
|
<Switch>
|
||||||
<Context.Provider value={userContext}>
|
<Route path="/login" component={Login} />
|
||||||
<Router>
|
<Route path="/authorize" component={Authorize} />
|
||||||
<Navbar />
|
<Route path="/form/:id" component={DoForm} />
|
||||||
<Switch>
|
<Route path="/create" component={CreateForm} />
|
||||||
<Route path="/login" component={Login} />
|
<Route path="/register" component={Register} />
|
||||||
<Route path="/authorize" component={Authorize} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route path="/user" component={UserPage} />
|
</Switch>
|
||||||
<Route path="/form/:id" component={DoForm} />
|
</Router>
|
||||||
<Route path="/create" component={CreateForm} />
|
</ApolloProvider>
|
||||||
<Route path="/register" component={Register} />
|
</div>
|
||||||
<Route exact path="/" component={Home} />
|
)
|
||||||
</Switch>
|
|
||||||
</Router>
|
|
||||||
</Context.Provider>
|
|
||||||
</ApolloProvider>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
export default App
|
||||||
|
@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'
|
|||||||
|
|
||||||
import styles from './main.module.css'
|
import styles from './main.module.css'
|
||||||
|
|
||||||
interface props {
|
interface ICardProps {
|
||||||
title: string
|
title: string
|
||||||
subtitle?: string
|
subtitle?: string
|
||||||
icon?: React.Component
|
icon?: React.Component
|
||||||
@ -11,7 +11,7 @@ interface props {
|
|||||||
id: number
|
id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const Card: React.FC<props> = ({ title, subtitle, id }) => {
|
const Card: React.FC<ICardProps> = ({ title, subtitle, id }) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/form/${id}`} className={styles.card}>
|
<Link to={`/form/${id}`} className={styles.card}>
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { ChoiseAnswer, InputAnswer } from '../../apollo/typeDefs.gen'
|
|
||||||
|
|
||||||
export const useForm = (initialValue?: (InputAnswer | ChoiseAnswer)[]) => {
|
|
||||||
const [answers, setAnswer] = useState<(InputAnswer | ChoiseAnswer)[]>(
|
|
||||||
initialValue || []
|
|
||||||
)
|
|
||||||
|
|
||||||
const answerChange = (num: number) => {
|
|
||||||
return (value: number | string) => {
|
|
||||||
setAnswer((prev) => {
|
|
||||||
return prev.map((el, index) => {
|
|
||||||
if (index === num) {
|
|
||||||
if (el.__typename === 'ChoiseAnswer' && typeof value === 'number')
|
|
||||||
return { ...el, userChoise: value }
|
|
||||||
if (el.__typename === 'InputAnswer' && typeof value === 'string')
|
|
||||||
return { ...el, userInput: value }
|
|
||||||
}
|
|
||||||
return el
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [answers, answerChange]
|
|
||||||
}
|
|
@ -1,220 +0,0 @@
|
|||||||
import { useMutation, useQuery } from '@apollo/client'
|
|
||||||
import React, { FormEvent, useEffect, useState } from 'react'
|
|
||||||
import { Redirect, useParams } from 'react-router-dom'
|
|
||||||
import { FORM, FORMSUBMIT } from '../../apollo'
|
|
||||||
import {
|
|
||||||
ChoiseAnswer,
|
|
||||||
ChoisesQuestion,
|
|
||||||
Form,
|
|
||||||
InputAnswer,
|
|
||||||
InputQuestion,
|
|
||||||
MutationFormSubmitArgs,
|
|
||||||
QueryFormArgs,
|
|
||||||
ServerAnswer,
|
|
||||||
} from '../../apollo/typeDefs.gen'
|
|
||||||
import Lists from './Lists'
|
|
||||||
|
|
||||||
interface IFormQuery {
|
|
||||||
form: Form
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IFormSubmitMutation {
|
|
||||||
formSubmit: ServerAnswer
|
|
||||||
}
|
|
||||||
|
|
||||||
const DoForm: React.FC = () => {
|
|
||||||
const { id: idString } = useParams<{ id: string }>()
|
|
||||||
|
|
||||||
const id = parseInt(idString)
|
|
||||||
|
|
||||||
const { data, error, loading, refetch: refetchForm } = useQuery<
|
|
||||||
IFormQuery,
|
|
||||||
QueryFormArgs
|
|
||||||
>(FORM, {
|
|
||||||
variables: { id },
|
|
||||||
skip: isNaN(id),
|
|
||||||
})
|
|
||||||
|
|
||||||
const [
|
|
||||||
doFormSubmit,
|
|
||||||
{ error: submitError, data: submitData, loading: submitLoading },
|
|
||||||
] = useMutation<IFormSubmitMutation, MutationFormSubmitArgs>(FORMSUBMIT)
|
|
||||||
|
|
||||||
const [answers, setAnswer] = useState<(InputAnswer | ChoiseAnswer)[]>([])
|
|
||||||
|
|
||||||
const getInitialState = (data: IFormQuery) => {
|
|
||||||
if (data && data.form) {
|
|
||||||
return data.form.questions!.flatMap<InputAnswer | ChoiseAnswer>(
|
|
||||||
(el: InputQuestion | ChoisesQuestion) => {
|
|
||||||
if (el.__typename === 'ChoisesQuestion')
|
|
||||||
return [
|
|
||||||
{ __typename: 'ChoiseAnswer', type: 'CHOISE', userChoise: -1 },
|
|
||||||
]
|
|
||||||
if (el.__typename === 'InputQuestion')
|
|
||||||
return [{ __typename: 'InputAnswer', type: 'INPUT', userInput: '' }]
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
const initialState = getInitialState(data)
|
|
||||||
setAnswer(initialState)
|
|
||||||
}
|
|
||||||
}, [data])
|
|
||||||
|
|
||||||
if (isNaN(id)) return <Redirect to="/" />
|
|
||||||
|
|
||||||
if (loading) return <div>Loading...</div>
|
|
||||||
if (error) return <div>{error.message}</div>
|
|
||||||
|
|
||||||
const { form } = data!
|
|
||||||
|
|
||||||
const handleSubmit = async (e: FormEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
|
|
||||||
answers.forEach((el) => {
|
|
||||||
delete el.__typename
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const submitAnswers = JSON.stringify(answers)
|
|
||||||
|
|
||||||
await doFormSubmit({
|
|
||||||
variables: {
|
|
||||||
formId: id,
|
|
||||||
answers: submitAnswers,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await refetchForm()
|
|
||||||
} catch (err) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const answerChange = (num: number) => {
|
|
||||||
return (value: number | string) => {
|
|
||||||
setAnswer((prev) => {
|
|
||||||
return prev.map((el, index) => {
|
|
||||||
if (index === num) {
|
|
||||||
if (el.__typename === 'ChoiseAnswer' && typeof value === 'number')
|
|
||||||
return { ...el, userChoise: value }
|
|
||||||
if (el.__typename === 'InputAnswer' && typeof value === 'string')
|
|
||||||
return { ...el, userInput: value }
|
|
||||||
}
|
|
||||||
return el
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>{form.title}</h1>
|
|
||||||
<p>{form.dateCreated}</p>
|
|
||||||
<h3>{form.author?.name || 'No author'}</h3>
|
|
||||||
{form.submissions ? (
|
|
||||||
form.submissions.length > 0 ? (
|
|
||||||
<div>
|
|
||||||
<h1>Submission{form.submissions.length > 1 && 's'}:</h1>
|
|
||||||
<ul>
|
|
||||||
{form.submissions.map((submission, submissionIndex) => (
|
|
||||||
<li key={submissionIndex}>
|
|
||||||
<h2>
|
|
||||||
User:{' '}
|
|
||||||
{submission.user ? submission.user.name : 'No submitter'}
|
|
||||||
</h2>
|
|
||||||
<ul>
|
|
||||||
{submission.answers.map(
|
|
||||||
(answer: InputAnswer | ChoiseAnswer, answerIndex) => (
|
|
||||||
<li key={answerIndex}>
|
|
||||||
<h3>{form.questions![answerIndex].title}</h3>
|
|
||||||
{answer.__typename === 'ChoiseAnswer' && (
|
|
||||||
<h4>
|
|
||||||
{
|
|
||||||
(form.questions![
|
|
||||||
answerIndex
|
|
||||||
] as ChoisesQuestion).variants[
|
|
||||||
answer.userChoise
|
|
||||||
].text
|
|
||||||
}
|
|
||||||
</h4>
|
|
||||||
)}
|
|
||||||
{answer.__typename === 'InputAnswer' && (
|
|
||||||
<h4>{answer.userInput}</h4>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
'No submissions yet'
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<ul>
|
|
||||||
{form.questions!.map((el: InputQuestion | ChoisesQuestion) => {
|
|
||||||
if (el.__typename === 'InputQuestion')
|
|
||||||
return (
|
|
||||||
<li key={el.number}>
|
|
||||||
<label>
|
|
||||||
{el.title}
|
|
||||||
<input
|
|
||||||
onChange={(e) =>
|
|
||||||
answerChange(el.number)(e.currentTarget.value)
|
|
||||||
}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
if (el.__typename === 'ChoisesQuestion')
|
|
||||||
return (
|
|
||||||
<li key={el.number}>
|
|
||||||
<label>
|
|
||||||
{el.title}
|
|
||||||
{el.type === 'SELECT' ? (
|
|
||||||
<select
|
|
||||||
onChange={(e) => {
|
|
||||||
const selectValue = el.variants.findIndex(
|
|
||||||
(val) => val.text === e.currentTarget.value
|
|
||||||
)
|
|
||||||
answerChange(el.number)(selectValue)
|
|
||||||
}}
|
|
||||||
name={el.title}
|
|
||||||
>
|
|
||||||
{el.variants.map((option, index) => (
|
|
||||||
<option key={index}>{option.text}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
) : (
|
|
||||||
<Lists
|
|
||||||
variants={el.variants}
|
|
||||||
onChange={answerChange(el.number)}
|
|
||||||
name={el.title}
|
|
||||||
type={el.type}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
return <li>Unknown question type</li>
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
{submitLoading ? <p>Uploading...</p> : <input type="submit" />}
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
{submitError && <p>{submitError.message}</p>}
|
|
||||||
{submitData && submitData.formSubmit && submitData.formSubmit.success && (
|
|
||||||
<p>Successfully uploaded</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DoForm
|
|
@ -1,13 +1,13 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface IProps {
|
interface IListsProps {
|
||||||
variants: { text: string }[]
|
variants: { text: string }[]
|
||||||
name: string
|
name: string
|
||||||
type: 'CHECK' | 'CHOOSE'
|
type: 'CHECK' | 'CHOOSE'
|
||||||
onChange: (num: number) => void
|
onChange: (num: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const Lists: React.FC<IProps> = ({ variants, name, type, onChange }) => {
|
const Lists: React.FC<IListsProps> = ({ variants, name, type, onChange }) => {
|
||||||
const inputType =
|
const inputType =
|
||||||
(type === 'CHECK' && 'check') || (type === 'CHOOSE' && 'radio') || undefined
|
(type === 'CHECK' && 'check') || (type === 'CHOOSE' && 'radio') || undefined
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import styles from './main.module.css'
|
import styles from './main.module.css'
|
||||||
import logo from './logo.svg'
|
import logo from './logo.svg'
|
||||||
|
|
||||||
|
34
src/components/QuestionsForm/hooks.ts
Normal file
34
src/components/QuestionsForm/hooks.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useState, useCallback } from 'react'
|
||||||
|
import { AnswerT, QuestionT } from '../../types'
|
||||||
|
import { getInitialState } from './utils'
|
||||||
|
|
||||||
|
export const useForm = () => {
|
||||||
|
const [answers, setAnswers] = useState<AnswerT[]>([])
|
||||||
|
|
||||||
|
const changeAnswer = useCallback(
|
||||||
|
(num: number) => (value: number | string) =>
|
||||||
|
setAnswers((prev) =>
|
||||||
|
prev.map((el, index) => {
|
||||||
|
if (index === num) {
|
||||||
|
if (el.__typename === 'ChoiseAnswer' && typeof value === 'number')
|
||||||
|
return { ...el, userChoise: value }
|
||||||
|
if (el.__typename === 'InputAnswer' && typeof value === 'string')
|
||||||
|
return { ...el, userInput: value }
|
||||||
|
}
|
||||||
|
return el
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[setAnswers]
|
||||||
|
)
|
||||||
|
|
||||||
|
const setInitialState = useCallback(
|
||||||
|
(questions: QuestionT[]) => {
|
||||||
|
const state = getInitialState(questions)
|
||||||
|
console.log('Setting state')
|
||||||
|
setAnswers(state)
|
||||||
|
},
|
||||||
|
[setAnswers]
|
||||||
|
)
|
||||||
|
|
||||||
|
return { answers, changeAnswer, setInitialState }
|
||||||
|
}
|
118
src/components/QuestionsForm/index.tsx
Normal file
118
src/components/QuestionsForm/index.tsx
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import React, { FormEvent, useEffect } from 'react'
|
||||||
|
import { useMutation } from '@apollo/client'
|
||||||
|
|
||||||
|
import { MutationFormSubmitArgs } from '../../apollo/typeDefs.gen'
|
||||||
|
import { FORMSUBMIT } from '../../apollo'
|
||||||
|
import Lists from '../FormLists'
|
||||||
|
import { useForm } from './hooks'
|
||||||
|
import { QuestionT } from '../../types'
|
||||||
|
import { RefetchQuestionsFT, IFormSubmitMutation } from './types'
|
||||||
|
|
||||||
|
interface IQuestionsFormProps {
|
||||||
|
formId: number
|
||||||
|
questions: QuestionT[]
|
||||||
|
refetchQuestions: RefetchQuestionsFT
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuestionsForm: React.FC<IQuestionsFormProps> = ({
|
||||||
|
formId,
|
||||||
|
questions,
|
||||||
|
refetchQuestions,
|
||||||
|
}) => {
|
||||||
|
const [
|
||||||
|
doFormSubmit,
|
||||||
|
{ error: submitError, data: submitData, loading: submitLoading },
|
||||||
|
] = useMutation<IFormSubmitMutation, MutationFormSubmitArgs>(FORMSUBMIT)
|
||||||
|
|
||||||
|
const {
|
||||||
|
answers,
|
||||||
|
changeAnswer,
|
||||||
|
setInitialState: setInitialFromState,
|
||||||
|
} = useForm()
|
||||||
|
|
||||||
|
useEffect(() => setInitialFromState(questions), [
|
||||||
|
questions,
|
||||||
|
setInitialFromState,
|
||||||
|
])
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
answers.forEach((el) => {
|
||||||
|
delete el.__typename
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const submitAnswers = JSON.stringify(answers)
|
||||||
|
|
||||||
|
await doFormSubmit({
|
||||||
|
variables: {
|
||||||
|
formId,
|
||||||
|
answers: submitAnswers,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await refetchQuestions()
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<ul>
|
||||||
|
{questions.map((el: QuestionT) => {
|
||||||
|
if (el.__typename === 'InputQuestion')
|
||||||
|
return (
|
||||||
|
<li key={el.number}>
|
||||||
|
<label>
|
||||||
|
{el.title}
|
||||||
|
<input
|
||||||
|
onChange={(e) =>
|
||||||
|
changeAnswer(el.number)(e.currentTarget.value)
|
||||||
|
}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
if (el.__typename === 'ChoisesQuestion')
|
||||||
|
return (
|
||||||
|
<li key={el.number}>
|
||||||
|
<label>
|
||||||
|
{el.title}
|
||||||
|
{el.type === 'SELECT' ? (
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
const selectValue = el.variants.findIndex(
|
||||||
|
(val) => val.text === e.currentTarget.value
|
||||||
|
)
|
||||||
|
changeAnswer(el.number)(selectValue)
|
||||||
|
}}
|
||||||
|
name={el.title}
|
||||||
|
>
|
||||||
|
{el.variants.map((option, index) => (
|
||||||
|
<option key={index}>{option.text}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<Lists
|
||||||
|
variants={el.variants}
|
||||||
|
onChange={changeAnswer(el.number)}
|
||||||
|
name={el.title}
|
||||||
|
type={el.type}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
return <li>Unknown question type</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
{submitLoading ? <p>Uploading...</p> : <input type="submit" />}
|
||||||
|
</form>
|
||||||
|
{submitError && <p>{submitError.message}</p>}
|
||||||
|
{submitData?.formSubmit.success && <p>Successfully uploaded</p>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuestionsForm
|
14
src/components/QuestionsForm/types.ts
Normal file
14
src/components/QuestionsForm/types.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { ApolloQueryResult } from '@apollo/client'
|
||||||
|
|
||||||
|
import { IFormQuery, AnswerT, QuestionT } from '../../types'
|
||||||
|
import { QueryFormArgs, ServerAnswer } from '../../apollo/typeDefs.gen'
|
||||||
|
|
||||||
|
export type RefetchQuestionsFT = (
|
||||||
|
variables?: Partial<QueryFormArgs> | undefined
|
||||||
|
) => Promise<ApolloQueryResult<IFormQuery>>
|
||||||
|
|
||||||
|
export interface IFormSubmitMutation {
|
||||||
|
formSubmit: ServerAnswer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetInitialStateFT = (questions: QuestionT[]) => AnswerT[]
|
11
src/components/QuestionsForm/utils.ts
Normal file
11
src/components/QuestionsForm/utils.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { AnswerT } from '../../types'
|
||||||
|
import { GetInitialStateFT } from './types'
|
||||||
|
|
||||||
|
export const getInitialState: GetInitialStateFT = (questions) =>
|
||||||
|
questions.flatMap<AnswerT>((el) => {
|
||||||
|
if (el.__typename === 'ChoisesQuestion')
|
||||||
|
return [{ __typename: 'ChoiseAnswer', type: 'CHOISE', userChoise: -1 }]
|
||||||
|
if (el.__typename === 'InputQuestion')
|
||||||
|
return [{ __typename: 'InputAnswer', type: 'INPUT', userInput: '' }]
|
||||||
|
return []
|
||||||
|
})
|
56
src/components/SubmissionsList/index.tsx
Normal file
56
src/components/SubmissionsList/index.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
FormSubmission,
|
||||||
|
InputAnswer,
|
||||||
|
ChoiseAnswer,
|
||||||
|
ChoisesQuestion,
|
||||||
|
Question,
|
||||||
|
} from '../../apollo/typeDefs.gen'
|
||||||
|
|
||||||
|
interface ISubmissionListProps {
|
||||||
|
submissions: FormSubmission[]
|
||||||
|
questions: Question[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubmissionList: React.FC<ISubmissionListProps> = ({
|
||||||
|
submissions,
|
||||||
|
questions,
|
||||||
|
}) => {
|
||||||
|
return submissions.length > 0 ? (
|
||||||
|
<ul>
|
||||||
|
{submissions.map((submission, submissionIndex) => (
|
||||||
|
<li key={submissionIndex}>
|
||||||
|
<h2>
|
||||||
|
User: {submission.user ? submission.user.name : 'No submitter'}
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
{submission.answers.map(
|
||||||
|
(answer: InputAnswer | ChoiseAnswer, answerIndex) => (
|
||||||
|
<li key={answerIndex}>
|
||||||
|
<h3>{questions![answerIndex].title}</h3>
|
||||||
|
{answer.__typename === 'ChoiseAnswer' && (
|
||||||
|
<h4>
|
||||||
|
{
|
||||||
|
(questions![answerIndex] as ChoisesQuestion).variants[
|
||||||
|
answer.userChoise
|
||||||
|
].text
|
||||||
|
}
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
|
{answer.__typename === 'InputAnswer' && (
|
||||||
|
<h4>{answer.userInput}</h4>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<p>No submissions yet</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SubmissionList
|
@ -1,41 +0,0 @@
|
|||||||
import { useQuery } from '@apollo/client'
|
|
||||||
import React from 'react'
|
|
||||||
import { USER } from '../../apollo'
|
|
||||||
import { QueryUserArgs, User } from '../../apollo/typeDefs.gen'
|
|
||||||
|
|
||||||
interface IUserQuery {
|
|
||||||
user: User
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserPage: React.FC = () => {
|
|
||||||
const { data, error, loading } = useQuery<IUserQuery, QueryUserArgs>(USER)
|
|
||||||
if (loading) return <p>Loading...</p>
|
|
||||||
|
|
||||||
if (error) return <p>{error.message}</p>
|
|
||||||
|
|
||||||
const { user } = data!
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Username: {user.name}</h1>
|
|
||||||
<h3>Email: {user.email}</h3>
|
|
||||||
<p>User ID: {user.id}</p>
|
|
||||||
{user.forms && (
|
|
||||||
<>
|
|
||||||
<h2>Forms list</h2>
|
|
||||||
<ul>
|
|
||||||
{user.forms.map((form, index) => (
|
|
||||||
<li key={index}>
|
|
||||||
<a href={`http://localhost:3000/form/${form.id}`}>
|
|
||||||
{form.title}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserPage
|
|
@ -1,27 +0,0 @@
|
|||||||
import { createContext } from 'react'
|
|
||||||
|
|
||||||
export type UserType = {
|
|
||||||
id: number
|
|
||||||
email: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ContextType = {
|
|
||||||
user: UserType
|
|
||||||
setUser: (user: UserType) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const initialUser: UserType = {
|
|
||||||
email: '',
|
|
||||||
id: 0,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: ContextType = {
|
|
||||||
user: initialUser,
|
|
||||||
setUser: () => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Context = createContext<ContextType>(initialState)
|
|
||||||
|
|
||||||
export default Context
|
|
12
src/hooks.ts
12
src/hooks.ts
@ -1,16 +1,4 @@
|
|||||||
import { useCallback, useState } from 'react'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { ContextType, initialUser, UserType } from './context'
|
|
||||||
|
|
||||||
export const useUser = (): ContextType => {
|
|
||||||
const [user, internalSerUser] = useState<UserType>(initialUser)
|
|
||||||
|
|
||||||
const setUser = useCallback((user: UserType): void => {
|
|
||||||
internalSerUser(user)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { user, setUser }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useURLQuery = () => {
|
export const useURLQuery = () => {
|
||||||
return new URLSearchParams(useLocation().search)
|
return new URLSearchParams(useLocation().search)
|
||||||
|
15
src/types.ts
Normal file
15
src/types.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import {
|
||||||
|
InputQuestion,
|
||||||
|
ChoisesQuestion,
|
||||||
|
InputAnswer,
|
||||||
|
ChoiseAnswer,
|
||||||
|
Form,
|
||||||
|
} from './apollo/typeDefs.gen'
|
||||||
|
|
||||||
|
export type QuestionT = InputQuestion | ChoisesQuestion
|
||||||
|
|
||||||
|
export type AnswerT = InputAnswer | ChoiseAnswer
|
||||||
|
|
||||||
|
export interface IFormQuery {
|
||||||
|
form: Form
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Redirect } from 'react-router-dom'
|
import { Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
import { useURLQuery } from '../../hooks'
|
import { useURLQuery } from '../../hooks'
|
||||||
|
|
||||||
const Authorize: React.FC = () => {
|
const Authorize: React.FC = () => {
|
@ -1,72 +1,27 @@
|
|||||||
import { ApolloError, useMutation } from '@apollo/client'
|
import { useMutation } from '@apollo/client'
|
||||||
import { ChangeEvent, FormEvent, useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { CREATEFORM } from '../../apollo'
|
import { CREATEFORM } from '../../apollo'
|
||||||
import { MutationCreateFormArgs, ServerAnswer } from '../../apollo/typeDefs.gen'
|
import { MutationCreateFormArgs } from '../../apollo/typeDefs.gen'
|
||||||
|
import {
|
||||||
type FormQuestion<T extends string> = {
|
FormatQuestionsToSubmitFT,
|
||||||
title: string
|
UseFormCreatorHookTurpleT,
|
||||||
type: T
|
FormT,
|
||||||
variants: string[]
|
ICreateFormMutation,
|
||||||
}
|
FormSubmitT,
|
||||||
|
HandleFormTitleChangeFT,
|
||||||
type Form<T extends string> = {
|
CreateQuestionFT,
|
||||||
title: string
|
HandleQuestionTitleChangeFT,
|
||||||
questions: FormQuestion<T>[]
|
HandleAnswerVariantChangeFT,
|
||||||
}
|
AddVariantFT,
|
||||||
|
} from './types'
|
||||||
export type FormatQuestionsToSubmitType = <T extends string>(
|
|
||||||
questions: FormQuestion<T>[]
|
|
||||||
) => string
|
|
||||||
|
|
||||||
interface ICreateFormMutation {
|
|
||||||
createForm: ServerAnswer
|
|
||||||
}
|
|
||||||
|
|
||||||
type FormSubmitType = (e: FormEvent<HTMLFormElement>) => void
|
|
||||||
|
|
||||||
type HandleFormTitleChangeType = (e: ChangeEvent<HTMLInputElement>) => void
|
|
||||||
|
|
||||||
type CreateQuestionType<T extends string> = (type: T) => void
|
|
||||||
|
|
||||||
type HandleQuestionTitleChangeType = (
|
|
||||||
questionNumber: number,
|
|
||||||
e: ChangeEvent<HTMLInputElement>
|
|
||||||
) => void
|
|
||||||
|
|
||||||
type AddVariantType = (questionNumber: number) => void
|
|
||||||
|
|
||||||
type HandleAnswerVariantChangeType = (
|
|
||||||
questionNumber: number,
|
|
||||||
variantNumber: number,
|
|
||||||
e: ChangeEvent<HTMLInputElement>
|
|
||||||
) => void
|
|
||||||
|
|
||||||
type UseFormCreatorHookTurple<T extends string> = [
|
|
||||||
Form<T>,
|
|
||||||
[
|
|
||||||
FormSubmitType,
|
|
||||||
{
|
|
||||||
submitData: ICreateFormMutation | null | undefined
|
|
||||||
submitError: ApolloError | undefined
|
|
||||||
submitLoading: boolean
|
|
||||||
}
|
|
||||||
],
|
|
||||||
{
|
|
||||||
handleFormTitleChange: HandleFormTitleChangeType
|
|
||||||
addQuestion: CreateQuestionType<T>
|
|
||||||
handleQuestionTitleChange: HandleQuestionTitleChangeType
|
|
||||||
handleAnswerVariantChange: HandleAnswerVariantChangeType
|
|
||||||
addVariant: AddVariantType
|
|
||||||
},
|
|
||||||
() => void
|
|
||||||
]
|
|
||||||
|
|
||||||
const initialState = { title: '', questions: [] }
|
const initialState = { title: '', questions: [] }
|
||||||
|
|
||||||
export const useFormCreator = <T extends string>(
|
export const useFormCreator = <T extends string>(
|
||||||
formatQuestionsToSubmit: FormatQuestionsToSubmitType
|
formatQuestionsToSubmit: FormatQuestionsToSubmitFT
|
||||||
): UseFormCreatorHookTurple<T> => {
|
): UseFormCreatorHookTurpleT<T> => {
|
||||||
const [form, setState] = useState<Form<T>>(initialState)
|
const [form, setState] = useState<FormT<T>>(initialState)
|
||||||
|
|
||||||
const [
|
const [
|
||||||
doFormCreation,
|
doFormCreation,
|
||||||
@ -78,26 +33,26 @@ export const useFormCreator = <T extends string>(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const formSubmit: FormSubmitType = async (e) => {
|
const formSubmit: FormSubmitT = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
try {
|
try {
|
||||||
await doFormCreation()
|
await doFormCreation()
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFormTitleChange: HandleFormTitleChangeType = (e) => {
|
const handleFormTitleChange: HandleFormTitleChangeFT = (e) => {
|
||||||
const title = e.currentTarget.value
|
const title = e.currentTarget.value
|
||||||
setState((prev) => ({ ...prev, title }))
|
setState((prev) => ({ ...prev, title }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const createQuestion: CreateQuestionType<T> = (type) => {
|
const createQuestion: CreateQuestionFT<T> = (type) => {
|
||||||
setState(({ title, questions }) => ({
|
setState(({ title, questions }) => ({
|
||||||
title,
|
title,
|
||||||
questions: questions.concat({ title: '', type, variants: [''] }),
|
questions: questions.concat({ title: '', type, variants: [''] }),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleQuestionTitleChange: HandleQuestionTitleChangeType = (
|
const handleQuestionTitleChange: HandleQuestionTitleChangeFT = (
|
||||||
questionNumber,
|
questionNumber,
|
||||||
e
|
e
|
||||||
) => {
|
) => {
|
||||||
@ -110,7 +65,7 @@ export const useFormCreator = <T extends string>(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAnswerVariantChange: HandleAnswerVariantChangeType = (
|
const handleAnswerVariantChange: HandleAnswerVariantChangeFT = (
|
||||||
questionNumber,
|
questionNumber,
|
||||||
variantNumber,
|
variantNumber,
|
||||||
e
|
e
|
||||||
@ -131,7 +86,7 @@ export const useFormCreator = <T extends string>(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const addVariant: AddVariantType = (questionNumber) => {
|
const addVariant: AddVariantFT = (questionNumber) => {
|
||||||
setState(({ title, questions }) => ({
|
setState(({ title, questions }) => ({
|
||||||
title,
|
title,
|
||||||
questions: questions.map((el, index) => ({
|
questions: questions.map((el, index) => ({
|
@ -1,30 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { FormatQuestionsToSubmitType, useFormCreator } from './hooks'
|
import { QuestionTypes } from './types'
|
||||||
|
import { useFormCreator } from './hooks'
|
||||||
const creationsArray = [
|
import { creationsArray, formatQuestionsToSubmit } from './utils'
|
||||||
{ title: 'Check', type: 'CHECK', enabled: false },
|
|
||||||
{ title: 'Input', type: 'INPUT', enabled: true },
|
|
||||||
{ title: 'Choose', type: 'CHOOSE', enabled: true },
|
|
||||||
{ title: 'Select', type: 'SELECT', enabled: true },
|
|
||||||
] as const
|
|
||||||
|
|
||||||
type QuestionTypes = 'CHECK' | 'INPUT' | 'CHOOSE' | 'SELECT'
|
|
||||||
|
|
||||||
const formatQuestionsToSubmit: FormatQuestionsToSubmitType = (questions) => {
|
|
||||||
return JSON.stringify(
|
|
||||||
questions.map((question) =>
|
|
||||||
question.type === 'INPUT'
|
|
||||||
? { title: question.title }
|
|
||||||
: {
|
|
||||||
...question,
|
|
||||||
variants: question.variants.map((variant) => ({
|
|
||||||
text: variant,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateForm: React.FC = () => {
|
const CreateForm: React.FC = () => {
|
||||||
const [
|
const [
|
||||||
@ -44,8 +22,8 @@ const CreateForm: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
resetForm()
|
|
||||||
formSubmit(e)
|
formSubmit(e)
|
||||||
|
resetForm()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
@ -126,10 +104,7 @@ const CreateForm: React.FC = () => {
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
{submitLoading ? 'Loading...' : <input type="submit" value="Submit" />}
|
{submitLoading ? 'Loading...' : <input type="submit" value="Submit" />}
|
||||||
</form>
|
</form>
|
||||||
{submitData &&
|
{submitData?.createForm.success && 'Successfully uploaded'}
|
||||||
submitData.createForm &&
|
|
||||||
submitData.createForm.success &&
|
|
||||||
'Successfully uploaded'}
|
|
||||||
{submitError && submitError.message}
|
{submitError && submitError.message}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
64
src/views/CreateForm/types.ts
Normal file
64
src/views/CreateForm/types.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { FormEvent, ChangeEvent } from 'react'
|
||||||
|
import { ApolloError } from '@apollo/client'
|
||||||
|
|
||||||
|
import { ServerAnswer } from '../../apollo/typeDefs.gen'
|
||||||
|
|
||||||
|
export type FormQuestionT<T extends string> = {
|
||||||
|
title: string
|
||||||
|
type: T
|
||||||
|
variants: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormT<T extends string> = {
|
||||||
|
title: string
|
||||||
|
questions: FormQuestionT<T>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormatQuestionsToSubmitFT = <T extends string>(
|
||||||
|
questions: FormQuestionT<T>[]
|
||||||
|
) => string
|
||||||
|
|
||||||
|
export interface ICreateFormMutation {
|
||||||
|
createForm: ServerAnswer
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FormSubmitT = (e: FormEvent<HTMLFormElement>) => void
|
||||||
|
|
||||||
|
export type HandleFormTitleChangeFT = (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
|
|
||||||
|
export type CreateQuestionFT<T extends string> = (type: T) => void
|
||||||
|
|
||||||
|
export type HandleQuestionTitleChangeFT = (
|
||||||
|
questionNumber: number,
|
||||||
|
e: ChangeEvent<HTMLInputElement>
|
||||||
|
) => void
|
||||||
|
|
||||||
|
export type AddVariantFT = (questionNumber: number) => void
|
||||||
|
|
||||||
|
export type HandleAnswerVariantChangeFT = (
|
||||||
|
questionNumber: number,
|
||||||
|
variantNumber: number,
|
||||||
|
e: ChangeEvent<HTMLInputElement>
|
||||||
|
) => void
|
||||||
|
|
||||||
|
export type UseFormCreatorHookTurpleT<T extends string> = [
|
||||||
|
FormT<T>,
|
||||||
|
[
|
||||||
|
FormSubmitT,
|
||||||
|
{
|
||||||
|
submitData: ICreateFormMutation | null | undefined
|
||||||
|
submitError: ApolloError | undefined
|
||||||
|
submitLoading: boolean
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
handleFormTitleChange: HandleFormTitleChangeFT
|
||||||
|
addQuestion: CreateQuestionFT<T>
|
||||||
|
handleQuestionTitleChange: HandleQuestionTitleChangeFT
|
||||||
|
handleAnswerVariantChange: HandleAnswerVariantChangeFT
|
||||||
|
addVariant: AddVariantFT
|
||||||
|
},
|
||||||
|
() => void
|
||||||
|
]
|
||||||
|
|
||||||
|
export type QuestionTypes = 'CHECK' | 'INPUT' | 'CHOOSE' | 'SELECT'
|
22
src/views/CreateForm/utils.ts
Normal file
22
src/views/CreateForm/utils.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { FormatQuestionsToSubmitFT } from './types'
|
||||||
|
|
||||||
|
export const creationsArray = [
|
||||||
|
{ title: 'Check', type: 'CHECK', enabled: false },
|
||||||
|
{ title: 'Input', type: 'INPUT', enabled: true },
|
||||||
|
{ title: 'Choose', type: 'CHOOSE', enabled: true },
|
||||||
|
{ title: 'Select', type: 'SELECT', enabled: true },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const formatQuestionsToSubmit: FormatQuestionsToSubmitFT = (questions) =>
|
||||||
|
JSON.stringify(
|
||||||
|
questions.map((question) =>
|
||||||
|
question.type === 'INPUT'
|
||||||
|
? { title: question.title }
|
||||||
|
: {
|
||||||
|
...question,
|
||||||
|
variants: question.variants.map((variant) => ({
|
||||||
|
text: variant,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
7
src/views/DoForm/hooks.ts
Normal file
7
src/views/DoForm/hooks.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { useParams } from 'react-router-dom'
|
||||||
|
|
||||||
|
export const useId = () => {
|
||||||
|
const { id: idString } = useParams<{ id: string }>()
|
||||||
|
|
||||||
|
return parseInt(idString)
|
||||||
|
}
|
68
src/views/DoForm/index.tsx
Normal file
68
src/views/DoForm/index.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { useQuery } from '@apollo/client'
|
||||||
|
import React from 'react'
|
||||||
|
import { Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
|
import { FORM } from '../../apollo'
|
||||||
|
import { QueryFormArgs } from '../../apollo/typeDefs.gen'
|
||||||
|
import SubmissionList from '../../components/SubmissionsList'
|
||||||
|
import styles from './main.module.css'
|
||||||
|
import QuestionsForm from '../../components/QuestionsForm'
|
||||||
|
import { IFormQuery } from '../../types'
|
||||||
|
import { useId } from './hooks'
|
||||||
|
import { getDateCreated } from './utils'
|
||||||
|
|
||||||
|
const DoForm: React.FC = () => {
|
||||||
|
const id = useId()
|
||||||
|
|
||||||
|
const { data, error, loading, refetch: refetchForm } = useQuery<
|
||||||
|
IFormQuery,
|
||||||
|
QueryFormArgs
|
||||||
|
>(FORM, {
|
||||||
|
variables: { id },
|
||||||
|
skip: isNaN(id),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isNaN(id)) return <Redirect to="/" />
|
||||||
|
|
||||||
|
if (loading) return <div>Loading...</div>
|
||||||
|
if (error) return <div>{error.message}</div>
|
||||||
|
|
||||||
|
const { form } = data!
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<h1 className={styles.formTitle}>{form.title}</h1>
|
||||||
|
<p className={styles.dateCreated}>
|
||||||
|
Published on {getDateCreated(form.dateCreated)}
|
||||||
|
</p>
|
||||||
|
<p className={styles.author}>
|
||||||
|
{'by ' + form.author?.name || 'No author'}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.mainTop}></div>
|
||||||
|
{form.submissions ? (
|
||||||
|
<>
|
||||||
|
<h1>Submissions</h1>
|
||||||
|
<SubmissionList
|
||||||
|
submissions={form.submissions}
|
||||||
|
questions={form.questions!}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h1>Questions</h1>
|
||||||
|
<QuestionsForm
|
||||||
|
formId={id}
|
||||||
|
questions={data!.form.questions!}
|
||||||
|
refetchQuestions={refetchForm}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DoForm
|
48
src/views/DoForm/main.module.css
Normal file
48
src/views/DoForm/main.module.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: calc(100vh - 4rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 2.3rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template:
|
||||||
|
'title title' auto
|
||||||
|
'date author' auto / auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formTitle {
|
||||||
|
text-align: center;
|
||||||
|
grid-area: title;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dateCreated {
|
||||||
|
grid-area: date;
|
||||||
|
}
|
||||||
|
|
||||||
|
.author {
|
||||||
|
text-align: right;
|
||||||
|
grid-area: author;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 2.3rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 2.3rem;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainTop {
|
||||||
|
height: 2.3rem;
|
||||||
|
border-top-left-radius: 20px;
|
||||||
|
border-top-right-radius: 20px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
position: absolute;
|
||||||
|
top: -2.3rem;
|
||||||
|
left: 0rem;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
1
src/views/DoForm/types.ts
Normal file
1
src/views/DoForm/types.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type GetDateCreatedFT = (dateString: string) => string
|
7
src/views/DoForm/utils.ts
Normal file
7
src/views/DoForm/utils.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { GetDateCreatedFT } from './types'
|
||||||
|
|
||||||
|
export const getDateCreated: GetDateCreatedFT = (dateString) => {
|
||||||
|
const date = new Date(dateString)
|
||||||
|
|
||||||
|
return `${date.getMonth()}/${date.getDate()}/${date.getFullYear()}`
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import { generateFromString } from 'generate-avatar'
|
import { generateFromString } from 'generate-avatar'
|
||||||
|
import { Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
import Card from '../Card'
|
import Card from '../../components/Card'
|
||||||
import { USER } from '../../apollo'
|
import { USER } from '../../apollo'
|
||||||
import { QueryUserArgs, User } from '../../apollo/typeDefs.gen'
|
import { QueryUserArgs, User } from '../../apollo/typeDefs.gen'
|
||||||
import styles from './main.module.css'
|
import styles from './main.module.css'
|
||||||
import { Redirect } from 'react-router-dom'
|
|
||||||
|
|
||||||
interface IUserQuery {
|
interface IUserQuery {
|
||||||
user: User
|
user: User
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Loading…
x
Reference in New Issue
Block a user