From 4b84cb15e867274d4809f7c21ee449944cb1ddaa Mon Sep 17 00:00:00 2001 From: Dm1tr1y147 Date: Wed, 14 Oct 2020 21:00:25 +0500 Subject: [PATCH] Added form submission --- .gitignore | 3 +- codegen.yml | 13 +++ package.json | 7 +- src/apollo/defs.ts | 30 +++++- src/apollo/typeDefs.gql | 84 +++++++++++++++ src/components/App.tsx | 29 +---- src/components/DoForm/Lists.tsx | 36 +++++++ src/components/DoForm/hooks.ts | 28 +++++ src/components/DoForm/index.tsx | 171 ++++++++++++++++++++++++++++++ src/components/UserPage/index.tsx | 16 +-- 10 files changed, 379 insertions(+), 38 deletions(-) create mode 100644 codegen.yml create mode 100644 src/apollo/typeDefs.gql create mode 100644 src/components/DoForm/Lists.tsx create mode 100644 src/components/DoForm/hooks.ts create mode 100644 src/components/DoForm/index.tsx diff --git a/.gitignore b/.gitignore index 6b26568..e00931e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -*.local* \ No newline at end of file +*.local* +*.gen* \ No newline at end of file diff --git a/codegen.yml b/codegen.yml new file mode 100644 index 0000000..2d64352 --- /dev/null +++ b/codegen.yml @@ -0,0 +1,13 @@ +overwrite: true +schema: 'src/apollo/typeDefs.gql' +documents: null +generates: + src/apollo/typeDefs.gen.ts: + config: + useIndexSignature: true + wrapFieldDefinitions: true + enumsAsTypes: true + declarationKind: 'interface' + + plugins: + - 'typescript' diff --git a/package.json b/package.json index e645900..a45f10e 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ }, "scripts": { "start": "react-scripts start", - "build": "react-scripts build" + "build": "react-scripts build", + "codegen": "graphql-codegen --config codegen.yml" }, "eslintConfig": { "extends": "react-app" @@ -34,5 +35,9 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@graphql-codegen/cli": "^1.17.10", + "@graphql-codegen/typescript": "^1.17.10" } } diff --git a/src/apollo/defs.ts b/src/apollo/defs.ts index 0d05073..0639a2d 100644 --- a/src/apollo/defs.ts +++ b/src/apollo/defs.ts @@ -11,7 +11,27 @@ const LOGIN = gql` const FORM = gql` query Form($id: Int!) { form(id: $id) { + author { + email + id + name + } + dateCreated id + questions { + number + ... on ChoisesQuestion { + title + type + variants { + text + } + } + ... on InputQuestion { + number + title + } + } title } } @@ -27,4 +47,12 @@ const USER = gql` } ` -export { LOGIN, FORM, USER } +const FORMSUBMIT = gql` + mutation FormSubmit($formId: Int!, $answers: String!) { + formSubmit(formId: $formId, answers: $answers) { + success + } + } +` + +export { LOGIN, FORM, USER, FORMSUBMIT } diff --git a/src/apollo/typeDefs.gql b/src/apollo/typeDefs.gql new file mode 100644 index 0000000..dbcb68d --- /dev/null +++ b/src/apollo/typeDefs.gql @@ -0,0 +1,84 @@ +type Query { + form(id: Int!): Form + forms: [Form!]! + user(id: Int): User +} + +type Mutation { + createForm(title: String!, questions: String!): serverAnswer + formSubmit(formId: Int!, answers: String!): serverAnswer + login(email: String!): serverAnswer + register(name: String!, email: String!): serverAnswer +} + +type Form { + author: User + dateCreated: String! + id: Int! + questions: [Question!]! + submissions: [FormSubmission!] + title: String! +} + +interface Question { + number: Int! + title: String! +} + +type ChoisesQuestion implements Question { + number: Int! + title: String! + type: ChoiseType! + variants: [Variant!]! +} + +type Variant { + text: String! +} + +type InputQuestion implements Question { + number: Int! + title: String! +} + +type FormSubmission { + answers: [Answer!]! + date: String! + id: Int! +} + +interface Answer { + type: AnswerType! +} + +type InputAnswer implements Answer { + type: AnswerType! + userInput: String +} + +type ChoiseAnswer implements Answer { + type: AnswerType! + userChoise: Int! +} + +enum ChoiseType { + CHECK + CHOOSE + SELECT +} + +enum AnswerType { + CHOISE + INPUT +} + +type User { + email: String! + forms: [Form!] + id: Int! + name: String! +} + +type serverAnswer { + success: Boolean! +} diff --git a/src/components/App.tsx b/src/components/App.tsx index a39064a..6d9c1bf 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -6,36 +6,10 @@ import client from '../apollo' import Context from '../context' import { useUser } from '../hooks' import Authorize from './Authorize' +import DoForm from './DoForm' import Login from './Login' import UserPage from './UserPage' -// const TestComponent: React.FC = () => { -// const { loading, error, data } = useQuery(FORM, { -// variables: { -// id: 1 -// } -// }) - -// const { user } = useContext(Context) - -// const [doLogin] = useMutation(LOGIN) - -// if (loading) return

Loading...

-// if (error) return

Error :(

- -// return ( -//
-// -// {user.id} -// {data.form.id} -//
-// ) -// } - const App: React.FC = () => { const userContext = useUser() @@ -48,6 +22,7 @@ const App: React.FC = () => { + diff --git a/src/components/DoForm/Lists.tsx b/src/components/DoForm/Lists.tsx new file mode 100644 index 0000000..b92697e --- /dev/null +++ b/src/components/DoForm/Lists.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +interface IProps { + variants: { text: string }[] + name: string + type: 'CHECK' | 'CHOOSE' + onChange: (num: number) => void +} + +const Lists: React.FC = ({ variants, name, type, onChange }) => { + const inputType = + (type === 'CHECK' && 'check') || (type === 'CHOOSE' && 'radio') || undefined + + return ( +
+ {variants.map((el, index) => ( + + ))} +
+ ) +} + +export default Lists diff --git a/src/components/DoForm/hooks.ts b/src/components/DoForm/hooks.ts new file mode 100644 index 0000000..00f5524 --- /dev/null +++ b/src/components/DoForm/hooks.ts @@ -0,0 +1,28 @@ +import { useState } from 'react' +import { ChoiseAnswer, InputAnswer } from '../../apollo/typeDefs.gen' + +export const useForm = (initialValue?: (InputAnswer | ChoiseAnswer)[]) => { + console.log(initialValue, 'Inside hook') + + 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] +} diff --git a/src/components/DoForm/index.tsx b/src/components/DoForm/index.tsx new file mode 100644 index 0000000..fe15db2 --- /dev/null +++ b/src/components/DoForm/index.tsx @@ -0,0 +1,171 @@ +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, + QueryFormArgs, +} from '../../apollo/typeDefs.gen' +import Lists from './Lists' + +interface IFormQuery { + form: Form +} + +const DoForm: React.FC = () => { + const { id: idString } = useParams<{ id: string }>() + + const id = parseInt(idString) + + const { data, error, loading } = useQuery(FORM, { + variables: { id }, + skip: isNaN(id), + }) + + const [ + doFormSubmit, + { error: submitError, data: submitData, loading: submitLoading }, + ] = useMutation(FORMSUBMIT) + + const [answers, setAnswer] = useState<(InputAnswer | ChoiseAnswer)[]>([]) + + const getInitialState = (data: IFormQuery) => { + if (data && data.form) { + return data.form.questions.flatMap( + (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]) + + useEffect(() => console.log(answers), [answers]) + + if (isNaN(id)) return + + if (loading) return
Loading...
+ if (error) return
{error.message}
+ + const { form } = data! + + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + console.log('Submited form:', answers) + + answers.forEach((el) => { + delete el.__typename + }) + + const submitAnswers = JSON.stringify(answers) + + console.log('Filtered answers: ', submitAnswers) + + doFormSubmit({ + variables: { + formId: id, + answers: submitAnswers, + }, + }) + } + + 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 ( +
+

{form.title}

+

{form.dateCreated}

+

{form.author?.name || 'No author'}

+ +
+
    + {form.questions.map((el: InputQuestion | ChoisesQuestion) => { + if (el.__typename === 'InputQuestion') + return ( +
  • + +
  • + ) + if (el.__typename === 'ChoisesQuestion') + return ( +
  • + +
  • + ) + return
  • Unknown question type
  • + })} +
+ {submitLoading ?

Uploading...

: } +
+ {submitError &&

{submitError.message}

} + {submitData && submitData.formSubmit && submitData.formSubmit.success && ( +

Successfully uploaded

+ )} +
+ ) +} + +export default DoForm diff --git a/src/components/UserPage/index.tsx b/src/components/UserPage/index.tsx index 2a62f60..4bd5c57 100644 --- a/src/components/UserPage/index.tsx +++ b/src/components/UserPage/index.tsx @@ -8,15 +8,15 @@ const UserPage: React.FC = () => { if (error) return

{error.message}

- console.log(Object.entries(data.user)) + const { name, email, id } = data.user - const user = Object.entries(data.user).map((el, index) => ( -
  • - {el[0]}: {el[1]} -
  • - )) - - return
      {user}
    + return ( +
    +

    Username: {name}

    +

    Email: {email}

    +

    User ID: {id}

    +
    + ) } export default UserPage