Added registration page and functionality, improved error handling, fixed some critical security problems

This commit is contained in:
Dmitriy Shishkov 2020-10-20 00:35:08 +05:00
parent b8450525a5
commit 6c60520aae
No known key found for this signature in database
GPG Key ID: D76D70029F55183E
10 changed files with 176 additions and 73 deletions

View File

@ -99,4 +99,12 @@ const CREATEFORM = gql`
}
`
export { LOGIN, FORM, USER, FORMSUBMIT, CREATEFORM }
const REGISTER = gql`
mutation Register($email: String!, $name: String!) {
register(email: $email, name: $name) {
success
}
}
`
export { LOGIN, FORM, USER, FORMSUBMIT, CREATEFORM, REGISTER }

View File

@ -1,11 +1,17 @@
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client'
import {
ApolloClient,
createHttpLink,
from,
InMemoryCache,
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'
export * from './defs'
const httpLink = createHttpLink({
uri: process.env.GRAPHQL_URL || process.env.REACT_APP_GRAPHQL_URL || undefined
uri:
process.env.GRAPHQL_URL || process.env.REACT_APP_GRAPHQL_URL || undefined,
// fetchOptions: {
// mode: 'no-cors'
// }
@ -17,14 +23,25 @@ const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
authorization: token ? `Bearer ${token}` : '',
},
}
})
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
link: from([errorLink, authLink, httpLink]),
cache: new InMemoryCache(),
})
export default client

View File

@ -9,6 +9,7 @@ import Authorize from './Authorize'
import CreateForm from './CreateForm'
import DoForm from './DoForm'
import Login from './Login'
import Register from './Register'
import UserPage from './UserPage'
const App: React.FC = () => {
@ -25,6 +26,7 @@ const App: React.FC = () => {
<Route path="/user" component={UserPage} />
<Route path="/form/:id" component={DoForm} />
<Route path="/create" component={CreateForm} />
<Route path="/register" component={Register} />
</Switch>
</Router>
</Context.Provider>

View File

@ -57,13 +57,16 @@ type UseFormCreatorHookTurple<T extends string> = [
handleQuestionTitleChange: HandleQuestionTitleChangeType
handleAnswerVariantChange: HandleAnswerVariantChangeType
addVariant: AddVariantType
}
},
() => void
]
const initialState = { title: '', questions: [] }
export const useFormCreator = <T extends string>(
formatQuestionsToSubmit: FormatQuestionsToSubmitType
): UseFormCreatorHookTurple<T> => {
const [form, setState] = useState<Form<T>>({ title: '', questions: [] })
const [form, setState] = useState<Form<T>>(initialState)
const [
doFormCreation,
@ -75,13 +78,11 @@ export const useFormCreator = <T extends string>(
},
})
const formSubmit: FormSubmitType = (e) => {
const formSubmit: FormSubmitType = async (e) => {
e.preventDefault()
console.log({
title: form.title,
questions: formatQuestionsToSubmit<T>(form.questions),
})
doFormCreation()
try {
await doFormCreation()
} catch (err) {}
}
const handleFormTitleChange: HandleFormTitleChangeType = (e) => {
@ -92,7 +93,7 @@ export const useFormCreator = <T extends string>(
const createQuestion: CreateQuestionType<T> = (type) => {
setState(({ title, questions }) => ({
title,
questions: questions.concat({ title: '', type, variants: [] }),
questions: questions.concat({ title: '', type, variants: [''] }),
}))
}
@ -131,7 +132,6 @@ export const useFormCreator = <T extends string>(
}
const addVariant: AddVariantType = (questionNumber) => {
console.log()
setState(({ title, questions }) => ({
title,
questions: questions.map((el, index) => ({
@ -142,6 +142,10 @@ export const useFormCreator = <T extends string>(
}))
}
const resetForm = () => {
setState(initialState)
}
return [
form,
[formSubmit, { submitData, submitError, submitLoading }],
@ -152,5 +156,6 @@ export const useFormCreator = <T extends string>(
handleAnswerVariantChange,
addVariant,
},
resetForm,
]
}

View File

@ -37,11 +37,17 @@ const CreateForm: React.FC = () => {
handleAnswerVariantChange,
addVariant,
},
resetForm,
] = useFormCreator<QuestionTypes>(formatQuestionsToSubmit)
return (
<>
<form onSubmit={formSubmit}>
<form
onSubmit={(e) => {
resetForm()
formSubmit(e)
}}
>
<label>
Title:
<input
@ -49,6 +55,7 @@ const CreateForm: React.FC = () => {
name="Title"
value={form.title}
onChange={handleFormTitleChange}
required
/>
</label>
@ -75,6 +82,7 @@ const CreateForm: React.FC = () => {
<li key={questionIndex}>
<p>{quesstion.type} question:</p>
<input
required
type="text"
name="questionTitle"
placeholder="Title"
@ -89,6 +97,7 @@ const CreateForm: React.FC = () => {
{quesstion.variants.map((variant, variantIndex) => (
<li key={variantIndex}>
<input
required
placeholder="Variant"
type="text"
value={variant}

View File

@ -2,8 +2,6 @@ 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 || []
)

View File

@ -8,7 +8,9 @@ import {
Form,
InputAnswer,
InputQuestion,
MutationFormSubmitArgs,
QueryFormArgs,
ServerAnswer,
} from '../../apollo/typeDefs.gen'
import Lists from './Lists'
@ -16,12 +18,19 @@ 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 } = useQuery<IFormQuery, QueryFormArgs>(FORM, {
const { data, error, loading, refetch: refetchForm } = useQuery<
IFormQuery,
QueryFormArgs
>(FORM, {
variables: { id },
skip: isNaN(id),
})
@ -29,7 +38,7 @@ const DoForm: React.FC = () => {
const [
doFormSubmit,
{ error: submitError, data: submitData, loading: submitLoading },
] = useMutation(FORMSUBMIT)
] = useMutation<IFormSubmitMutation, MutationFormSubmitArgs>(FORMSUBMIT)
const [answers, setAnswer] = useState<(InputAnswer | ChoiseAnswer)[]>([])
@ -57,8 +66,6 @@ const DoForm: React.FC = () => {
}
}, [data])
useEffect(() => console.log(answers), [answers])
if (isNaN(id)) return <Redirect to="/" />
if (loading) return <div>Loading...</div>
@ -66,24 +73,24 @@ const DoForm: React.FC = () => {
const { form } = data!
const handleSubmit = (e: FormEvent) => {
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
console.log('Submited form:', answers)
answers.forEach((el) => {
delete el.__typename
})
try {
const submitAnswers = JSON.stringify(answers)
console.log('Filtered answers: ', submitAnswers)
doFormSubmit({
await doFormSubmit({
variables: {
formId: id,
answers: submitAnswers,
},
})
await refetchForm()
} catch (err) {}
}
const answerChange = (num: number) => {
@ -108,6 +115,7 @@ const DoForm: React.FC = () => {
<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>
@ -141,6 +149,9 @@ const DoForm: React.FC = () => {
))}
</ul>
</div>
) : (
'No submissions yet'
)
) : (
<form onSubmit={handleSubmit}>
<ul>

View File

@ -3,11 +3,19 @@ import React, { ChangeEvent, FormEvent, useState } from 'react'
import { Redirect } from 'react-router-dom'
import { LOGIN } from '../../apollo'
import { MutationLoginArgs, ServerAnswer } from '../../apollo/typeDefs.gen'
interface ILoginMutation {
login: ServerAnswer
}
const Login: React.FC = () => {
const [email, setEmail] = useState<string>('')
const [doLogin, { error, data }] = useMutation(LOGIN)
const [doLogin, { error, data }] = useMutation<
ILoginMutation,
MutationLoginArgs
>(LOGIN)
const handleFormSubmit = async (e: FormEvent) => {
e.preventDefault()

View File

@ -0,0 +1,45 @@
import { useMutation } from '@apollo/client'
import React, { FormEvent } from 'react'
import { Redirect } from 'react-router-dom'
import { REGISTER } from '../../apollo'
import { MutationRegisterArgs, ServerAnswer } from '../../apollo/typeDefs.gen'
interface IRegisterMutation {
register: ServerAnswer
}
const Register: React.FC = () => {
const [doRegister, { data, loading, error }] = useMutation<
IRegisterMutation,
MutationRegisterArgs
>(REGISTER)
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
try {
await doRegister({
variables: {
email: formData.get('email') as string,
name: formData.get('name') as string,
},
})
} catch (err) {}
}
return (
<div>
Register
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="email" />
<input type="text" name="name" placeholder="username" />
{loading ? 'Loading...' : <input type="submit" value="Submit" />}
{error && error.message}
{data && data.register && data.register.success && <Redirect to="/" />}
</form>
</div>
)
}
export default Register

View File

@ -1,14 +1,14 @@
import { useQuery } from '@apollo/client'
import React from 'react'
import { USER } from '../../apollo'
import { User } from '../../apollo/typeDefs.gen'
import { QueryUserArgs, User } from '../../apollo/typeDefs.gen'
interface UserQuery {
interface IUserQuery {
user: User
}
const UserPage: React.FC = () => {
const { data, error, loading } = useQuery<UserQuery>(USER)
const { data, error, loading } = useQuery<IUserQuery, QueryUserArgs>(USER)
if (loading) return <p>Loading...</p>
if (error) return <p>{error.message}</p>