Massive code refactor, added initial states to form submission component (named DoForm)

This commit is contained in:
Dmitriy Shishkov 2020-11-06 22:38:28 +05:00
parent 1060ea4e41
commit eaba1665ce
No known key found for this signature in database
GPG Key ID: D76D70029F55183E
34 changed files with 528 additions and 486 deletions

View File

@ -40,6 +40,7 @@
"devDependencies": {
"@graphql-codegen/cli": "^1.17.10",
"@graphql-codegen/typescript": "^1.17.10",
"eslint-plugin-react-hooks": "^4.2.0",
"typescript-plugin-css-modules": "^2.7.0"
}
}

View File

@ -10,33 +10,13 @@
content="QuestionForm is an open source alternative to Google Forms"
/>
<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" />
<!--
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>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<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>
</html>

View File

@ -3,40 +3,30 @@ import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import client from '../apollo'
import Context from '../context'
import { useUser } from '../hooks'
import Authorize from './Authorize'
import CreateForm from './CreateForm'
import DoForm from './DoForm'
import Home from './Home'
import Login from './Login'
import Authorize from '../views/Authorize'
import CreateForm from '../views/CreateForm'
import DoForm from '../views/DoForm'
import Home from '../views/Home'
import Login from '../views/Login'
import Navbar from './Navbar'
import Register from './Register'
import UserPage from './UserPage'
import Register from '../views/Register'
const App: React.FC = () => {
const userContext = useUser()
return (
const App: React.FC = () => (
<div className="App">
<ApolloProvider client={client}>
<Context.Provider value={userContext}>
<Router>
<Navbar />
<Switch>
<Route path="/login" component={Login} />
<Route path="/authorize" component={Authorize} />
<Route path="/user" component={UserPage} />
<Route path="/form/:id" component={DoForm} />
<Route path="/create" component={CreateForm} />
<Route path="/register" component={Register} />
<Route exact path="/" component={Home} />
</Switch>
</Router>
</Context.Provider>
</ApolloProvider>
</div>
)
}
)
export default App

View File

