This commit is contained in:
2026-06-24 14:20:05 +02:00
commit 1c859d20c8
442 changed files with 25625 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.env
.next
.git
+2
View File
@@ -0,0 +1,2 @@
PORT=
NEXT_PUBLIC_BACKEND_URI=
+37
View File
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
+9
View File
@@ -0,0 +1,9 @@
FROM node:24.4.1-alpine3.22
WORKDIR /app
COPY . .
RUN npm ci
RUN npx next telemetry disable
RUN npm run build
RUN adduser -D appuser && chown -R appuser /app
USER appuser
CMD npm run start
+14
View File
@@ -0,0 +1,14 @@
'use client'
import { apollo } from '@/lib'
import { ApolloProvider } from '@apollo/client/react'
import { ChakraProvider } from '@chakra-ui/react'
export const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<ApolloProvider client={apollo}>
<ChakraProvider>
{children}
</ChakraProvider>
</ApolloProvider>
)
}
+1
View File
@@ -0,0 +1 @@
# Frontend for LitReddit 🔥
+64
View File
@@ -0,0 +1,64 @@
'use client'
import { LogoutDocument, MeDocument, PostsDocument } from '@/generated/graphql/graphql'
import { useMutation, useQuery } from '@apollo/client/react'
import { Link } from '@chakra-ui/next-js'
import { Box, Button, Flex, Text } from '@chakra-ui/react'
import { usePathname, useRouter } from 'next/navigation'
const NavBar: React.FC = () => {
const { data, loading, refetch } = useQuery(MeDocument)
const { refetch: refetchPosts } = useQuery(PostsDocument)
const [logout] = useMutation(LogoutDocument)
const router = useRouter()
const pathname = usePathname()
return (
<Flex bgColor='tan' paddingY={4} paddingX={6} ml='auto' minH='72px' alignItems='center'>
<Box mr='auto'>
<Link href='/'>
<Button>Home</Button>
</Link>
</Box>
<Box ml='auto'>
{
loading &&
<Text>Loading...</Text>
}
{
!loading && !data?.me &&
<>
<Link href='/login' mr={4}>
<Button>Login</Button>
</Link>
<Link href='/register' mr={4}>
<Button>Register</Button>
</Link>
</>
}
{
data?.me &&
<Flex alignItems='center'>
<Text mr={4}>Logged in as {data.me.username}!</Text>
<Button
onClick={async () => {
await logout()
await refetch() // Calling refetch will also refetch the data for any other components using useQuery(MeDocument)
await refetchPosts()
if (pathname == '/') {
router.refresh()
}
else {
router.push('/')
}
}}
>
Log out
</Button>
</Flex>
}
</Box>
</Flex>
)
}
export default NavBar
+49
View File
@@ -0,0 +1,49 @@
'use client'
import { InputField, TextareaField, Wrapper } from '@/components'
import { CreatePostDocument, PostInput, PostsDocument } from '@/generated/graphql/graphql'
import { useAuthenticate } from '@/hooks'
import { errorMapper } from '@/utils'
import { useMutation, useQuery } from '@apollo/client/react'
import { Box, Button, Flex } from '@chakra-ui/react'
import { Form, Formik } from 'formik'
import { useRouter } from 'next/navigation'
const CreatePostPage: React.FC = () => {
useAuthenticate()
const router = useRouter()
const [createPost] = useMutation(CreatePostDocument)
const { refetch } = useQuery(PostsDocument)
return (
<Wrapper variant='small'>
<Formik
initialValues={{ title: '', content: '' } as PostInput}
onSubmit={async (values, { setErrors }) => {
const response = await createPost({ variables: { input: values } })
const errors = response.data?.createPost.errors
if (errors) {
setErrors(errorMapper(errors))
}
else if (response.data?.createPost.post) {
refetch()
router.push(`/post/${response.data.createPost.post.id}`)
}
}}
>
{({ isSubmitting }) => (
<Form>
<Flex flexDir='column' justifyContent='center'>
<InputField name='title' label='Title' placeholder='title' />
<Box mt={4}>
<TextareaField name='content' label='Content' placeholder='content' />
</Box>
<Button type='submit' colorScheme='teal' mt={4} isLoading={isSubmitting} alignSelf='center'>Create</Button>
</Flex>
</Form>
)}
</Formik>
</Wrapper>
)
}
export default CreatePostPage
+65
View File
@@ -0,0 +1,65 @@
'use client'
import { FormSuccessMessage, InputField, TextareaField, Wrapper } from '@/components'
import { PostDocument, PostInput, UpdatePostDocument } from '@/generated/graphql/graphql'
import { useAuthenticate } from '@/hooks'
import { errorMapper } from '@/utils'
import { useMutation, useQuery } from '@apollo/client/react'
import { Box, Button, Flex, Text } from '@chakra-ui/react'
import { Form, Formik } from 'formik'
import { use, useState } from 'react'
interface Props {
params: Promise<{
id: string
}>
}
const EditPostPage: React.FC<Props> = ({ params }) => {
const { id } = use(params)
useAuthenticate()
const { data, loading, refetch } = useQuery(PostDocument, { variables: { id } })
const [updatePost] = useMutation(UpdatePostDocument)
const [showSuccessMessage, setShowSuccessMessage] = useState(false)
if (loading) {
return <Text>Loading...</Text>
}
return (
<Wrapper variant='small'>
<Formik
initialValues={{ title: data?.post?.title || '', content: data?.post?.content || '' } as PostInput}
onSubmit={async ({ title, content }, { setErrors }) => {
const response = await updatePost({ variables: { id, title: title as string, content } })
const errors = response.data?.updatePost.errors
if (errors) {
setErrors(errorMapper(errors))
}
else if (response.data?.updatePost.post) {
refetch()
setShowSuccessMessage(true)
setTimeout(() => {
setShowSuccessMessage(false)
}, 10000)
}
}}
>
{({ isSubmitting }) => (
<Form>
<Flex flexDir='column' justifyContent='center'>
<InputField name='title' label='Title' placeholder='title' />
<Box mt={4}>
<TextareaField name='content' label='Content' placeholder='content' />
</Box>
{showSuccessMessage && <FormSuccessMessage message='Post successfully updated!' />}
<Button type='submit' colorScheme='teal' mt={4} isLoading={isSubmitting} alignSelf='center'>Update</Button>
</Flex>
</Form>
)}
</Formik>
</Wrapper>
)
}
export default EditPostPage
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+52
View File
@@ -0,0 +1,52 @@
'use client'
import { FormErrorMessage, FormSuccessMessage, InputField, Wrapper } from '@/components'
import { ForgotPasswordDocument } from '@/generated/graphql/graphql'
import { errorMapper } from '@/utils'
import { useMutation } from '@apollo/client/react'
import { Button } from '@chakra-ui/react'
import { Form, Formik } from 'formik'
import { useState } from 'react'
const ForgotPasswordPage: React.FC = () => {
const [forgotPassword] = useMutation(ForgotPasswordDocument)
const [message, setMessage] = useState('')
const [messageType, setMessageType] = useState<'success' | 'error' | ''>('')
return (
<Wrapper variant='small'>
<Formik
initialValues={{ email: '' }}
onSubmit={async ({ email }, { setErrors }) => {
setMessage('')
setMessageType('')
const response = await forgotPassword({ variables: { email } })
const errors = response.data?.forgotPassword.errors
if (errors) {
setErrors(errorMapper(errors))
}
else if (response.data?.forgotPassword.message) {
setMessage(response.data.forgotPassword.message)
setMessageType(response.data.forgotPassword.messageType as any)
}
}}
>
{({ isSubmitting }) => (
<Form>
<InputField name='email' label="Enter your account's email to reset your password:" placeholder='email' />
{
(message && messageType == 'success') &&
<FormSuccessMessage message={message}/>
}
{
(message && messageType == 'error') &&
<FormErrorMessage message={message}/>
}
<Button type='submit' colorScheme='teal' mt={4} isLoading={isSubmitting}>Submit</Button>
</Form>
)}
</Formik>
</Wrapper>
)
}
export default ForgotPasswordPage
View File
+26
View File
@@ -0,0 +1,26 @@
import { Providers } from '@/Providers'
import { Flex } from '@chakra-ui/react'
import type { Metadata } from 'next'
import './globals.css'
import NavBar from './NavBar'
export const metadata: Metadata = {
title: 'LitReddit 🔥'
}
const RootLayout = ({ children, }: Readonly<{ children: React.ReactNode }>) => {
return (
<html lang='en'>
<body>
<Providers>
<NavBar />
<Flex justifyContent='center' width='100%' padding={4} minH='calc(100vh - 72px)'>
{children}
</Flex>
</Providers>
</body>
</html>
)
}
export default RootLayout
+69
View File
@@ -0,0 +1,69 @@
'use client'
import { InputField, Wrapper } from '@/components'
import { LoginDocument, MeDocument, PostsDocument } from '@/generated/graphql/graphql'
import { errorMapper } from '@/utils'
import { useMutation, useQuery } from '@apollo/client/react'
import { Link } from '@chakra-ui/next-js'
import { Box, Button } from '@chakra-ui/react'
import { Form, Formik } from 'formik'
import { useRouter, useSearchParams } from 'next/navigation'
import { Suspense } from 'react'
const Page: React.FC = () => {
const router = useRouter()
const searchParams = useSearchParams()
const { refetch } = useQuery(MeDocument)
const { refetch: refetchPosts } = useQuery(PostsDocument)
const [login] = useMutation(LoginDocument)
return (
<Wrapper variant='small'>
<Formik
initialValues={{ username: '', password: '' }}
onSubmit={async (values, { setErrors }) => {
const response = await login({ variables: { input: values } })
const errors = response.data?.login.errors
if (errors) {
setErrors(errorMapper(errors))
}
else if (response.data?.login.user) {
// Successful login
await refetch() // Refetch client-side
await refetchPosts()
const destination = searchParams.get('redirect')
if (destination) {
router.push(destination)
}
else {
router.push('/')
}
}
}}
>
{({ isSubmitting }) => (
<Form>
<InputField name='username' label='Username' placeholder='username' />
<Box mt={4}>
<InputField name='password' label='Password' placeholder='password' type='password' />
</Box>
<Box mt={4}>
<Link href='/forgot-password'>Forgot password?</Link>
</Box>
<Button type='submit' colorScheme='teal' mt={4} isLoading={isSubmitting}>Login</Button>
</Form>
)}
</Formik>
</Wrapper>
)
}
const LoginPage = () => {
return (
<Suspense>
<Page />
</Suspense>
)
}
export default LoginPage
View File
+58
View File
@@ -0,0 +1,58 @@
'use client'
import { Post, Wrapper } from '@/components'
import { PostsDocument } from '@/generated/graphql/graphql'
import { useQuery } from '@apollo/client/react'
import { Link } from '@chakra-ui/next-js'
import { Box, Button, Flex, Heading, Stack, Text } from '@chakra-ui/react'
import { useState } from 'react'
const Home = (): React.ReactNode => {
// https://www.apollographql.com/docs/react/pagination/core-api/
const { data, loading, fetchMore } = useQuery(PostsDocument)
const [doesntHaveMore, setDoesntHaveMore] = useState(false)
return (
<Box w='100%'>
<Wrapper>
<Flex w='100%'>
<Heading as='h3' size='lg'>LitReddit 🔥</Heading>
<Link href='/create-post' ml='auto'>
<Button>Create Post</Button>
</Link>
</Flex>
<Flex flexDir='column' alignItems='center'>
{
loading && !data?.posts &&
<Text>Loading...</Text>
}
<Stack spacing={4} mt={8} w='100%'>
{
data?.posts.map((p, idx) => <Post post={p} key={idx} />)
}
</Stack>
<Flex w='100%' mt={4}>
{
(data?.posts?.length && !doesntHaveMore) ?
<Button
onClick={async () => {
const result = await fetchMore({ variables: { cursor: data.posts[data.posts.length - 1].createdAt } })
if (result.data?.posts.length == 0) {
setDoesntHaveMore(true)
}
}}
m='auto'
>
More
</Button>
: <></>
}
</Flex>
</Flex>
</Wrapper>
</Box>
)
}
export default Home
+79
View File
@@ -0,0 +1,79 @@
'use client'
import { DeletePostDocument, PostQuery } from '@/generated/graphql/graphql'
import { useMutation } from '@apollo/client/react'
import { DeleteIcon, EditIcon } from '@chakra-ui/icons'
import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, Flex, IconButton, useDisclosure } from '@chakra-ui/react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useRef } from 'react'
interface Props {
data: PostQuery
}
export const ClientSection: React.FC<Props> = ({ data }) => {
const router = useRouter()
const { isOpen, onOpen, onClose } = useDisclosure()
const cancelRef: any = useRef(null)
const [deletePost] = useMutation(DeletePostDocument)
return (
<Flex mr='auto' alignItems='center' mt={4}>
<Link href={`/edit-post/${data?.post?.id}`}>
<IconButton
aria-label='edit-button'
icon={<EditIcon />}
mr={4}
/>
</Link>
<IconButton
aria-label='delete-button'
icon={<DeleteIcon />}
colorScheme='red'
onClick={onOpen}
/>
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
Delete Post
</AlertDialogHeader>
<AlertDialogBody>
Are you sure? You can't undo this action afterwards.
</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose}>
Cancel
</Button>
<Button
colorScheme='red'
onClick={async () => {
await deletePost({
variables: { id: data.post?.id! },
// https://stackoverflow.com/questions/63192774/apollo-client-delete-item-from-cache
// https://www.apollographql.com/docs/react/caching/garbage-collection/#cacheevict
update: cache => {
const normalizedId = cache.identify({ id: data.post?.id!, __typename: 'Post' })
cache.evict({ id: normalizedId })
cache.gc()
}
})
router.refresh()
}}
ml={3}
>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</Flex>
)
}
+8
View File
@@ -0,0 +1,8 @@
import { Text } from '@chakra-ui/react'
const Loading: React.FC = () => {
return (
<Text>Loading...</Text>
)
}
export default Loading
+42
View File
@@ -0,0 +1,42 @@
import { Wrapper } from '@/components'
import { SESSION_COOKIE_NAME } from '@/constants'
import { MeDocument, PostDocument } from '@/generated/graphql/graphql'
import { createApolloClient } from '@/lib'
import { Heading, Text } from '@chakra-ui/react'
import { cookies } from 'next/headers'
import { ClientSection } from './ClientSection'
interface Props {
params: Promise<{
id: string
}>
}
const PostPage: React.FC<Props> = async ({ params }) => {
const { id } = await params
const cookieStore = await cookies()
const cookie = cookieStore.get(SESSION_COOKIE_NAME)?.value
const apollo = await createApolloClient(cookie)
const { data } = await apollo.query({ query: PostDocument, variables: { id } })
const { data: meData } = await apollo.query({ query: MeDocument })
return (
<Wrapper>
{
data?.post ?
<>
<Heading as='h3' size='md'>{data.post.title}</Heading>
<Text>Posted by {data.post.author.username}</Text>
<Text mt={4}>{data.post.content}</Text>
{
data?.post?.authorID == meData?.me?.id &&
<ClientSection data={data} />
}
</>
:
<>Post not found!</>
}
</Wrapper>
)
}
export default PostPage
+48
View File
@@ -0,0 +1,48 @@
'use client'
import { InputField, Wrapper } from '@/components'
import { MeDocument, RegisterDocument } from '@/generated/graphql/graphql'
import { errorMapper } from '@/utils'
import { useMutation, useQuery } from '@apollo/client/react'
import { Box, Button } from '@chakra-ui/react'
import { Form, Formik } from 'formik'
import { useRouter } from 'next/navigation'
const RegisterPage: React.FC = () => {
const router = useRouter()
const { refetch } = useQuery(MeDocument)
const [register] = useMutation(RegisterDocument)
return (
<Wrapper variant='small'>
<Formik
initialValues={{ username: '', password: '' }}
onSubmit={async (values, { setErrors }) => {
const response = await register({ variables: { input: values } })
const errors = response.data?.register.errors
if (errors) {
setErrors(errorMapper(errors))
}
else if (response.data?.register.user) {
// Successful register
await refetch()
router.push('/')
}
}}
>
{({ isSubmitting }) => (
<Form>
<InputField name='email' label='Email' placeholder='email' />
<Box mt={4}>
<InputField name='username' label='Username' placeholder='username' />
</Box>
<Box mt={4}>
<InputField name='password' label='Password' placeholder='password' type='password' />
</Box>
<Button type='submit' colorScheme='teal' mt={4} isLoading={isSubmitting}>Register</Button>
</Form>
)}
</Formik>
</Wrapper>
)
}
export default RegisterPage
@@ -0,0 +1,36 @@
import { PasswordResetForm } from '@/components'
import { SESSION_COOKIE_NAME } from '@/constants'
import { CheckResetPasswordTokenDocument } from '@/generated/graphql/graphql'
import { createApolloClient } from '@/lib'
import { Text } from '@chakra-ui/react'
import { cookies } from 'next/headers'
interface Props {
params: Promise<{
token: string
}>
}
export const dynamic = 'force-dynamic'
const ResetPasswordPage: React.FC<Props> = async ({ params }) => {
const { token } = await params
const cookieStore = await cookies()
const cookie = cookieStore.get(SESSION_COOKIE_NAME)?.value
const apollo = await createApolloClient(cookie)
const { data, error } = await apollo.query({ query: CheckResetPasswordTokenDocument, variables: { token } })
if (error || data === undefined) {
console.log(error)
return <Text>An error has occured. Please try again later.</Text>
}
else if (!data.checkResetPasswordToken) {
return <Text>Invalid token!</Text>
}
else {
return <PasswordResetForm token={token}/>
}
}
export default ResetPasswordPage
+17
View File
@@ -0,0 +1,17 @@
'use client'
import { CloseIcon } from '@chakra-ui/icons'
import { Flex, Text } from '@chakra-ui/react'
interface Props {
message: string
}
export const FormErrorMessage: React.FC<Props> = (props) => {
const { message } = props
return (
<Flex alignItems='center' color='red' mt={4} fontSize='0.875rem' >
<CloseIcon color='red' mr={2.5} fontSize='0.725rem'/>
<Text color='red'>{message}</Text>
</Flex>
)
}
@@ -0,0 +1,17 @@
'use client'
import { CheckIcon } from '@chakra-ui/icons'
import { Flex, Text } from '@chakra-ui/react'
interface Props {
message: string
}
export const FormSuccessMessage: React.FC<Props> = (props) => {
const { message } = props
return (
<Flex alignItems='center' color='green-500' mt={4} fontSize='0.875rem'>
<CheckIcon color='green.700' fontSize='0.875rem' mr={2} />
<Text color='green'>{message}</Text>
</Flex>
)
}
+18
View File
@@ -0,0 +1,18 @@
'use client'
import { FormControl, FormErrorMessage, FormLabel, Input } from '@chakra-ui/react'
import { useField } from 'formik'
type Props = React.InputHTMLAttributes<HTMLInputElement> & { name: string, label: string, isTextArea?: boolean }
export const InputField: React.FC<Props> = ({size: _, isTextArea=false, ...props}) => {
const [field, { error }] = useField(props)
const { label, placeholder } = props
return (
<FormControl isInvalid={!!error}>
<FormLabel htmlFor={field.name}>{label}</FormLabel>
<Input {...field} {...props} id={field.name} placeholder={placeholder} />
{error ? <FormErrorMessage>{error}</FormErrorMessage> : null}
</FormControl>
)
}
+60
View File
@@ -0,0 +1,60 @@
'use client'
import { FormErrorMessage, FormSuccessMessage, InputField, Wrapper } from '@/components'
import { MeDocument, ResetPasswordDocument } from '@/generated/graphql/graphql'
import { errorMapper } from '@/utils'
import { useMutation, useQuery } from '@apollo/client/react'
import { Button } from '@chakra-ui/react'
import { Form, Formik } from 'formik'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
interface Props {
token: string
}
export const PasswordResetForm: React.FC<Props> = ({ token }) => {
const router = useRouter()
const [resetPassword] = useMutation(ResetPasswordDocument)
const { refetch } = useQuery(MeDocument)
const [message, setMessage] = useState('')
const [messageType, setMessageType] = useState<'success' | 'error' | ''>('')
return (
<Wrapper variant='small'>
<Formik
initialValues={{ newPassword: '' }}
onSubmit={async ({ newPassword }, { setErrors }) => {
const response = await resetPassword({ variables: { newPassword, token } })
const errors = response.data?.resetPassword.errors
if (errors) {
setErrors(errorMapper(errors))
}
else if (response.data?.resetPassword.message) {
setMessage(response.data.resetPassword.message)
setMessageType(response.data.resetPassword.messageType as any)
if (response.data.resetPassword.messageType == 'success') {
await refetch()
setTimeout(() => { router.push('/login') }, 1500)
}
}
}}
>
{({ isSubmitting }) => (
<Form>
<InputField name='newPassword' label='Enter your new password:' placeholder='password' type='password' />
{
(message && messageType == 'success') &&
<FormSuccessMessage message={message}/>
}
{
(message && messageType == 'error') &&
<FormErrorMessage message={message}/>
}
<Button type='submit' colorScheme='teal' mt={4} isLoading={isSubmitting}>Submit</Button>
</Form>
)}
</Formik>
</Wrapper>
)
}
+105
View File
@@ -0,0 +1,105 @@
'use client'
import { DeletePostDocument, DownvoteDocument, MeDocument, PostDocument, Post as PostType, RemoveDownvoteDocument, RemoveUpvoteDocument, UpvoteDocument } from '@/generated/graphql/graphql'
import { useMutation, useQuery } from '@apollo/client/react'
import { ArrowDownIcon, ArrowUpIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons'
import { Box, Flex, Heading, IconButton, Text } from '@chakra-ui/react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
interface Props {
post: PostType
}
export const Post: React.FC<Props> = ({ post: p }) => {
const { data } = useQuery(MeDocument)
const router = useRouter()
const { refetch } = useQuery(PostDocument, { returnPartialData: false, variables: { id: p.id } })
const [upvote, { loading: upvoting }] = useMutation(UpvoteDocument)
const [downvote, { loading: downvoting }] = useMutation(DownvoteDocument)
const [removeUpvote, { loading: removingUpvote }] = useMutation(RemoveUpvoteDocument)
const [removeDownvote, { loading: removingDownvote }] = useMutation(RemoveDownvoteDocument)
const [deletePost, { loading: deletingPost }] = useMutation(DeletePostDocument)
return (
<Flex key={p.id} p={5} borderWidth='1px'>
<Flex flexDir='column' mr='4' justifyContent='space-between' alignItems='center'>
<IconButton
aria-label='upvote-button'
icon={<ArrowUpIcon />}
color={p.upvoted ? 'green' : ''}
isLoading={upvoting || removingUpvote}
onClick={async () => {
if (!data?.me) {
router.push('/login')
}
if (p.upvoted) {
await removeUpvote({ variables: { postID: p.id } })
// Apollo Client automatically refetches one single post and merge it with the cache of previous posts and updating the result of useQuery(PostsDocument), so we don't need to refetch every post.
await refetch()
}
else {
await upvote({ variables: { postID: p.id } })
await refetch()
}
}}
/>
<Text marginY='2'>{p.points}</Text>
<IconButton
aria-label='downvote-button'
icon={<ArrowDownIcon />}
color={p.downvoted ? 'red' : ''}
isLoading={downvoting || removingDownvote}
onClick={async () => {
if (!data?.me) {
router.push('/login')
}
if (p.downvoted) {
await removeDownvote({ variables: { postID: p.id } })
await refetch()
}
else {
await downvote({ variables: { postID: p.id } })
await refetch()
}
}}
/>
</Flex>
<Box>
<Link href={`/post/${p.id}`}>
<Heading as='h3' size='md'>{p.title}</Heading>
</Link>
<Text>Posted by {p.author.username}</Text>
<Text mt={4}>{p.snippet}</Text>
</Box>
{
p.authorID == data?.me?.id &&
<Flex flexDir='column' ml='auto' justifyContent='space-between' alignItems='center'>
<Link href={`/edit-post/${p.id}`}>
<IconButton
aria-label='edit-button'
icon={<EditIcon />}
/>
</Link>
<IconButton
aria-label='delete-button'
icon={<DeleteIcon />}
colorScheme='red'
isLoading={deletingPost}
onClick={async () => {
await deletePost({
variables: { id: p.id },
// https://stackoverflow.com/questions/63192774/apollo-client-delete-item-from-cache
// https://www.apollographql.com/docs/react/caching/garbage-collection/#cacheevict
update: cache => {
const normalizedId = cache.identify({ id: p.id, __typename: 'Post' })
cache.evict({ id: normalizedId })
cache.gc()
}
})
}}
/>
</Flex>
}
</Flex>
)
}
+10
View File
@@ -0,0 +1,10 @@
'use client'
import { Button, PropsOf } from '@chakra-ui/react'
import { useRouter } from 'next/navigation'
export const RefetchButton: React.FC<PropsOf<typeof Button>> = (props) => {
const router = useRouter()
return (
<Button onClick={() => { router.refresh() }} {...props}>Refetch</Button>
)
}
+36
View File
@@ -0,0 +1,36 @@
'use client'
import { FormControl, FormErrorMessage, FormLabel, Textarea } from '@chakra-ui/react'
import autosize from 'autosize'
import { useField } from 'formik'
import { useRef, useEffect } from 'react'
type Props = React.TextareaHTMLAttributes<HTMLTextAreaElement> & { name: string, label: string }
export const TextareaField: React.FC<Props> = (props) => {
const [field, { error }] = useField(props)
const { label, placeholder } = props
// https://github.com/chakra-ui/chakra-ui/issues/670
const ref: any = useRef(null)
useEffect(() => {
const current = ref.current
autosize(current)
return () => {
autosize.destroy(current)
}
}, [])
return (
<FormControl isInvalid={!!error}>
<FormLabel htmlFor={field.name}>{label}</FormLabel>
<Textarea
{...field}
{...props}
ref={ref}
id={field.name}
placeholder={placeholder}
/>
{error ? <FormErrorMessage>{error}</FormErrorMessage> : null}
</FormControl>
)
}
+14
View File
@@ -0,0 +1,14 @@
import { Box } from '@chakra-ui/react'
interface Props {
children: React.ReactNode
variant?: 'small' | 'regular'
}
export const Wrapper: React.FC<Props> = ({ children, variant = 'regular' }) => {
return (
<Box w='100%' maxW={variant == 'small' ? 400 : 800} mt={8} mx='auto'>
{children}
</Box>
)
}
+8
View File
@@ -0,0 +1,8 @@
export * from './Wrapper'
export * from './InputField'
export * from './PasswordResetForm'
export * from './TextareaField'
export * from './RefetchButton'
export * from './Post'
export * from './FormSuccessMessage'
export * from './FormErrorMessage'
+2
View File
@@ -0,0 +1,2 @@
export const __prod__ = process.env.NODE_ENV ==='production'
export const SESSION_COOKIE_NAME = 'qid'
+9
View File
@@ -0,0 +1,9 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NEXT_PUBLIC_BACKEND_URI: string;
}
}
}
export {}
@@ -0,0 +1,87 @@
/* eslint-disable */
import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
import { FragmentDefinitionNode } from 'graphql';
import { Incremental } from './graphql';
export type FragmentType<TDocumentType extends DocumentTypeDecoration<any, any>> = TDocumentType extends DocumentTypeDecoration<
infer TType,
any
>
? [TType] extends [{ ' $fragmentName'?: infer TKey }]
? TKey extends string
? { ' $fragmentRefs'?: { [key in TKey]: TType } }
: never
: never
: never;
// return non-nullable if `fragmentType` is non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>>
): TType;
// return nullable if `fragmentType` is undefined
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | undefined
): TType | undefined;
// return nullable if `fragmentType` is nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null
): TType | null;
// return nullable if `fragmentType` is nullable or undefined
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | null | undefined
): TType | null | undefined;
// return array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: Array<FragmentType<DocumentTypeDecoration<TType, any>>>
): Array<TType>;
// return array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: Array<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): Array<TType> | null | undefined;
// return readonly array of non-nullable if `fragmentType` is array of non-nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>>
): ReadonlyArray<TType>;
// return readonly array of nullable if `fragmentType` is array of nullable
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): ReadonlyArray<TType> | null | undefined;
export function useFragment<TType>(
_documentNode: DocumentTypeDecoration<TType, any>,
fragmentType: FragmentType<DocumentTypeDecoration<TType, any>> | Array<FragmentType<DocumentTypeDecoration<TType, any>>> | ReadonlyArray<FragmentType<DocumentTypeDecoration<TType, any>>> | null | undefined
): TType | Array<TType> | ReadonlyArray<TType> | null | undefined {
return fragmentType as any;
}
export function makeFragmentData<
F extends DocumentTypeDecoration<any, any>,
FT extends ResultOf<F>
>(data: FT, _fragment: F): FragmentType<F> {
return data as FragmentType<F>;
}
export function isFragmentReady<TQuery, TFrag>(
queryNode: DocumentTypeDecoration<TQuery, any>,
fragmentNode: TypedDocumentNode<TFrag>,
data: FragmentType<TypedDocumentNode<Incremental<TFrag>, any>> | null | undefined
): data is FragmentType<typeof fragmentNode> {
const deferredFields = (queryNode as { __meta__?: { deferredFields: Record<string, (keyof TFrag)[]> } }).__meta__
?.deferredFields;
if (!deferredFields) return true;
const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined;
const fragName = fragDef?.name?.value;
const fields = (fragName && deferredFields[fragName]) || [];
return fields.length > 0 && fields.every(field => data && field in data);
}
+142
View File
@@ -0,0 +1,142 @@
/* eslint-disable */
import * as types from './graphql';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
/**
* Map of all GraphQL operations in the project.
*
* This map has several performance disadvantages:
* 1. It is not tree-shakeable, so it will include all operations in the project.
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
* 3. It does not support dead code elimination, so it will add unused operations.
*
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
const documents = {
"fragment RegularError on FieldError {\n field\n message\n}": types.RegularErrorFragmentDoc,
"fragment RegularPost on Post {\n id\n authorID\n title\n content\n createdAt\n updatedAt\n snippet\n points\n upvoted\n downvoted\n author {\n ...RegularUser\n }\n}": types.RegularPostFragmentDoc,
"fragment RegularPostResponse on PostResponse {\n errors {\n ...RegularError\n }\n post {\n ...RegularPost\n }\n}": types.RegularPostResponseFragmentDoc,
"fragment RegularUser on User {\n id\n email\n username\n createdAt\n updatedAt\n}": types.RegularUserFragmentDoc,
"fragment RegularUserResponse on UserResponse {\n errors {\n ...RegularError\n }\n user {\n ...RegularUser\n }\n}": types.RegularUserResponseFragmentDoc,
"mutation CreatePost($input: PostInput!) {\n createPost(input: $input) {\n ...RegularPostResponse\n }\n}": types.CreatePostDocument,
"mutation DeletePost($id: String!) {\n deletePost(id: $id)\n}": types.DeletePostDocument,
"mutation Downvote($postID: String!) {\n downvote(postID: $postID) {\n ...RegularPost\n }\n}": types.DownvoteDocument,
"mutation ForgotPassword($email: String!) {\n forgotPassword(email: $email) {\n errors {\n ...RegularError\n }\n message\n messageType\n }\n}": types.ForgotPasswordDocument,
"mutation Login($input: UsernamePasswordInput!) {\n login(input: $input) {\n ...RegularUserResponse\n }\n}": types.LoginDocument,
"mutation Logout {\n logout\n}": types.LogoutDocument,
"mutation Register($input: UsernamePasswordInput!) {\n register(input: $input) {\n ...RegularUserResponse\n }\n}": types.RegisterDocument,
"mutation RemoveDownvote($postID: String!) {\n removeDownvote(postID: $postID) {\n ...RegularPost\n }\n}": types.RemoveDownvoteDocument,
"mutation RemoveUpvote($postID: String!) {\n removeUpvote(postID: $postID) {\n ...RegularPost\n }\n}": types.RemoveUpvoteDocument,
"mutation ResetPassword($newPassword: String!, $token: String!) {\n resetPassword(newPassword: $newPassword, token: $token) {\n errors {\n ...RegularError\n }\n message\n messageType\n }\n}": types.ResetPasswordDocument,
"mutation UpdatePost($id: String!, $title: String!, $content: String!) {\n updatePost(id: $id, title: $title, content: $content) {\n errors {\n ...RegularError\n }\n post {\n ...RegularPost\n }\n }\n}": types.UpdatePostDocument,
"mutation Upvote($postID: String!) {\n upvote(postID: $postID) {\n ...RegularPost\n }\n}": types.UpvoteDocument,
"query CheckResetPasswordToken($token: String!) {\n checkResetPasswordToken(token: $token)\n}": types.CheckResetPasswordTokenDocument,
"query Me {\n me {\n ...RegularUser\n }\n}": types.MeDocument,
"query Post($id: String!) {\n post(id: $id) {\n ...RegularPost\n }\n}": types.PostDocument,
"query Posts($limit: Int, $cursor: Timestamp) {\n posts(limit: $limit, cursor: $cursor) {\n ...RegularPost\n }\n}": types.PostsDocument,
};
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*
*
* @example
* ```ts
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
* ```
*
* The query argument is unknown!
* Please regenerate the types.
*/
export function graphql(source: string): unknown;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "fragment RegularError on FieldError {\n field\n message\n}"): (typeof documents)["fragment RegularError on FieldError {\n field\n message\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "fragment RegularPost on Post {\n id\n authorID\n title\n content\n createdAt\n updatedAt\n snippet\n points\n upvoted\n downvoted\n author {\n ...RegularUser\n }\n}"): (typeof documents)["fragment RegularPost on Post {\n id\n authorID\n title\n content\n createdAt\n updatedAt\n snippet\n points\n upvoted\n downvoted\n author {\n ...RegularUser\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "fragment RegularPostResponse on PostResponse {\n errors {\n ...RegularError\n }\n post {\n ...RegularPost\n }\n}"): (typeof documents)["fragment RegularPostResponse on PostResponse {\n errors {\n ...RegularError\n }\n post {\n ...RegularPost\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "fragment RegularUser on User {\n id\n email\n username\n createdAt\n updatedAt\n}"): (typeof documents)["fragment RegularUser on User {\n id\n email\n username\n createdAt\n updatedAt\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "fragment RegularUserResponse on UserResponse {\n errors {\n ...RegularError\n }\n user {\n ...RegularUser\n }\n}"): (typeof documents)["fragment RegularUserResponse on UserResponse {\n errors {\n ...RegularError\n }\n user {\n ...RegularUser\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation CreatePost($input: PostInput!) {\n createPost(input: $input) {\n ...RegularPostResponse\n }\n}"): (typeof documents)["mutation CreatePost($input: PostInput!) {\n createPost(input: $input) {\n ...RegularPostResponse\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation DeletePost($id: String!) {\n deletePost(id: $id)\n}"): (typeof documents)["mutation DeletePost($id: String!) {\n deletePost(id: $id)\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation Downvote($postID: String!) {\n downvote(postID: $postID) {\n ...RegularPost\n }\n}"): (typeof documents)["mutation Downvote($postID: String!) {\n downvote(postID: $postID) {\n ...RegularPost\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation ForgotPassword($email: String!) {\n forgotPassword(email: $email) {\n errors {\n ...RegularError\n }\n message\n messageType\n }\n}"): (typeof documents)["mutation ForgotPassword($email: String!) {\n forgotPassword(email: $email) {\n errors {\n ...RegularError\n }\n message\n messageType\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation Login($input: UsernamePasswordInput!) {\n login(input: $input) {\n ...RegularUserResponse\n }\n}"): (typeof documents)["mutation Login($input: UsernamePasswordInput!) {\n login(input: $input) {\n ...RegularUserResponse\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation Logout {\n logout\n}"): (typeof documents)["mutation Logout {\n logout\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation Register($input: UsernamePasswordInput!) {\n register(input: $input) {\n ...RegularUserResponse\n }\n}"): (typeof documents)["mutation Register($input: UsernamePasswordInput!) {\n register(input: $input) {\n ...RegularUserResponse\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation RemoveDownvote($postID: String!) {\n removeDownvote(postID: $postID) {\n ...RegularPost\n }\n}"): (typeof documents)["mutation RemoveDownvote($postID: String!) {\n removeDownvote(postID: $postID) {\n ...RegularPost\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation RemoveUpvote($postID: String!) {\n removeUpvote(postID: $postID) {\n ...RegularPost\n }\n}"): (typeof documents)["mutation RemoveUpvote($postID: String!) {\n removeUpvote(postID: $postID) {\n ...RegularPost\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation ResetPassword($newPassword: String!, $token: String!) {\n resetPassword(newPassword: $newPassword, token: $token) {\n errors {\n ...RegularError\n }\n message\n messageType\n }\n}"): (typeof documents)["mutation ResetPassword($newPassword: String!, $token: String!) {\n resetPassword(newPassword: $newPassword, token: $token) {\n errors {\n ...RegularError\n }\n message\n messageType\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation UpdatePost($id: String!, $title: String!, $content: String!) {\n updatePost(id: $id, title: $title, content: $content) {\n errors {\n ...RegularError\n }\n post {\n ...RegularPost\n }\n }\n}"): (typeof documents)["mutation UpdatePost($id: String!, $title: String!, $content: String!) {\n updatePost(id: $id, title: $title, content: $content) {\n errors {\n ...RegularError\n }\n post {\n ...RegularPost\n }\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "mutation Upvote($postID: String!) {\n upvote(postID: $postID) {\n ...RegularPost\n }\n}"): (typeof documents)["mutation Upvote($postID: String!) {\n upvote(postID: $postID) {\n ...RegularPost\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "query CheckResetPasswordToken($token: String!) {\n checkResetPasswordToken(token: $token)\n}"): (typeof documents)["query CheckResetPasswordToken($token: String!) {\n checkResetPasswordToken(token: $token)\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "query Me {\n me {\n ...RegularUser\n }\n}"): (typeof documents)["query Me {\n me {\n ...RegularUser\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "query Post($id: String!) {\n post(id: $id) {\n ...RegularPost\n }\n}"): (typeof documents)["query Post($id: String!) {\n post(id: $id) {\n ...RegularPost\n }\n}"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "query Posts($limit: Int, $cursor: Timestamp) {\n posts(limit: $limit, cursor: $cursor) {\n ...RegularPost\n }\n}"): (typeof documents)["query Posts($limit: Int, $cursor: Timestamp) {\n posts(limit: $limit, cursor: $cursor) {\n ...RegularPost\n }\n}"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};
}
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;
+479
View File
@@ -0,0 +1,479 @@
/* eslint-disable */
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
/** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.This scalar is serialized to a string in ISO 8601 format and parsed from a string in ISO 8601 format. */
DateTimeISO: { input: any; output: any; }
/** The javascript `Date` as integer. Type represents date and time as number of milliseconds from start of UNIX epoch. */
Timestamp: { input: any; output: any; }
};
export type DateTimeFilter = {
equals?: InputMaybe<Scalars['DateTimeISO']['input']>;
gt?: InputMaybe<Scalars['DateTimeISO']['input']>;
gte?: InputMaybe<Scalars['DateTimeISO']['input']>;
in?: InputMaybe<Array<Scalars['DateTimeISO']['input']>>;
lt?: InputMaybe<Scalars['DateTimeISO']['input']>;
lte?: InputMaybe<Scalars['DateTimeISO']['input']>;
not?: InputMaybe<NestedDateTimeFilter>;
notIn?: InputMaybe<Array<Scalars['DateTimeISO']['input']>>;
};
export type FieldError = {
__typename?: 'FieldError';
field?: Maybe<Scalars['String']['output']>;
message: Scalars['String']['output'];
};
export type IntFilter = {
equals?: InputMaybe<Scalars['Int']['input']>;
gt?: InputMaybe<Scalars['Int']['input']>;
gte?: InputMaybe<Scalars['Int']['input']>;
in?: InputMaybe<Array<Scalars['Int']['input']>>;
lt?: InputMaybe<Scalars['Int']['input']>;
lte?: InputMaybe<Scalars['Int']['input']>;
not?: InputMaybe<NestedIntFilter>;
notIn?: InputMaybe<Array<Scalars['Int']['input']>>;
};
export type Mutation = {
__typename?: 'Mutation';
createPost: PostResponse;
deletePost: Scalars['Boolean']['output'];
downvote?: Maybe<Post>;
forgotPassword: ResetPasswordResponse;
login: UserResponse;
logout: Scalars['Boolean']['output'];
register: UserResponse;
removeDownvote?: Maybe<Post>;
removeUpvote?: Maybe<Post>;
resetPassword: ResetPasswordResponse;
updatePost: PostResponse;
upvote?: Maybe<Post>;
};
export type MutationCreatePostArgs = {
input: PostInput;
};
export type MutationDeletePostArgs = {
id: Scalars['String']['input'];
};
export type MutationDownvoteArgs = {
postID: Scalars['String']['input'];
};
export type MutationForgotPasswordArgs = {
email: Scalars['String']['input'];
};
export type MutationLoginArgs = {
input: UsernamePasswordInput;
};
export type MutationRegisterArgs = {
input: UsernamePasswordInput;
};
export type MutationRemoveDownvoteArgs = {
postID: Scalars['String']['input'];
};
export type MutationRemoveUpvoteArgs = {
postID: Scalars['String']['input'];
};
export type MutationResetPasswordArgs = {
newPassword: Scalars['String']['input'];
token: Scalars['String']['input'];
};
export type MutationUpdatePostArgs = {
content?: InputMaybe<Scalars['String']['input']>;
id: Scalars['String']['input'];
title?: InputMaybe<Scalars['String']['input']>;
};
export type MutationUpvoteArgs = {
postID: Scalars['String']['input'];
};
export type NestedDateTimeFilter = {
equals?: InputMaybe<Scalars['DateTimeISO']['input']>;
gt?: InputMaybe<Scalars['DateTimeISO']['input']>;
gte?: InputMaybe<Scalars['DateTimeISO']['input']>;
in?: InputMaybe<Array<Scalars['DateTimeISO']['input']>>;
lt?: InputMaybe<Scalars['DateTimeISO']['input']>;
lte?: InputMaybe<Scalars['DateTimeISO']['input']>;
not?: InputMaybe<NestedDateTimeFilter>;
notIn?: InputMaybe<Array<Scalars['DateTimeISO']['input']>>;
};
export type NestedIntFilter = {
equals?: InputMaybe<Scalars['Int']['input']>;
gt?: InputMaybe<Scalars['Int']['input']>;
gte?: InputMaybe<Scalars['Int']['input']>;
in?: InputMaybe<Array<Scalars['Int']['input']>>;
lt?: InputMaybe<Scalars['Int']['input']>;
lte?: InputMaybe<Scalars['Int']['input']>;
not?: InputMaybe<NestedIntFilter>;
notIn?: InputMaybe<Array<Scalars['Int']['input']>>;
};
export type NestedStringFilter = {
contains?: InputMaybe<Scalars['String']['input']>;
endsWith?: InputMaybe<Scalars['String']['input']>;
equals?: InputMaybe<Scalars['String']['input']>;
gt?: InputMaybe<Scalars['String']['input']>;
gte?: InputMaybe<Scalars['String']['input']>;
in?: InputMaybe<Array<Scalars['String']['input']>>;
lt?: InputMaybe<Scalars['String']['input']>;
lte?: InputMaybe<Scalars['String']['input']>;
not?: InputMaybe<NestedStringFilter>;
notIn?: InputMaybe<Array<Scalars['String']['input']>>;
startsWith?: InputMaybe<Scalars['String']['input']>;
};
export type NestedStringNullableFilter = {
contains?: InputMaybe<Scalars['String']['input']>;
endsWith?: InputMaybe<Scalars['String']['input']>;
equals?: InputMaybe<Scalars['String']['input']>;
gt?: InputMaybe<Scalars['String']['input']>;
gte?: InputMaybe<Scalars['String']['input']>;
in?: InputMaybe<Array<Scalars['String']['input']>>;
lt?: InputMaybe<Scalars['String']['input']>;
lte?: InputMaybe<Scalars['String']['input']>;
not?: InputMaybe<NestedStringNullableFilter>;
notIn?: InputMaybe<Array<Scalars['String']['input']>>;
startsWith?: InputMaybe<Scalars['String']['input']>;
};
export type Post = {
__typename?: 'Post';
author: User;
authorID: Scalars['String']['output'];
content: Scalars['String']['output'];
createdAt: Scalars['DateTimeISO']['output'];
downvoted?: Maybe<Scalars['Boolean']['output']>;
id: Scalars['String']['output'];
points: Scalars['Int']['output'];
snippet: Scalars['String']['output'];
title?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTimeISO']['output'];
upvoted?: Maybe<Scalars['Boolean']['output']>;
};
export type PostInput = {
content: Scalars['String']['input'];
title?: InputMaybe<Scalars['String']['input']>;
};
export type PostListRelationFilter = {
every?: InputMaybe<PostWhereInput>;
none?: InputMaybe<PostWhereInput>;
some?: InputMaybe<PostWhereInput>;
};
export type PostResponse = {
__typename?: 'PostResponse';
errors?: Maybe<Array<FieldError>>;
post?: Maybe<Post>;
};
export type PostWhereInput = {
AND?: InputMaybe<Array<PostWhereInput>>;
NOT?: InputMaybe<Array<PostWhereInput>>;
OR?: InputMaybe<Array<PostWhereInput>>;
author?: InputMaybe<UserRelationFilter>;
authorID?: InputMaybe<StringFilter>;
content?: InputMaybe<StringFilter>;
createdAt?: InputMaybe<DateTimeFilter>;
id?: InputMaybe<StringFilter>;
points?: InputMaybe<IntFilter>;
title?: InputMaybe<StringNullableFilter>;
updatedAt?: InputMaybe<DateTimeFilter>;
};
export type Query = {
__typename?: 'Query';
checkResetPasswordToken: Scalars['Boolean']['output'];
me?: Maybe<User>;
post?: Maybe<Post>;
posts: Array<Post>;
};
export type QueryCheckResetPasswordTokenArgs = {
token: Scalars['String']['input'];
};
export type QueryPostArgs = {
id: Scalars['String']['input'];
};
export type QueryPostsArgs = {
cursor?: InputMaybe<Scalars['Timestamp']['input']>;
limit?: InputMaybe<Scalars['Int']['input']>;
};
export enum QueryMode {
Default = 'default',
Insensitive = 'insensitive'
}
export type ResetPasswordResponse = {
__typename?: 'ResetPasswordResponse';
errors?: Maybe<Array<FieldError>>;
message?: Maybe<Scalars['String']['output']>;
messageType?: Maybe<Scalars['String']['output']>;
};
export type StringFilter = {
contains?: InputMaybe<Scalars['String']['input']>;
endsWith?: InputMaybe<Scalars['String']['input']>;
equals?: InputMaybe<Scalars['String']['input']>;
gt?: InputMaybe<Scalars['String']['input']>;
gte?: InputMaybe<Scalars['String']['input']>;
in?: InputMaybe<Array<Scalars['String']['input']>>;
lt?: InputMaybe<Scalars['String']['input']>;
lte?: InputMaybe<Scalars['String']['input']>;
mode?: InputMaybe<QueryMode>;
not?: InputMaybe<NestedStringFilter>;
notIn?: InputMaybe<Array<Scalars['String']['input']>>;
startsWith?: InputMaybe<Scalars['String']['input']>;
};
export type StringNullableFilter = {
contains?: InputMaybe<Scalars['String']['input']>;
endsWith?: InputMaybe<Scalars['String']['input']>;
equals?: InputMaybe<Scalars['String']['input']>;
gt?: InputMaybe<Scalars['String']['input']>;
gte?: InputMaybe<Scalars['String']['input']>;
in?: InputMaybe<Array<Scalars['String']['input']>>;
lt?: InputMaybe<Scalars['String']['input']>;
lte?: InputMaybe<Scalars['String']['input']>;
mode?: InputMaybe<QueryMode>;
not?: InputMaybe<NestedStringNullableFilter>;
notIn?: InputMaybe<Array<Scalars['String']['input']>>;
startsWith?: InputMaybe<Scalars['String']['input']>;
};
export type User = {
__typename?: 'User';
_count?: Maybe<UserCount>;
createdAt: Scalars['DateTimeISO']['output'];
email?: Maybe<Scalars['String']['output']>;
id: Scalars['String']['output'];
updatedAt: Scalars['DateTimeISO']['output'];
username: Scalars['String']['output'];
};
export type UserCount = {
__typename?: 'UserCount';
Post: Scalars['Int']['output'];
};
export type UserCountPostArgs = {
where?: InputMaybe<PostWhereInput>;
};
export type UserRelationFilter = {
is?: InputMaybe<UserWhereInput>;
isNot?: InputMaybe<UserWhereInput>;
};
export type UserResponse = {
__typename?: 'UserResponse';
errors?: Maybe<Array<FieldError>>;
user?: Maybe<User>;
};
export type UserWhereInput = {
AND?: InputMaybe<Array<UserWhereInput>>;
NOT?: InputMaybe<Array<UserWhereInput>>;
OR?: InputMaybe<Array<UserWhereInput>>;
Post?: InputMaybe<PostListRelationFilter>;
createdAt?: InputMaybe<DateTimeFilter>;
email?: InputMaybe<StringNullableFilter>;
id?: InputMaybe<StringFilter>;
password?: InputMaybe<StringFilter>;
updatedAt?: InputMaybe<DateTimeFilter>;
username?: InputMaybe<StringFilter>;
};
export type UsernamePasswordInput = {
email?: InputMaybe<Scalars['String']['input']>;
password: Scalars['String']['input'];
username: Scalars['String']['input'];
};
export type RegularErrorFragment = { __typename?: 'FieldError', field?: string | null, message: string };
export type RegularPostFragment = { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } };
export type RegularPostResponseFragment = { __typename?: 'PostResponse', errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null, post?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null };
export type RegularUserFragment = { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any };
export type RegularUserResponseFragment = { __typename?: 'UserResponse', errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null, user?: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } | null };
export type CreatePostMutationVariables = Exact<{
input: PostInput;
}>;
export type CreatePostMutation = { __typename?: 'Mutation', createPost: { __typename?: 'PostResponse', errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null, post?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null } };
export type DeletePostMutationVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type DeletePostMutation = { __typename?: 'Mutation', deletePost: boolean };
export type DownvoteMutationVariables = Exact<{
postID: Scalars['String']['input'];
}>;
export type DownvoteMutation = { __typename?: 'Mutation', downvote?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null };
export type ForgotPasswordMutationVariables = Exact<{
email: Scalars['String']['input'];
}>;
export type ForgotPasswordMutation = { __typename?: 'Mutation', forgotPassword: { __typename?: 'ResetPasswordResponse', message?: string | null, messageType?: string | null, errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null } };
export type LoginMutationVariables = Exact<{
input: UsernamePasswordInput;
}>;
export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'UserResponse', errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null, user?: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } | null } };
export type LogoutMutationVariables = Exact<{ [key: string]: never; }>;
export type LogoutMutation = { __typename?: 'Mutation', logout: boolean };
export type RegisterMutationVariables = Exact<{
input: UsernamePasswordInput;
}>;
export type RegisterMutation = { __typename?: 'Mutation', register: { __typename?: 'UserResponse', errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null, user?: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } | null } };
export type RemoveDownvoteMutationVariables = Exact<{
postID: Scalars['String']['input'];
}>;
export type RemoveDownvoteMutation = { __typename?: 'Mutation', removeDownvote?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null };
export type RemoveUpvoteMutationVariables = Exact<{
postID: Scalars['String']['input'];
}>;
export type RemoveUpvoteMutation = { __typename?: 'Mutation', removeUpvote?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null };
export type ResetPasswordMutationVariables = Exact<{
newPassword: Scalars['String']['input'];
token: Scalars['String']['input'];
}>;
export type ResetPasswordMutation = { __typename?: 'Mutation', resetPassword: { __typename?: 'ResetPasswordResponse', message?: string | null, messageType?: string | null, errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null } };
export type UpdatePostMutationVariables = Exact<{
id: Scalars['String']['input'];
title: Scalars['String']['input'];
content: Scalars['String']['input'];
}>;
export type UpdatePostMutation = { __typename?: 'Mutation', updatePost: { __typename?: 'PostResponse', errors?: Array<{ __typename?: 'FieldError', field?: string | null, message: string }> | null, post?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null } };
export type UpvoteMutationVariables = Exact<{
postID: Scalars['String']['input'];
}>;
export type UpvoteMutation = { __typename?: 'Mutation', upvote?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null };
export type CheckResetPasswordTokenQueryVariables = Exact<{
token: Scalars['String']['input'];
}>;
export type CheckResetPasswordTokenQuery = { __typename?: 'Query', checkResetPasswordToken: boolean };
export type MeQueryVariables = Exact<{ [key: string]: never; }>;
export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } | null };
export type PostQueryVariables = Exact<{
id: Scalars['String']['input'];
}>;
export type PostQuery = { __typename?: 'Query', post?: { __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } } | null };
export type PostsQueryVariables = Exact<{
limit?: InputMaybe<Scalars['Int']['input']>;
cursor?: InputMaybe<Scalars['Timestamp']['input']>;
}>;
export type PostsQuery = { __typename?: 'Query', posts: Array<{ __typename?: 'Post', id: string, authorID: string, title?: string | null, content: string, createdAt: any, updatedAt: any, snippet: string, points: number, upvoted?: boolean | null, downvoted?: boolean | null, author: { __typename?: 'User', id: string, email?: string | null, username: string, createdAt: any, updatedAt: any } }> };
export const RegularErrorFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]} as unknown as DocumentNode<RegularErrorFragment, unknown>;
export const RegularUserFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<RegularUserFragment, unknown>;
export const RegularPostFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<RegularPostFragment, unknown>;
export const RegularPostResponseFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPostResponse"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PostResponse"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"post"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<RegularPostResponseFragment, unknown>;
export const RegularUserResponseFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUserResponse"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserResponse"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<RegularUserResponseFragment, unknown>;
export const CreatePostDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreatePost"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"PostInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createPost"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPostResponse"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPostResponse"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PostResponse"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"post"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}}]} as unknown as DocumentNode<CreatePostMutation, CreatePostMutationVariables>;
export const DeletePostDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeletePost"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deletePost"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode<DeletePostMutation, DeletePostMutationVariables>;
export const DownvoteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Downvote"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"postID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"downvote"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"postID"},"value":{"kind":"Variable","name":{"kind":"Name","value":"postID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<DownvoteMutation, DownvoteMutationVariables>;
export const ForgotPasswordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ForgotPassword"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"forgotPassword"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"messageType"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]} as unknown as DocumentNode<ForgotPasswordMutation, ForgotPasswordMutationVariables>;
export const LoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Login"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UsernamePasswordInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"login"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUserResponse"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUserResponse"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserResponse"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<LoginMutation, LoginMutationVariables>;
export const LogoutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Logout"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"logout"}}]}}]} as unknown as DocumentNode<LogoutMutation, LogoutMutationVariables>;
export const RegisterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Register"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UsernamePasswordInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"register"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUserResponse"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUserResponse"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserResponse"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<RegisterMutation, RegisterMutationVariables>;
export const RemoveDownvoteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveDownvote"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"postID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeDownvote"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"postID"},"value":{"kind":"Variable","name":{"kind":"Name","value":"postID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<RemoveDownvoteMutation, RemoveDownvoteMutationVariables>;
export const RemoveUpvoteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveUpvote"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"postID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeUpvote"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"postID"},"value":{"kind":"Variable","name":{"kind":"Name","value":"postID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<RemoveUpvoteMutation, RemoveUpvoteMutationVariables>;
export const ResetPasswordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResetPassword"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"resetPassword"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"newPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"messageType"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]} as unknown as DocumentNode<ResetPasswordMutation, ResetPasswordMutationVariables>;
export const UpdatePostDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdatePost"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"title"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"content"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updatePost"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"title"},"value":{"kind":"Variable","name":{"kind":"Name","value":"title"}}},{"kind":"Argument","name":{"kind":"Name","value":"content"},"value":{"kind":"Variable","name":{"kind":"Name","value":"content"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularError"}}]}},{"kind":"Field","name":{"kind":"Name","value":"post"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularError"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FieldError"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"field"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<UpdatePostMutation, UpdatePostMutationVariables>;
export const UpvoteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Upvote"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"postID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"upvote"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"postID"},"value":{"kind":"Variable","name":{"kind":"Name","value":"postID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<UpvoteMutation, UpvoteMutationVariables>;
export const CheckResetPasswordTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CheckResetPasswordToken"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"checkResetPasswordToken"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}]}]}}]} as unknown as DocumentNode<CheckResetPasswordTokenQuery, CheckResetPasswordTokenQueryVariables>;
export const MeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"me"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<MeQuery, MeQueryVariables>;
export const PostDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Post"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"post"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<PostQuery, PostQueryVariables>;
export const PostsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Posts"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Timestamp"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"posts"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularPost"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularUser"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RegularPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Post"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorID"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"snippet"}},{"kind":"Field","name":{"kind":"Name","value":"points"}},{"kind":"Field","name":{"kind":"Name","value":"upvoted"}},{"kind":"Field","name":{"kind":"Name","value":"downvoted"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RegularUser"}}]}}]}}]} as unknown as DocumentNode<PostsQuery, PostsQueryVariables>;
+1
View File
@@ -0,0 +1 @@
export * from "./gql";
+17
View File
@@ -0,0 +1,17 @@
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
overwrite: true,
schema: 'http://localhost:4000/graphql',
documents: 'graphql/**/*.graphql',
generates: {
'generated/graphql/': {
preset: 'client',
presetConfig: {
fragmentMasking: false
}
}
}
}
export default config
@@ -0,0 +1,4 @@
fragment RegularError on FieldError {
field
message
}
@@ -0,0 +1,15 @@
fragment RegularPost on Post {
id
authorID
title
content
createdAt
updatedAt
snippet
points
upvoted
downvoted
author {
...RegularUser
}
}
@@ -0,0 +1,8 @@
fragment RegularPostResponse on PostResponse {
errors {
...RegularError
}
post {
...RegularPost
}
}
@@ -0,0 +1,7 @@
fragment RegularUser on User {
id
email
username
createdAt
updatedAt
}
@@ -0,0 +1,8 @@
fragment RegularUserResponse on UserResponse {
errors {
...RegularError
}
user {
...RegularUser
}
}
@@ -0,0 +1,5 @@
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
...RegularPostResponse
}
}
@@ -0,0 +1,3 @@
mutation DeletePost($id: String!) {
deletePost(id: $id)
}
@@ -0,0 +1,5 @@
mutation Downvote($postID: String!) {
downvote(postID: $postID) {
...RegularPost
}
}
@@ -0,0 +1,9 @@
mutation ForgotPassword($email: String!) {
forgotPassword(email: $email) {
errors {
...RegularError
}
message
messageType
}
}
+5
View File
@@ -0,0 +1,5 @@
mutation Login($input: UsernamePasswordInput!) {
login(input: $input) {
...RegularUserResponse
}
}
@@ -0,0 +1,3 @@
mutation Logout {
logout
}
@@ -0,0 +1,5 @@
mutation Register($input: UsernamePasswordInput!) {
register(input: $input) {
...RegularUserResponse
}
}
@@ -0,0 +1,5 @@
mutation RemoveDownvote($postID: String!) {
removeDownvote(postID: $postID) {
...RegularPost
}
}
@@ -0,0 +1,5 @@
mutation RemoveUpvote($postID: String!) {
removeUpvote(postID: $postID) {
...RegularPost
}
}
@@ -0,0 +1,9 @@
mutation ResetPassword($newPassword: String!, $token: String!) {
resetPassword(newPassword: $newPassword, token: $token) {
errors {
...RegularError
}
message
messageType
}
}
@@ -0,0 +1,10 @@
mutation UpdatePost($id: String!, $title: String!, $content: String!) {
updatePost(id: $id, title: $title, content: $content) {
errors {
...RegularError
}
post {
...RegularPost
}
}
}
@@ -0,0 +1,5 @@
mutation Upvote($postID: String!) {
upvote(postID: $postID) {
...RegularPost
}
}
@@ -0,0 +1,3 @@
query CheckResetPasswordToken($token: String!) {
checkResetPasswordToken(token: $token)
}
+5
View File
@@ -0,0 +1,5 @@
query Me {
me {
...RegularUser
}
}
+5
View File
@@ -0,0 +1,5 @@
query Post($id: String!) {
post(id: $id) {
...RegularPost
}
}
+5
View File
@@ -0,0 +1,5 @@
query Posts($limit: Int, $cursor: Timestamp) {
posts(limit: $limit, cursor: $cursor) {
...RegularPost
}
}
+1
View File
@@ -0,0 +1 @@
export * from './useAuthenticate'
+15
View File
@@ -0,0 +1,15 @@
import { MeDocument } from '@/generated/graphql/graphql'
import { useQuery } from '@apollo/client/react'
import { usePathname, useRouter } from 'next/navigation'
import { useEffect } from 'react'
export const useAuthenticate = () => {
const router = useRouter()
const { data, loading } = useQuery(MeDocument)
const pathname = usePathname()
useEffect(() => {
if (!loading && !data?.me) {
router.replace(`/login?redirect=${pathname}`)
}
}, [router, data, loading])
}
+38
View File
@@ -0,0 +1,38 @@
import { HttpLink } from '@apollo/client'
import { ApolloClient, InMemoryCache } from '@apollo/client'
// This is the client-side client
export const apollo = new ApolloClient({
link: new HttpLink({
uri: process.env.NODE_ENV == 'production' ? 'https://litreddit-backend.elliot-at-zuri.ch/graphql' : process.env.NEXT_PUBLIC_BACKEND_URI,
credentials: 'include', // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
}),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
// https://www.apollographql.com/docs/react/pagination/core-api/
posts: {
// Don't cache separate results based on
// any of this field's arguments.
keyArgs: false,
// Concatenate the incoming list items with
// the existing list items.
merge: (existing: { __ref: string }[] = [], incoming: { __ref: string }[]) => {
const cacheItems: typeof existing = JSON.parse(JSON.stringify(existing))
const refs = existing.map(e => e.__ref)
for (let i = 0;i < incoming.length;i++) {
if (!refs.includes(incoming[i].__ref)) {
cacheItems.push(incoming[i])
refs.push(incoming[i].__ref)
}
}
return cacheItems
},
}
}
}
}
}),
})
+2
View File
@@ -0,0 +1,2 @@
export * from './client'
export * from './server'
+18
View File
@@ -0,0 +1,18 @@
'use server'
import { SESSION_COOKIE_NAME } from '@/constants'
import { HttpLink } from '@apollo/client'
import { ApolloClient, InMemoryCache, registerApolloClient } from '@apollo/client-integration-nextjs'
// This is the server-side client
export const createApolloClient = async (cookie?: string) => registerApolloClient(() => {
return new ApolloClient({
link: new HttpLink({
uri: process.env.NODE_ENV == 'production' ? 'https://litreddit-backend.elliot-at-zuri.ch/graphql' : process.env.NEXT_PUBLIC_BACKEND_URI,
credentials: 'include',
headers: {
cookie: cookie ? `${SESSION_COOKIE_NAME}=${cookie}` : ''
}
}),
cache: new InMemoryCache()
})
})
+1
View File
@@ -0,0 +1 @@
export * from './apollo'
+4
View File
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
export default nextConfig
+221
View File
@@ -0,0 +1,221 @@
All pages are **server components** by default. Only pages with `'use-client'` on top are **client components**.
**Server components fetch data on the server and pre-render the HTML.** For example, in the following page:
```tsx
import LikeButton from '@/app/ui/like-button'
import { getPost } from '@/lib/data'
export default async function Page({ params }: { params: { id: string } }) {
const post = await getPost(params.id)
return (
<div>
<main>
<h1>{post.title}</h1>
{/* ... */}
<LikeButton likes={post.likes} />
</main>
</div>
)
}
```
```tsx
'use client'
import { useState } from 'react'
export default function LikeButton({ likes }: { likes: number }) {
// ...
}
```
The `getPost` function is run server-side to get the data and render the whole `div` tag except for the `LikeButton` which is rendered client-side.
Using any hooks e.g. `useQuery` or `useMutation` requires us to do so inside a `client component`.
We can also have `Next.js` fetches data and pre-renders HTML **at build time and on periodical revalidations** using [**Incremental Static Regeneration (ISR)**](https://nextjs.org/docs/app/guides/incremental-static-regeneration) as follows:
```tsx
interface Post {
id: string
title: string
content: string
}
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
// For each `post` in the array returned by `generateStaticParams`,
// we will pre-render and cache a corresponding page at build time and on later revalidations.
// If a request comes in for a path that hasn't been generated,
// i.e. if there is a request for a post not retrieved when `generateStaticParams` was last run,
// Next.js will server-render the page on-demand.
export const dynamicParams = true // or false, to 404 on unknown paths
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
(res) => res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
```
SSR with `@apollo/client`:
```tsx
import { Wrapper } from '@/components'
import { SESSION_COOKIE_NAME } from '@/constants'
import { MeDocument, PostDocument } from '@/generated/graphql/graphql'
import { createApolloClient } from '@/lib'
import { Heading, Text } from '@chakra-ui/react'
import { cookies } from 'next/headers'
import { ClientSection } from './ClientSection'
interface Props {
params: {
id: string
}
}
const PostPage: React.FC<Props> = async ({ params: { id } }) => {
const cookieStore = cookies()
const cookie = cookieStore.get(SESSION_COOKIE_NAME)?.value
const apollo = await createApolloClient(cookie)
const { data } = await apollo.query({ query: PostDocument, variables: { id } })
const { data: meData } = await apollo.query({ query: MeDocument })
return (
<Wrapper>
{
data?.post ?
<>
<Heading as='h3' size='md'>{data.post.title}</Heading>
<Text>Posted by {data.post.author.username}</Text>
<Text mt={4}>{data.post.content}</Text>
{
data?.post?.authorID == meData?.me?.id &&
<ClientSection data={data} />
}
</>
:
<>Post not found!</>
}
</Wrapper>
)
}
export default PostPage
```
where `createApolloClient` is:
```tsx
'use server'
import { SESSION_COOKIE_NAME } from '@/constants'
import { from, HttpLink, NormalizedCacheObject } from '@apollo/client'
import { onError } from '@apollo/client/link/error'
import { ApolloClient, InMemoryCache, registerApolloClient } from '@apollo/experimental-nextjs-app-support'
const errorLink = onError(({ graphQLErrors, networkError }) => {
graphQLErrors?.forEach(({ message, locations, path }) => {
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
})
if (networkError) console.error(`[Network error]: ${networkError}`)
})
// This is the server-side client
export const createApolloClient = async (cookie?: string) => registerApolloClient<ApolloClient<NormalizedCacheObject>>(() => {
const httpLink = new HttpLink({
uri: process.env.NEXT_PUBLIC_BACKEND_URI,
credentials: 'include',
headers: {
cookie: cookie ? `${SESSION_COOKIE_NAME}=${cookie}` : ''
}
})
return new ApolloClient({
ssrMode: true,
link: from([errorLink, httpLink]),
cache: new InMemoryCache()
})
})
```
If we use `react-query`, we can combine the initial server-side data fetching and subsequent `useQuery` calls as follows:
```tsx
// app/posts/page.tsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query'
import Posts from './posts'
export default async function PostsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: getPosts,
})
return (
// Hydration is when React converts the pre-rendered HTML from the server into a fully interactive application by attaching event handlers.
// Dehydration, on the other hand, is when an interactive JavaScript object is converted into HTML.
// Here we are converting the queryClient which contains data fetched server-side into HTML and passing that HTML into HydrationBoundary, which is a Client component.
// When HydrationBoundary is rendered client-side, the HTML will be hydrated, i.e. the data fetched server-side will be integrated with useQuery seamlessly.
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
)
}
```
```tsx
// app/posts/posts.tsx
'use client'
export default function Posts() {
// This useQuery could just as well happen in some deeper
// child to <Posts>, data will be available immediately either way
const { data } = useQuery({
queryKey: ['posts'],
queryFn: () => getPosts(),
})
// This query was not prefetched on the server and will not start
// fetching until on the client, both patterns are fine to mix.
const { data: commentsData } = useQuery({
queryKey: ['posts-comments'],
queryFn: getComments,
})
// ...
}
```
+6481
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
{
"name": "litreddit-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"env:generate": "gen-env-types .env -o env.d.ts -e .",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"graphql:generate": "graphql-codegen --config graphql-codegen.ts",
"deploy": "vercel --prod"
},
"dependencies": {
"@apollo/client": "^4.0.4",
"@apollo/client-integration-nextjs": "^0.13.1",
"@chakra-ui/icons": "^2.1.1",
"@chakra-ui/next-js": "^2.2.0",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@types/autosize": "^4.0.3",
"autosize": "^6.0.1",
"formik": "^2.4.6",
"framer-motion": "^11.3.4",
"graphql": "^16.9.0",
"next": "^15.5.3",
"react": "^19.1.1",
"react-dom": "^19.1.1"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.2",
"@graphql-codegen/client-preset": "^4.3.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.3.1",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@types/ws": "^8.5.11",
"gen-env-types": "^1.3.4",
"typescript": "^5"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

+40
View File
@@ -0,0 +1,40 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
},
"target": "ES2017"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
+13
View File
@@ -0,0 +1,13 @@
import { FieldError } from '@/generated/graphql/graphql'
type Result = Record<string, string>
export const errorMapper = (errors: FieldError[]): Result => {
const result: Result = {}
errors.forEach(({ field, message }) => {
if (field) {
result[field] = message
}
})
return result
}
+1
View File
@@ -0,0 +1 @@
export * from './errorMapper'