Add a Posts Schema just to test authentication

This commit is contained in:
2024-10-04 09:50:58 +02:00
parent 437e49b842
commit 1a12ed6c9c
23 changed files with 984 additions and 1 deletions

View File

@ -0,0 +1,78 @@
import type {
EditPostById,
UpdatePostInput,
UpdatePostMutationVariables,
} from 'types/graphql'
import { navigate, routes } from '@redwoodjs/router'
import type {
CellSuccessProps,
CellFailureProps,
TypedDocumentNode,
} from '@redwoodjs/web'
import { useMutation } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import PostForm from 'src/components/Post/PostForm'
export const QUERY: TypedDocumentNode<EditPostById> = gql`
query EditPostById($id: Int!) {
post: post(id: $id) {
id
title
body
createdAt
updatedAt
}
}
`
const UPDATE_POST_MUTATION: TypedDocumentNode<
EditPostById,
UpdatePostMutationVariables
> = gql`
mutation UpdatePostMutation($id: Int!, $input: UpdatePostInput!) {
updatePost(id: $id, input: $input) {
id
title
body
createdAt
updatedAt
}
}
`
export const Loading = () => <div>Loading...</div>
export const Failure = ({ error }: CellFailureProps) => (
<div className="rw-cell-error">{error?.message}</div>
)
export const Success = ({ post }: CellSuccessProps<EditPostById>) => {
const [updatePost, { loading, error }] = useMutation(UPDATE_POST_MUTATION, {
onCompleted: () => {
toast.success('Post updated')
navigate(routes.posts())
},
onError: (error) => {
toast.error(error.message)
},
})
const onSave = (input: UpdatePostInput, id: EditPostById['post']['id']) => {
updatePost({ variables: { id, input } })
}
return (
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">
Edit Post {post?.id}
</h2>
</header>
<div className="rw-segment-main">
<PostForm post={post} onSave={onSave} error={error} loading={loading} />
</div>
</div>
)
}

View File

@ -0,0 +1,52 @@
import type {
CreatePostMutation,
CreatePostInput,
CreatePostMutationVariables,
} from 'types/graphql'
import { navigate, routes } from '@redwoodjs/router'
import { useMutation } from '@redwoodjs/web'
import type { TypedDocumentNode } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import PostForm from 'src/components/Post/PostForm'
const CREATE_POST_MUTATION: TypedDocumentNode<
CreatePostMutation,
CreatePostMutationVariables
> = gql`
mutation CreatePostMutation($input: CreatePostInput!) {
createPost(input: $input) {
id
}
}
`
const NewPost = () => {
const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, {
onCompleted: () => {
toast.success('Post created')
navigate(routes.posts())
},
onError: (error) => {
toast.error(error.message)
},
})
const onSave = (input: CreatePostInput) => {
createPost({ variables: { input } })
}
return (
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">New Post</h2>
</header>
<div className="rw-segment-main">
<PostForm onSave={onSave} loading={loading} error={error} />
</div>
</div>
)
}
export default NewPost

View File

@ -0,0 +1,98 @@
import type {
DeletePostMutation,
DeletePostMutationVariables,
FindPostById,
} from 'types/graphql'
import { Link, routes, navigate } from '@redwoodjs/router'
import { useMutation } from '@redwoodjs/web'
import type { TypedDocumentNode } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import { timeTag } from 'src/lib/formatters'
const DELETE_POST_MUTATION: TypedDocumentNode<
DeletePostMutation,
DeletePostMutationVariables
> = gql`
mutation DeletePostMutation($id: Int!) {
deletePost(id: $id) {
id
}
}
`
interface Props {
post: NonNullable<FindPostById['post']>
}
const Post = ({ post }: Props) => {
const [deletePost] = useMutation(DELETE_POST_MUTATION, {
onCompleted: () => {
toast.success('Post deleted')
navigate(routes.posts())
},
onError: (error) => {
toast.error(error.message)
},
})
const onDeleteClick = (id: DeletePostMutationVariables['id']) => {
if (confirm('Are you sure you want to delete post ' + id + '?')) {
deletePost({ variables: { id } })
}
}
return (
<>
<div className="rw-segment">
<header className="rw-segment-header">
<h2 className="rw-heading rw-heading-secondary">
Post {post.id} Detail
</h2>
</header>
<table className="rw-table">
<tbody>
<tr>
<th>Id</th>
<td>{post.id}</td>
</tr>
<tr>
<th>Title</th>
<td>{post.title}</td>
</tr>
<tr>
<th>Body</th>
<td>{post.body}</td>
</tr>
<tr>
<th>Created at</th>
<td>{timeTag(post.createdAt)}</td>
</tr>
<tr>
<th>Updated at</th>
<td>{timeTag(post.updatedAt)}</td>
</tr>
</tbody>
</table>
</div>
<nav className="rw-button-group">
<Link
to={routes.editPost({ id: post.id })}
className="rw-button rw-button-blue"
>
Edit
</Link>
<button
type="button"
className="rw-button rw-button-red"
onClick={() => onDeleteClick(post.id)}
>
Delete
</button>
</nav>
</>
)
}
export default Post

View File

@ -0,0 +1,36 @@
import type { FindPostById, FindPostByIdVariables } from 'types/graphql'
import type {
CellSuccessProps,
CellFailureProps,
TypedDocumentNode,
} from '@redwoodjs/web'
import Post from 'src/components/Post/Post'
export const QUERY: TypedDocumentNode<FindPostById, FindPostByIdVariables> =
gql`
query FindPostById($id: Int!) {
post: post(id: $id) {
id
title
body
createdAt
updatedAt
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => <div>Post not found</div>
export const Failure = ({ error }: CellFailureProps<FindPostByIdVariables>) => (
<div className="rw-cell-error">{error?.message}</div>
)
export const Success = ({
post,
}: CellSuccessProps<FindPostById, FindPostByIdVariables>) => {
return <Post post={post} />
}

View File

@ -0,0 +1,83 @@
import type { EditPostById, UpdatePostInput } from 'types/graphql'
import type { RWGqlError } from '@redwoodjs/forms'
import {
Form,
FormError,
FieldError,
Label,
TextField,
Submit,
} from '@redwoodjs/forms'
type FormPost = NonNullable<EditPostById['post']>
interface PostFormProps {
post?: EditPostById['post']
onSave: (data: UpdatePostInput, id?: FormPost['id']) => void
error: RWGqlError
loading: boolean
}
const PostForm = (props: PostFormProps) => {
const onSubmit = (data: FormPost) => {
props.onSave(data, props?.post?.id)
}
return (
<div className="rw-form-wrapper">
<Form<FormPost> onSubmit={onSubmit} error={props.error}>
<FormError
error={props.error}
wrapperClassName="rw-form-error-wrapper"
titleClassName="rw-form-error-title"
listClassName="rw-form-error-list"
/>
<Label
name="title"
className="rw-label"
errorClassName="rw-label rw-label-error"
>
Title
</Label>
<TextField
name="title"
defaultValue={props.post?.title}
className="rw-input"
errorClassName="rw-input rw-input-error"
validation={{ required: true }}
/>
<FieldError name="title" className="rw-field-error" />
<Label
name="body"
className="rw-label"
errorClassName="rw-label rw-label-error"
>
Body
</Label>
<TextField
name="body"
defaultValue={props.post?.body}
className="rw-input"
errorClassName="rw-input rw-input-error"
validation={{ required: true }}
/>
<FieldError name="body" className="rw-field-error" />
<div className="rw-button-group">
<Submit disabled={props.loading} className="rw-button rw-button-blue">
Save
</Submit>
</div>
</Form>
</div>
)
}
export default PostForm

View File

@ -0,0 +1,102 @@
import type {
DeletePostMutation,
DeletePostMutationVariables,
FindPosts,
} from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import { useMutation } from '@redwoodjs/web'
import type { TypedDocumentNode } from '@redwoodjs/web'
import { toast } from '@redwoodjs/web/toast'
import { QUERY } from 'src/components/Post/PostsCell'
import { timeTag, truncate } from 'src/lib/formatters'
const DELETE_POST_MUTATION: TypedDocumentNode<
DeletePostMutation,
DeletePostMutationVariables
> = gql`
mutation DeletePostMutation($id: Int!) {
deletePost(id: $id) {
id
}
}
`
const PostsList = ({ posts }: FindPosts) => {
const [deletePost] = useMutation(DELETE_POST_MUTATION, {
onCompleted: () => {
toast.success('Post deleted')
},
onError: (error) => {
toast.error(error.message)
},
// This refetches the query on the list page. Read more about other ways to
// update the cache over here:
// https://www.apollographql.com/docs/react/data/mutations/#making-all-other-cache-updates
refetchQueries: [{ query: QUERY }],
awaitRefetchQueries: true,
})
const onDeleteClick = (id: DeletePostMutationVariables['id']) => {
if (confirm('Are you sure you want to delete post ' + id + '?')) {
deletePost({ variables: { id } })
}
}
return (
<div className="rw-segment rw-table-wrapper-responsive">
<table className="rw-table">
<thead>
<tr>
<th>Id</th>
<th>Title</th>
<th>Body</th>
<th>Created at</th>
<th>Updated at</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{posts.map((post) => (
<tr key={post.id}>
<td>{truncate(post.id)}</td>
<td>{truncate(post.title)}</td>
<td>{truncate(post.body)}</td>
<td>{timeTag(post.createdAt)}</td>
<td>{timeTag(post.updatedAt)}</td>
<td>
<nav className="rw-table-actions">
<Link
to={routes.post({ id: post.id })}
title={'Show post ' + post.id + ' detail'}
className="rw-button rw-button-small"
>
Show
</Link>
<Link
to={routes.editPost({ id: post.id })}
title={'Edit post ' + post.id}
className="rw-button rw-button-small rw-button-blue"
>
Edit
</Link>
<button
type="button"
title={'Delete post ' + post.id}
className="rw-button rw-button-small rw-button-red"
onClick={() => onDeleteClick(post.id)}
>
Delete
</button>
</nav>
</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
export default PostsList

View File

@ -0,0 +1,45 @@
import type { FindPosts, FindPostsVariables } from 'types/graphql'
import { Link, routes } from '@redwoodjs/router'
import type {
CellSuccessProps,
CellFailureProps,
TypedDocumentNode,
} from '@redwoodjs/web'
import Posts from 'src/components/Post/Posts'
export const QUERY: TypedDocumentNode<FindPosts, FindPostsVariables> = gql`
query FindPosts {
posts {
id
title
body
createdAt
updatedAt
}
}
`
export const Loading = () => <div>Loading...</div>
export const Empty = () => {
return (
<div className="rw-text-center">
No posts yet.{' '}
<Link to={routes.newPost()} className="rw-link">
Create one?
</Link>
</div>
)
}
export const Failure = ({ error }: CellFailureProps<FindPosts>) => (
<div className="rw-cell-error">{error?.message}</div>
)
export const Success = ({
posts,
}: CellSuccessProps<FindPosts, FindPostsVariables>) => {
return <Posts posts={posts} />
}