@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'
import styles from './main.module.css'
interface props {
interface ICardProps {
title: string
subtitle?: string
icon?: React.Component
@ -11,7 +11,7 @@ interface props {
id: number
}
const Card: React.FC<props> = ({ title, subtitle, id }) => {
const Card: React.FC<ICardProps> = ({ title, subtitle, id }) => {
return (
<Link to={`/form/${id}`} className={styles.card}>
<h3>{title}</h3>

View File

@ -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]
}

View File

@ -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

View File

@ -1,13 +1,13 @@
import React from 'react'
interface IProps {
interface IListsProps {
variants: { text: string }[]
name: string
type: 'CHECK' | 'CHOOSE'
onChange: (num: number) => void
}
const Lists: React.FC<IProps> = ({ variants, name, type, onChange }) => {
const Lists: React.FC<IListsProps> = ({ variants, name, type, onChange }) => {
const inputType =
(type === 'CHECK' && 'check') || (type === 'CHOOSE' && 'radio') || undefined

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Link } from 'react-router-dom'
import styles from './main.module.css'
import logo from './logo.svg'

View 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 }
}

View 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

View 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[]

View 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 []
})

View 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

View File

@ -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

View File

@ -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

View File

@ -1,16 +1,4 @@
import { useCallback, useState } from 'react'
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 = () => {
return new URLSearchParams(useLocation().search)

15
src/types.ts Normal file
View 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
}

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Redirect } from 'react-router-dom'
import { useURLQuery } from '../../hooks'
const Authorize: React.FC = () => {

View File

@ -1,72 +1,27 @@
import { ApolloError, useMutation } from '@apollo/client'
import { ChangeEvent, FormEvent, useState } from 'react'
import { useMutation } from '@apollo/client'
import { useState } from 'react'
import { CREATEFORM } from '../../apollo'
import { MutationCreateFormArgs, ServerAnswer } from '../../apollo/typeDefs.gen'
type FormQuestion<T extends string> = {
title: string
type: T
variants: string[]
}
type Form<T extends string> = {
title: string
questions: FormQuestion<T>[]
}
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
]
import { MutationCreateFormArgs } from '../../apollo/typeDefs.gen'
import {
FormatQuestionsToSubmitFT,
UseFormCreatorHookTurpleT,
FormT,
ICreateFormMutation,
FormSubmitT,
HandleFormTitleChangeFT,
CreateQuestionFT,
HandleQuestionTitleChangeFT,
HandleAnswerVariantChangeFT,
AddVariantFT,
} from './types'
const initialState = { title: '', questions: [] }
export const useFormCreator = <T extends string>(
formatQuestionsToSubmit: FormatQuestionsToSubmitType
): UseFormCreatorHookTurple<T> => {
const [form, setState] = useState<Form<T>>(initialState)
formatQuestionsToSubmit: FormatQuestionsToSubmitFT
): UseFormCreatorHookTurpleT<T> => {
const [form, setState] = useState<FormT<T>>(initialState)
const [
doFormCreation,
@ -78,26 +33,26 @@ export const useFormCreator = <T extends string>(
},
})
const formSubmit: FormSubmitType = async (e) => {
const formSubmit: FormSubmitT = async (e) => {
e.preventDefault()
try {
await doFormCreation()
} catch (err) {}
}
const handleFormTitleChange: HandleFormTitleChangeType = (e) => {
const handleFormTitleChange: HandleFormTitleChangeFT = (e) => {
const title = e.currentTarget.value
setState((prev) => ({ ...prev, title }))
}
const createQuestion: CreateQuestionType<T> = (type) => {
const createQuestion: CreateQuestionFT<T> = (type) => {
setState(({ title, questions }) => ({
title,
questions: questions.concat({ title: '', type, variants: [''] }),
}))
}
const handleQuestionTitleChange: HandleQuestionTitleChangeType = (
const handleQuestionTitleChange: HandleQuestionTitleChangeFT = (
questionNumber,
e
) => {
@ -110,7 +65,7 @@ export const useFormCreator = <T extends string>(
}))
}
const handleAnswerVariantChange: HandleAnswerVariantChangeType = (
const handleAnswerVariantChange: HandleAnswerVariantChangeFT = (
questionNumber,
variantNumber,
e
@ -131,7 +86,7 @@ export const useFormCreator = <T extends string>(
}))
}
const addVariant: AddVariantType = (questionNumber) => {
const addVariant: AddVariantFT = (questionNumber) => {
setState(({ title, questions }) => ({
title,
questions: questions.map((el, index) => ({

View File

@ -1,30 +1,8 @@
import React from 'react'
import { FormatQuestionsToSubmitType, useFormCreator } from './hooks'
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
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,
})),
}
)
)
}
import { QuestionTypes } from './types'
import { useFormCreator } from './hooks'
import { creationsArray, formatQuestionsToSubmit } from './utils'
const CreateForm: React.FC = () => {
const [
@ -44,8 +22,8 @@ const CreateForm: React.FC = () => {
<>
<form
onSubmit={(e) => {
resetForm()
formSubmit(e)
resetForm()
}}
>
<label>
@ -126,10 +104,7 @@ const CreateForm: React.FC = () => {
</fieldset>
{submitLoading ? 'Loading...' : <input type="submit" value="Submit" />}
</form>
{submitData &&
submitData.createForm &&
submitData.createForm.success &&
'Successfully uploaded'}
{submitData?.createForm.success && 'Successfully uploaded'}
{submitError && submitError.message}
</>
)

View 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'

View 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,
})),
}
)
)

View File

@ -0,0 +1,7 @@
import { useParams } from 'react-router-dom'
export const useId = () => {
const { id: idString } = useParams<{ id: string }>()
return parseInt(idString)
}

View 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

View 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;
}

View File

@ -0,0 +1 @@
export type GetDateCreatedFT = (dateString: string) => string

View 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()}`
}

View File

@ -1,12 +1,12 @@
import React from 'react'
import { useQuery } from '@apollo/client'
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 { QueryUserArgs, User } from '../../apollo/typeDefs.gen'
import styles from './main.module.css'
import { Redirect } from 'react-router-dom'
interface IUserQuery {
user: User

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB