Wipe slate clean
This commit is contained in:
@@ -1,29 +0,0 @@
|
|||||||
export const schema = gql`
|
|
||||||
type Contact {
|
|
||||||
id: Int!
|
|
||||||
name: String!
|
|
||||||
email: String!
|
|
||||||
message: String!
|
|
||||||
createdAt: DateTime!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
contacts: [Contact!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
input CreateContactInput {
|
|
||||||
name: String!
|
|
||||||
email: String!
|
|
||||||
message: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdateContactInput {
|
|
||||||
name: String
|
|
||||||
email: String
|
|
||||||
message: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
createContact(input: CreateContactInput!): Contact
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
export const schema = gql`
|
|
||||||
type Part {
|
|
||||||
id: Int!
|
|
||||||
title: String!
|
|
||||||
description: String!
|
|
||||||
code: String!
|
|
||||||
mainImage: String!
|
|
||||||
createdAt: DateTime!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
parts: [Part!]!
|
|
||||||
part(id: Int!): Part
|
|
||||||
}
|
|
||||||
|
|
||||||
input CreatePartInput {
|
|
||||||
title: String!
|
|
||||||
description: String!
|
|
||||||
code: String
|
|
||||||
mainImage: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdatePartInput {
|
|
||||||
title: String
|
|
||||||
description: String
|
|
||||||
code: String
|
|
||||||
mainImage: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
createPart(input: CreatePartInput!): Part!
|
|
||||||
updatePart(id: Int!, input: UpdatePartInput!): Part!
|
|
||||||
deletePart(id: Int!): Part!
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
export const schema = gql`
|
|
||||||
type Post {
|
|
||||||
id: Int!
|
|
||||||
title: String!
|
|
||||||
body: String!
|
|
||||||
createdAt: DateTime!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
posts: [Post!]!
|
|
||||||
post(id: Int!): Post
|
|
||||||
}
|
|
||||||
|
|
||||||
input CreatePostInput {
|
|
||||||
title: String!
|
|
||||||
body: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdatePostInput {
|
|
||||||
title: String
|
|
||||||
body: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
createPost(input: CreatePostInput!): Post!
|
|
||||||
updatePost(id: Int!, input: UpdatePostInput!): Post!
|
|
||||||
deletePost(id: Int!): Post!
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
export const schema = gql`
|
|
||||||
type User {
|
|
||||||
id: Int!
|
|
||||||
email: String!
|
|
||||||
createdAt: DateTime!
|
|
||||||
updatedAt: DateTime!
|
|
||||||
image: String
|
|
||||||
bio: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Query {
|
|
||||||
users: [User!]!
|
|
||||||
user(id: Int!): User
|
|
||||||
}
|
|
||||||
|
|
||||||
input CreateUserInput {
|
|
||||||
email: String!
|
|
||||||
# issuer: String!
|
|
||||||
image: String
|
|
||||||
bio: String
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdateUserInput {
|
|
||||||
email: String
|
|
||||||
# issuer: String
|
|
||||||
image: String
|
|
||||||
bio: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
createUser(input: CreateUserInput!): User!
|
|
||||||
updateUser(id: Int!, input: UpdateUserInput!): User!
|
|
||||||
deleteUser(id: Int!): User!
|
|
||||||
}
|
|
||||||
`
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { UserInputError } from '@redwoodjs/api'
|
|
||||||
|
|
||||||
import { db } from 'src/lib/db'
|
|
||||||
|
|
||||||
|
|
||||||
const validate = (input) => {
|
|
||||||
if (input.email && !input.email.match(/[^@]+@[^.]+\..+/)) {
|
|
||||||
throw new UserInputError("Can't create new contact", {
|
|
||||||
messages: {
|
|
||||||
email: ['is not formatted like an email address'],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const contacts = () => {
|
|
||||||
return db.contact.findMany()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createContact = ({ input }) => {
|
|
||||||
|
|
||||||
validate(input)
|
|
||||||
return db.contact.create({ data: input })
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
import { contacts } from './contacts'
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('contacts', () => {
|
|
||||||
it('returns true', () => {
|
|
||||||
expect(true).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { db } from 'src/lib/db'
|
|
||||||
|
|
||||||
export const parts = () => {
|
|
||||||
return db.part.findMany()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const part = ({ id }) => {
|
|
||||||
return db.part.findOne({
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createPart = ({ input }) => {
|
|
||||||
return db.part.create({
|
|
||||||
data: input,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updatePart = ({ id, input }) => {
|
|
||||||
return db.part.update({
|
|
||||||
data: input,
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deletePart = ({ id }) => {
|
|
||||||
return db.part.delete({
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
import { parts } from './parts'
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('parts', () => {
|
|
||||||
it('returns true', () => {
|
|
||||||
expect(true).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { db } from 'src/lib/db'
|
|
||||||
import { requireAuth } from 'src/lib/auth'
|
|
||||||
|
|
||||||
export const posts = () => {
|
|
||||||
return db.post.findMany()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const post = ({ id }) => {
|
|
||||||
return db.post.findOne({
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createPost = ({ input }) => {
|
|
||||||
requireAuth()
|
|
||||||
return db.post.create({
|
|
||||||
data: input,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updatePost = ({ id, input }) => {
|
|
||||||
requireAuth()
|
|
||||||
return db.post.update({
|
|
||||||
data: input,
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deletePost = ({ id }) => {
|
|
||||||
requireAuth()
|
|
||||||
return db.post.delete({
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
import { posts } from './posts'
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('posts', () => {
|
|
||||||
it('returns true', () => {
|
|
||||||
expect(true).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { db } from 'src/lib/db'
|
|
||||||
import { requireAuth } from 'src/lib/auth'
|
|
||||||
|
|
||||||
export const users = () => {
|
|
||||||
requireAuth({ role: 'admin' })
|
|
||||||
return db.user.findMany()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const user = ({ id }) => {
|
|
||||||
requireAuth()
|
|
||||||
return db.user.findOne({
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createUser = ({ input }) => {
|
|
||||||
requireAuth({ role: 'admin' })
|
|
||||||
return createUserInsecure({input})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createUserInsecure = ({ input }) => {
|
|
||||||
return db.user.create({
|
|
||||||
data: input,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateUser = ({ id, input }) => {
|
|
||||||
requireAuth()
|
|
||||||
return db.user.update({
|
|
||||||
data: input,
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deleteUser = ({ id }) => {
|
|
||||||
requireAuth({ role: 'admin' })
|
|
||||||
return db.user.delete({
|
|
||||||
where: { id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
import { users } from './users'
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe('users', () => {
|
|
||||||
it('returns true', () => {
|
|
||||||
expect(true).toBe(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -12,30 +12,8 @@ import { Router, Route, Private } from '@redwoodjs/router'
|
|||||||
const Routes = () => {
|
const Routes = () => {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
{/* TODO add add min role to users and users/new */}
|
|
||||||
<Private unauthenticated="home" role="admin">
|
|
||||||
<Route path="/users" page={UsersPage} name="users" />
|
|
||||||
<Route path="/users/new" page={NewUserPage} name="newUser" />
|
|
||||||
</Private>
|
|
||||||
<Private unauthenticated="home">
|
|
||||||
<Route path="/users/{id:Int}/edit" page={EditUserPage} name="editUser" />
|
|
||||||
</Private>
|
|
||||||
<Route path="/users/{id:Int}" page={UserPage} name="user" />
|
|
||||||
<Route path="/contact" page={ContactPage} name="contact" />
|
|
||||||
<Route path="/parts/new" page={NewPartPage} name="newPart" />
|
|
||||||
<Route path="/parts/{id:Int}/edit" page={EditPartPage} name="editPart" />
|
|
||||||
<Route path="/parts/{id:Int}/ide" page={IdePartPage} name="partIde" />
|
|
||||||
<Route path="/parts/{id:Int}" page={PartPage} name="part" />
|
|
||||||
<Route path="/parts" page={PartsPage} name="parts" />
|
|
||||||
<Route path="/blog-post/{id:Int}" page={BlogPostPage} name="blogPost" />
|
|
||||||
<Private unauthenticated="home">
|
|
||||||
<Route path="/admin/posts/new" page={NewPostPage} name="newPost" />
|
|
||||||
<Route path="/admin/posts/{id:Int}/edit" page={EditPostPage} name="editPost" />
|
|
||||||
<Route path="/admin/posts/{id:Int}" page={PostPage} name="post" />
|
|
||||||
<Route path="/admin/posts" page={PostsPage} name="posts" />
|
|
||||||
</Private>
|
|
||||||
<Route path="/about" page={AboutPage} name="about" />
|
|
||||||
<Route path="/" page={PartsPage} name="home" />
|
<Route path="/" page={PartsPage} name="home" />
|
||||||
|
<Route path="/blah/*" page={PartsPage} name="home" />
|
||||||
<Route notfound page={NotFoundPage} />
|
<Route notfound page={NotFoundPage} />
|
||||||
</Router>
|
</Router>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
const BlogPost = ({ post }) => {
|
|
||||||
return (
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h2>
|
|
||||||
<Link to={routes.blogPost({ id: post.id })}>{post.title}</Link>
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<div>{post.body}</div>
|
|
||||||
</article>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BlogPost
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import BlogPost from './BlogPost'
|
|
||||||
|
|
||||||
export const generated = () => {
|
|
||||||
return <BlogPost />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Components/BlogPost' }
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { render } from '@redwoodjs/testing'
|
|
||||||
|
|
||||||
import BlogPost from './BlogPost'
|
|
||||||
|
|
||||||
describe('BlogPost', () => {
|
|
||||||
it('renders successfully', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<BlogPost />)
|
|
||||||
}).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import BlogPost from 'src/components/BlogPost'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query BlogPostQuery($id: Int!) {
|
|
||||||
post(id: $id) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => <div>Empty</div>
|
|
||||||
|
|
||||||
export const Failure = ({ error }) => <div>Error: {error.message}</div>
|
|
||||||
|
|
||||||
export const Success = ({ post }) => {
|
|
||||||
return <BlogPost post={post} />
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Define your own mock data here:
|
|
||||||
export const standard = (/* vars, { ctx, req } */) => ({
|
|
||||||
blogPost: {
|
|
||||||
id: 42,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Loading, Empty, Failure, Success } from './BlogPostCell'
|
|
||||||
import { standard } from './BlogPostCell.mock'
|
|
||||||
|
|
||||||
export const loading = () => {
|
|
||||||
return Loading ? <Loading /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const empty = () => {
|
|
||||||
return Empty ? <Empty /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const failure = () => {
|
|
||||||
return Failure ? <Failure error={new Error('Oh no')} /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const success = () => {
|
|
||||||
return Success ? <Success {...standard()} /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Cells/BlogPostCell' }
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { render, screen } from '@redwoodjs/testing'
|
|
||||||
import { Loading, Empty, Failure, Success } from './BlogPostCell'
|
|
||||||
import { standard } from './BlogPostCell.mock'
|
|
||||||
|
|
||||||
describe('BlogPostCell', () => {
|
|
||||||
test('Loading renders successfully', () => {
|
|
||||||
render(<Loading />)
|
|
||||||
// Use screen.debug() to see output
|
|
||||||
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Empty renders successfully', async () => {
|
|
||||||
render(<Empty />)
|
|
||||||
expect(screen.getByText('Empty')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Failure renders successfully', async () => {
|
|
||||||
render(<Failure error={new Error('Oh no')} />)
|
|
||||||
expect(screen.getByText(/Oh no/i)).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Success renders successfully', async () => {
|
|
||||||
render(<Success blogPost={standard().blogPost} />)
|
|
||||||
expect(screen.getByText(/42/i)).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
import BlogPost from 'src/components/BlogPost'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query BlogPostsQuery {
|
|
||||||
posts {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => <div>Empty</div>
|
|
||||||
|
|
||||||
export const Failure = ({ error }) => <div>Error: {error.message}</div>
|
|
||||||
|
|
||||||
export const Success = ({ posts }) => {
|
|
||||||
return posts.map((post) => <BlogPost key={post.id} post={post} />)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Define your own mock data here:
|
|
||||||
export const standard = (/* vars, { ctx, req } */) => ({
|
|
||||||
blogPosts: {
|
|
||||||
id: 42,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Loading, Empty, Failure, Success } from './BlogPostsCell'
|
|
||||||
import { standard } from './BlogPostsCell.mock'
|
|
||||||
|
|
||||||
export const loading = () => {
|
|
||||||
return Loading ? <Loading /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const empty = () => {
|
|
||||||
return Empty ? <Empty /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const failure = () => {
|
|
||||||
return Failure ? <Failure error={new Error('Oh no')} /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const success = () => {
|
|
||||||
return Success ? <Success {...standard()} /> : null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Cells/BlogPostsCell' }
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { render, screen } from '@redwoodjs/testing'
|
|
||||||
import { Loading, Empty, Failure, Success } from './BlogPostsCell'
|
|
||||||
import { standard } from './BlogPostsCell.mock'
|
|
||||||
|
|
||||||
describe('BlogPostsCell', () => {
|
|
||||||
test('Loading renders successfully', () => {
|
|
||||||
render(<Loading />)
|
|
||||||
// Use screen.debug() to see output
|
|
||||||
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Empty renders successfully', async () => {
|
|
||||||
render(<Empty />)
|
|
||||||
expect(screen.getByText('Empty')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Failure renders successfully', async () => {
|
|
||||||
render(<Failure error={new Error('Oh no')} />)
|
|
||||||
expect(screen.getByText(/Oh no/i)).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Success renders successfully', async () => {
|
|
||||||
render(<Success blogPosts={standard().blogPosts} />)
|
|
||||||
expect(screen.getByText(/42/i)).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { useMutation } from '@redwoodjs/web'
|
|
||||||
import PartForm from 'src/components/PartForm'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query FIND_PART_BY_ID($id: Int!) {
|
|
||||||
part: part(id: $id) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
description
|
|
||||||
code
|
|
||||||
mainImage
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const UPDATE_PART_MUTATION = gql`
|
|
||||||
mutation UpdatePartMutation($id: Int!, $input: UpdatePartInput!) {
|
|
||||||
updatePart(id: $id, input: $input) {
|
|
||||||
id
|
|
||||||
code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Success = ({ part }) => {
|
|
||||||
const [updatePart, { loading, error }] = useMutation(UPDATE_PART_MUTATION)
|
|
||||||
|
|
||||||
const onSave = (input, id) => updatePart({ variables: { id, input } })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PartForm part={part} onSave={onSave} error={error} loading={loading} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import PostForm from 'src/components/PostForm'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query FIND_POST_BY_ID($id: Int!) {
|
|
||||||
post: post(id: $id) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const UPDATE_POST_MUTATION = gql`
|
|
||||||
mutation UpdatePostMutation($id: Int!, $input: UpdatePostInput!) {
|
|
||||||
updatePost(id: $id, input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Success = ({ post }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [updatePost, { loading, error }] = useMutation(UPDATE_POST_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.posts())
|
|
||||||
addMessage('Post updated.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSave = (input, 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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import UserForm from 'src/components/UserForm'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query FIND_USER_BY_ID($id: Int!) {
|
|
||||||
user: user(id: $id) {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
image
|
|
||||||
bio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const UPDATE_USER_MUTATION = gql`
|
|
||||||
mutation UpdateUserMutation($id: Int!, $input: UpdateUserInput!) {
|
|
||||||
updateUser(id: $id, input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Success = ({ user }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [updateUser, { loading, error }] = useMutation(UPDATE_USER_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.users())
|
|
||||||
addMessage('User updated.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSave = (input, id) => {
|
|
||||||
updateUser({ variables: { id, input } })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rw-segment">
|
|
||||||
<header className="rw-segment-header">
|
|
||||||
<h2 className="rw-heading rw-heading-secondary">Edit User {user.id}</h2>
|
|
||||||
</header>
|
|
||||||
<div className="rw-segment-main">
|
|
||||||
<UserForm user={user} onSave={onSave} error={error} loading={loading} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import PartForm from 'src/components/PartForm'
|
|
||||||
|
|
||||||
const CREATE_PART_MUTATION = gql`
|
|
||||||
mutation CreatePartMutation($input: CreatePartInput!) {
|
|
||||||
createPart(input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const NewPart = () => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [createPart, { loading, error }] = useMutation(CREATE_PART_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.parts())
|
|
||||||
addMessage('Part created.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSave = (input) => {
|
|
||||||
createPart({ variables: { input } })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rw-segment">
|
|
||||||
<header className="rw-segment-header">
|
|
||||||
<h2 className="rw-heading rw-heading-secondary">New Part</h2>
|
|
||||||
</header>
|
|
||||||
<div className="rw-segment-main">
|
|
||||||
<PartForm onSave={onSave} loading={loading} error={error} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewPart
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import PostForm from 'src/components/PostForm'
|
|
||||||
|
|
||||||
const CREATE_POST_MUTATION = gql`
|
|
||||||
mutation CreatePostMutation($input: CreatePostInput!) {
|
|
||||||
createPost(input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const NewPost = () => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.posts())
|
|
||||||
addMessage('Post created.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSave = (input) => {
|
|
||||||
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
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import UserForm from 'src/components/UserForm'
|
|
||||||
|
|
||||||
const CREATE_USER_MUTATION = gql`
|
|
||||||
mutation CreateUserMutation($input: CreateUserInput!) {
|
|
||||||
createUser(input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const NewUser = () => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [createUser, { loading, error }] = useMutation(CREATE_USER_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.users())
|
|
||||||
addMessage('User created.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSave = (input) => {
|
|
||||||
createUser({ variables: { input } })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rw-segment">
|
|
||||||
<header className="rw-segment-header">
|
|
||||||
<h2 className="rw-heading rw-heading-secondary">New User</h2>
|
|
||||||
</header>
|
|
||||||
<div className="rw-segment-main">
|
|
||||||
<UserForm onSave={onSave} loading={loading} error={error} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewUser
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { Link, routes, navigate } from '@redwoodjs/router'
|
|
||||||
import { initialize } from 'src/cascade/js/MainPage/CascadeMain'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
|
|
||||||
const DELETE_PART_MUTATION = gql`
|
|
||||||
mutation DeletePartMutation($id: Int!) {
|
|
||||||
deletePart(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const Part = ({ part, saveCode, loading, error}) => {
|
|
||||||
const [code, setCode] = useState(part.code)
|
|
||||||
useEffect(() => {
|
|
||||||
const sickCallback = (code) => setCode(code)
|
|
||||||
initialize(sickCallback, part.code)
|
|
||||||
}, [])
|
|
||||||
const hasChanges = code !== part.code
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [deletePart] = useMutation(DELETE_PART_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.parts())
|
|
||||||
addMessage('Part deleted.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onDeleteClick = (id) => {
|
|
||||||
if (confirm('Are you sure you want to delete part ' + id + '?')) {
|
|
||||||
deletePart({ variables: { id } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="rw-segment">
|
|
||||||
<header className="rw-segment-header">
|
|
||||||
<h2 className="rw-heading rw-heading-secondary">
|
|
||||||
Part {part.id} Detail
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<table className="rw-table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Title</th>
|
|
||||||
<td>{part.title}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Description</th>
|
|
||||||
<td>{part.description}</td>
|
|
||||||
</tr>
|
|
||||||
{/* <tr>
|
|
||||||
<th>Code</th>
|
|
||||||
<td>{part.code}</td>
|
|
||||||
</tr> */}
|
|
||||||
{/* <tr>
|
|
||||||
<th>Main image</th>
|
|
||||||
<td>{part.mainImage}</td>
|
|
||||||
</tr> */}
|
|
||||||
{/* <tr>
|
|
||||||
<th>Created at</th>
|
|
||||||
<td>{timeTag(part.createdAt)}</td>
|
|
||||||
</tr> */}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<img src={part.mainImage} />
|
|
||||||
</div>
|
|
||||||
<nav className="rw-button-group">
|
|
||||||
{loading && 'Loading...'}
|
|
||||||
{hasChanges && !loading && <button onClick={() => saveCode({code}, part.id)} className="rw-button rw-button-blue">
|
|
||||||
Save Changes
|
|
||||||
</button>}
|
|
||||||
</nav>
|
|
||||||
<div>
|
|
||||||
<div id="topnav" className="topnav">
|
|
||||||
<a href="https://github.com/zalo/CascadeStudio">Cascade Studio 0.0.6</a>
|
|
||||||
<a href="#" id="main-proj-button" title="Sets this project to save in local storage." onClick={() => makeMainProject()}>Make Main Project</a>
|
|
||||||
<a href="#" title="Save Project to .json" onClick={() => saveProject()}>Save Project</a>
|
|
||||||
<label htmlFor="project-file" title="Load Project from .json">Load Project
|
|
||||||
<input
|
|
||||||
id="project-file"
|
|
||||||
name="project-file"
|
|
||||||
type="file"
|
|
||||||
accept=".json"
|
|
||||||
style={{display:'none'}}
|
|
||||||
onInput={() => loadProject()}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<a href="#" onClick={() => threejsViewport.saveShapeSTEP()}>Save STEP</a>
|
|
||||||
<a href="#" onClick={() => threejsViewport.saveShapeSTL()}>Save STL</a>
|
|
||||||
<a href="#" onClick={() => threejsViewport.saveShapeOBJ()}>Save OBJ</a>
|
|
||||||
<label htmlFor="files" title="Import STEP, IGES, or (ASCII) STL from File">Import STEP/IGES/STL
|
|
||||||
<input id="files" name="files" type="file" accept=".iges,.step,.igs,.stp,.stl" multiple style={{display: 'none'}} onInput={ () =>loadFiles()}/>
|
|
||||||
</label>
|
|
||||||
<a href="#" title="Clears the external step/iges/stl files stored in the project." onClick={() => clearExternalFiles()}>Clear Imported Files</a>
|
|
||||||
<a href="" title="Resets the project and localstorage." onClick={() => {
|
|
||||||
window.localStorage.clear();
|
|
||||||
window.history.replaceState({}, 'Cascade Studio','?')
|
|
||||||
}}>Reset Project</a>
|
|
||||||
</div>
|
|
||||||
<div id="cascade-container" style={{height:'auto'}}>
|
|
||||||
</div>
|
|
||||||
<footer>footer</footer>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Part
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
|
|
||||||
import {QUERY as reExportQuery} from 'src/components/EditPartCell'
|
|
||||||
import Editor from "rich-markdown-editor";
|
|
||||||
|
|
||||||
export const QUERY = reExportQuery
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => <div>Empty</div>
|
|
||||||
|
|
||||||
export const Failure = ({ error }) => <div>Error: {error.message}</div>
|
|
||||||
|
|
||||||
export const Success = ({ part }) => {
|
|
||||||
console.log(part)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="max-w-7xl mx-auto">
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<img src={part.mainImage} />
|
|
||||||
</div>
|
|
||||||
<div className="bg-white p-8 my-12 min-h-md">
|
|
||||||
<h2 className="text-4xl py-4">{part.title}</h2>
|
|
||||||
<Editor
|
|
||||||
className="markdown-overrides"
|
|
||||||
defaultValue={part.description}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import {
|
|
||||||
Form,
|
|
||||||
FormError,
|
|
||||||
FieldError,
|
|
||||||
Label,
|
|
||||||
TextField,
|
|
||||||
Submit,
|
|
||||||
} from '@redwoodjs/forms'
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import { useFlash } from '@redwoodjs/web'
|
|
||||||
import ImageUploader from './ImageUploader.js'
|
|
||||||
|
|
||||||
|
|
||||||
import Editor from "rich-markdown-editor";
|
|
||||||
|
|
||||||
const PartForm = (props) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [description, setDescription] = useState(props?.part?.description)
|
|
||||||
const [imageUrl, setImageUrl] = useState(props?.part?.mainImage)
|
|
||||||
const onSubmit = async (data, e) => {
|
|
||||||
|
|
||||||
await props.onSave({
|
|
||||||
...data,
|
|
||||||
description,
|
|
||||||
mainImage: imageUrl
|
|
||||||
}, props?.part?.id)
|
|
||||||
const shouldOpenIde = e?.nativeEvent?.submitter?.dataset?.openIde
|
|
||||||
if(shouldOpenIde) {
|
|
||||||
navigate(routes.partIde({id: props?.part?.id}))
|
|
||||||
} else {
|
|
||||||
navigate(routes.part({id: props?.part?.id}))
|
|
||||||
}
|
|
||||||
addMessage('Part updated.', { classes: 'rw-flash-success' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="max-w-7xl mx-auto mt-10">
|
|
||||||
<Form 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="p-0"
|
|
||||||
errorClassName="rw-label rw-label-error"
|
|
||||||
>
|
|
||||||
Title
|
|
||||||
</Label>
|
|
||||||
<TextField
|
|
||||||
name="title"
|
|
||||||
defaultValue={props.part?.title}
|
|
||||||
className="rw-input"
|
|
||||||
errorClassName="rw-input rw-input-error"
|
|
||||||
validation={{ required: true }}
|
|
||||||
/>
|
|
||||||
<FieldError name="title" className="rw-field-error" />
|
|
||||||
|
|
||||||
<ImageUploader onImageUpload={({cloudinaryPublicId}) => setImageUrl(cloudinaryPublicId)} />
|
|
||||||
|
|
||||||
|
|
||||||
<Label
|
|
||||||
name="description"
|
|
||||||
className="p-0"
|
|
||||||
errorClassName="rw-label rw-label-error"
|
|
||||||
>
|
|
||||||
Description
|
|
||||||
</Label>
|
|
||||||
<div name="description" className="markdown-overrides bg-white p-12 my-10 min-h-md">
|
|
||||||
<Editor
|
|
||||||
defaultValue={props.part?.description}
|
|
||||||
onChange={(valueFn) => setDescription(valueFn())}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="rw-button-group">
|
|
||||||
<Submit disabled={props.loading} className="rw-button rw-button-blue">
|
|
||||||
Save
|
|
||||||
</Submit>
|
|
||||||
<Submit disabled={props.loading} data-open-ide={true} className="rw-button rw-button-blue">
|
|
||||||
Save and open IDE
|
|
||||||
</Submit>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PartForm
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
import { Image as CloudinaryImage } from 'cloudinary-react'
|
|
||||||
|
|
||||||
import avatar from 'src/assets/harold.jpg'
|
|
||||||
|
|
||||||
const DELETE_PART_MUTATION = gql`
|
|
||||||
mutation DeletePartMutation($id: Int!) {
|
|
||||||
deletePart(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const MAX_STRING_LENGTH = 150
|
|
||||||
|
|
||||||
const truncate = (text) => {
|
|
||||||
let output = text
|
|
||||||
if (text && text.length > MAX_STRING_LENGTH) {
|
|
||||||
output = output.substring(0, MAX_STRING_LENGTH) + '...'
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonTruncate = (obj) => {
|
|
||||||
return truncate(JSON.stringify(obj, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeTag = (datetime) => {
|
|
||||||
return (
|
|
||||||
<time dateTime={datetime} title={datetime}>
|
|
||||||
{new Date(datetime).toUTCString()}
|
|
||||||
</time>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkboxInputTag = (checked) => {
|
|
||||||
return <input type="checkbox" checked={checked} disabled />
|
|
||||||
}
|
|
||||||
|
|
||||||
const PartsList = ({ parts }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [deletePart] = useMutation(DELETE_PART_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
addMessage('Part deleted.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onDeleteClick = (id) => {
|
|
||||||
if (confirm('Are you sure you want to delete part ' + id + '?')) {
|
|
||||||
deletePart({ variables: { id }, refetchQueries: ['PARTS'] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="max-w-xs sm:max-w-sm md:max-w-2xl lg:max-w-5xl xl:max-w-6xl mx-auto grid gap-8 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid- cols-4">
|
|
||||||
{parts.map((part) => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={routes.part({ id: part.id })}
|
|
||||||
title={'Show part ' + part.id + ' detail'}
|
|
||||||
key={part.id}
|
|
||||||
className="relative bg-gray-900 rounded-t-2xl"
|
|
||||||
>
|
|
||||||
<div className="rounded-t-2xl bg-gray-900">
|
|
||||||
<div className="flex items-center p-2 text-indigo-200">
|
|
||||||
<div className="h-full absolute inset-0 text-6xl flex items-center justify-center text-indigo-700" ><span>?</span></div>
|
|
||||||
<div className="mr-4">
|
|
||||||
<img src={avatar} className="rounded-full h-10 w-10" />
|
|
||||||
</div>
|
|
||||||
<h3>{part.title}</h3>
|
|
||||||
</div>
|
|
||||||
<div className="relative z-10">
|
|
||||||
<CloudinaryImage
|
|
||||||
className="object-cover w-full rounded shadow"
|
|
||||||
cloudName="irevdev"
|
|
||||||
publicId={part.mainImage}
|
|
||||||
width="300"
|
|
||||||
crop="scale"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)})}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PartsList
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
import Parts from 'src/components/Parts'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query PARTS {
|
|
||||||
parts {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
description
|
|
||||||
code
|
|
||||||
mainImage
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => {
|
|
||||||
return (
|
|
||||||
<div className="rw-text-center">
|
|
||||||
{'No parts yet. '}
|
|
||||||
<Link to={routes.newPart()} className="rw-link">
|
|
||||||
{'Create one?'}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Success = ({ parts }) => {
|
|
||||||
return <Parts parts={parts} />
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { Link, routes, navigate } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
const DELETE_POST_MUTATION = gql`
|
|
||||||
mutation DeletePostMutation($id: Int!) {
|
|
||||||
deletePost(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const jsonDisplay = (obj) => {
|
|
||||||
return (
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(obj, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeTag = (datetime) => {
|
|
||||||
return (
|
|
||||||
<time dateTime={datetime} title={datetime}>
|
|
||||||
{new Date(datetime).toUTCString()}
|
|
||||||
</time>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkboxInputTag = (checked) => {
|
|
||||||
return <input type="checkbox" checked={checked} disabled />
|
|
||||||
}
|
|
||||||
|
|
||||||
const Post = ({ post }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [deletePost] = useMutation(DELETE_POST_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.posts())
|
|
||||||
addMessage('Post deleted.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onDeleteClick = (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>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<nav className="rw-button-group">
|
|
||||||
<Link
|
|
||||||
to={routes.editPost({ id: post.id })}
|
|
||||||
className="rw-button rw-button-blue"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="rw-button rw-button-red"
|
|
||||||
onClick={() => onDeleteClick(post.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Post
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import Post from 'src/components/Post'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query FIND_POST_BY_ID($id: Int!) {
|
|
||||||
post: post(id: $id) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => <div>Post not found</div>
|
|
||||||
|
|
||||||
export const Success = ({ post }) => {
|
|
||||||
return <Post post={post} />
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import {
|
|
||||||
Form,
|
|
||||||
FormError,
|
|
||||||
FieldError,
|
|
||||||
Label,
|
|
||||||
TextField,
|
|
||||||
Submit,
|
|
||||||
} from '@redwoodjs/forms'
|
|
||||||
|
|
||||||
const PostForm = (props) => {
|
|
||||||
const onSubmit = (data) => {
|
|
||||||
props.onSave(data, props?.post?.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rw-form-wrapper">
|
|
||||||
<Form 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
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
const DELETE_POST_MUTATION = gql`
|
|
||||||
mutation DeletePostMutation($id: Int!) {
|
|
||||||
deletePost(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const MAX_STRING_LENGTH = 150
|
|
||||||
|
|
||||||
const truncate = (text) => {
|
|
||||||
let output = text
|
|
||||||
if (text && text.length > MAX_STRING_LENGTH) {
|
|
||||||
output = output.substring(0, MAX_STRING_LENGTH) + '...'
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonTruncate = (obj) => {
|
|
||||||
return truncate(JSON.stringify(obj, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeTag = (datetime) => {
|
|
||||||
return (
|
|
||||||
<time dateTime={datetime} title={datetime}>
|
|
||||||
{new Date(datetime).toUTCString()}
|
|
||||||
</time>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkboxInputTag = (checked) => {
|
|
||||||
return <input type="checkbox" checked={checked} disabled />
|
|
||||||
}
|
|
||||||
|
|
||||||
const PostsList = ({ posts }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [deletePost] = useMutation(DELETE_POST_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
addMessage('Post deleted.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onDeleteClick = (id) => {
|
|
||||||
if (confirm('Are you sure you want to delete post ' + id + '?')) {
|
|
||||||
deletePost({ variables: { id }, refetchQueries: ['POSTS'] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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> </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>
|
|
||||||
<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>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
title={'Delete post ' + post.id}
|
|
||||||
className="rw-button rw-button-small rw-button-red"
|
|
||||||
onClick={() => onDeleteClick(post.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostsList
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
import Posts from 'src/components/Posts'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query POSTS {
|
|
||||||
posts {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
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 Success = ({ posts }) => {
|
|
||||||
return <Posts posts={posts} />
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { Link, routes, navigate } from '@redwoodjs/router'
|
|
||||||
import { Image as CloudinaryImage } from 'cloudinary-react'
|
|
||||||
const DELETE_USER_MUTATION = gql`
|
|
||||||
mutation DeleteUserMutation($id: Int!) {
|
|
||||||
deleteUser(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const jsonDisplay = (obj) => {
|
|
||||||
return (
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(obj, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeTag = (datetime) => {
|
|
||||||
return (
|
|
||||||
<time dateTime={datetime} title={datetime}>
|
|
||||||
{new Date(datetime).toUTCString()}
|
|
||||||
</time>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkboxInputTag = (checked) => {
|
|
||||||
return <input type="checkbox" checked={checked} disabled />
|
|
||||||
}
|
|
||||||
|
|
||||||
const User = ({ user }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [deleteUser] = useMutation(DELETE_USER_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
navigate(routes.users())
|
|
||||||
addMessage('User deleted.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onDeleteClick = (id) => {
|
|
||||||
if (confirm('Are you sure you want to delete user ' + id + '?')) {
|
|
||||||
deleteUser({ variables: { id } })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="rw-segment">
|
|
||||||
<header className="rw-segment-header">
|
|
||||||
<h2 className="rw-heading rw-heading-secondary">
|
|
||||||
User {user.id} Detail
|
|
||||||
</h2>
|
|
||||||
</header>
|
|
||||||
<table className="rw-table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th>Id</th>
|
|
||||||
<td>{user.id}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Email</th>
|
|
||||||
<td>{user.email}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Created at</th>
|
|
||||||
<td>{timeTag(user.createdAt)}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Updated at</th>
|
|
||||||
<td>{timeTag(user.updatedAt)}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Image</th>
|
|
||||||
<td><CloudinaryImage
|
|
||||||
className="object-cover w-full rounded shadow"
|
|
||||||
cloudName="irevdev"
|
|
||||||
publicId={user.image}
|
|
||||||
width="300"
|
|
||||||
crop="scale"
|
|
||||||
/></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>Bio</th>
|
|
||||||
<td>{user.bio}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<nav className="rw-button-group">
|
|
||||||
<Link
|
|
||||||
to={routes.editUser({ id: user.id })}
|
|
||||||
className="rw-button rw-button-blue"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="rw-button rw-button-red"
|
|
||||||
onClick={() => onDeleteClick(user.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default User
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import User from 'src/components/User'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query FIND_USER_BY_ID($id: Int!) {
|
|
||||||
user: user(id: $id) {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
image
|
|
||||||
bio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => <div>User not found</div>
|
|
||||||
|
|
||||||
export const Success = ({ user }) => {
|
|
||||||
return <User user={user} />
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import {
|
|
||||||
Form,
|
|
||||||
FormError,
|
|
||||||
FieldError,
|
|
||||||
Label,
|
|
||||||
TextField,
|
|
||||||
Submit,
|
|
||||||
} from '@redwoodjs/forms'
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { navigate, routes } from '@redwoodjs/router'
|
|
||||||
import { useFlash } from '@redwoodjs/web'
|
|
||||||
import ImageUploader from '../PartForm/ImageUploader'
|
|
||||||
const UserForm = (props) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
// const [bio, setBio] = useState(props?.user?.bio)
|
|
||||||
const [imageUrl, setImageUrl] = useState(props?.user?.image)
|
|
||||||
const onSubmit = async (data, e) => {
|
|
||||||
|
|
||||||
await props.onSave({
|
|
||||||
...data,
|
|
||||||
image: imageUrl
|
|
||||||
}, props?.user?.id)
|
|
||||||
addMessage('User updated.', { classes: 'rw-flash-success' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rw-form-wrapper">
|
|
||||||
<Form 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="email"
|
|
||||||
className="rw-label"
|
|
||||||
errorClassName="rw-label rw-label-error"
|
|
||||||
>
|
|
||||||
Email
|
|
||||||
</Label>
|
|
||||||
<TextField
|
|
||||||
name="email"
|
|
||||||
defaultValue={props.user?.email}
|
|
||||||
className="rw-input"
|
|
||||||
errorClassName="rw-input rw-input-error"
|
|
||||||
validation={{ required: true }}
|
|
||||||
/>
|
|
||||||
<FieldError name="email" className="rw-field-error" />
|
|
||||||
<ImageUploader onImageUpload={({cloudinaryPublicId}) => setImageUrl(cloudinaryPublicId)} />
|
|
||||||
|
|
||||||
<Label
|
|
||||||
name="bio"
|
|
||||||
className="rw-label"
|
|
||||||
errorClassName="rw-label rw-label-error"
|
|
||||||
>
|
|
||||||
Bio
|
|
||||||
</Label>
|
|
||||||
<TextField
|
|
||||||
name="bio"
|
|
||||||
defaultValue={props.user?.bio}
|
|
||||||
className="rw-input"
|
|
||||||
errorClassName="rw-input rw-input-error"
|
|
||||||
validation={{ required: true }}
|
|
||||||
/>
|
|
||||||
<FieldError name="bio" 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 UserForm
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
function UserPart({ userName, partName }) {
|
|
||||||
return (
|
|
||||||
<h3 className="text-xl font-roboto">
|
|
||||||
<div className="w-1 inline-block text-indigo-800 bg-indigo-800 mr-2">.</div>
|
|
||||||
<span className="text-gray-500">
|
|
||||||
{userName}
|
|
||||||
</span>
|
|
||||||
<div className="w-1 inline-block bg-gray-400 text-gray-400 mx-3 transform -skew-x-20" >.</div>
|
|
||||||
<span className="text-indigo-800">
|
|
||||||
{partName}
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserPart
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import { useMutation, useFlash } from '@redwoodjs/web'
|
|
||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
const DELETE_USER_MUTATION = gql`
|
|
||||||
mutation DeleteUserMutation($id: Int!) {
|
|
||||||
deleteUser(id: $id) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const MAX_STRING_LENGTH = 150
|
|
||||||
|
|
||||||
const truncate = (text) => {
|
|
||||||
let output = text
|
|
||||||
if (text && text.length > MAX_STRING_LENGTH) {
|
|
||||||
output = output.substring(0, MAX_STRING_LENGTH) + '...'
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonTruncate = (obj) => {
|
|
||||||
return truncate(JSON.stringify(obj, null, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeTag = (datetime) => {
|
|
||||||
return (
|
|
||||||
<time dateTime={datetime} title={datetime}>
|
|
||||||
{new Date(datetime).toUTCString()}
|
|
||||||
</time>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkboxInputTag = (checked) => {
|
|
||||||
return <input type="checkbox" checked={checked} disabled />
|
|
||||||
}
|
|
||||||
|
|
||||||
const UsersList = ({ users }) => {
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [deleteUser] = useMutation(DELETE_USER_MUTATION, {
|
|
||||||
onCompleted: () => {
|
|
||||||
addMessage('User deleted.', { classes: 'rw-flash-success' })
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const onDeleteClick = (id) => {
|
|
||||||
if (confirm('Are you sure you want to delete user ' + id + '?')) {
|
|
||||||
deleteUser({ variables: { id }, refetchQueries: ['USERS'] })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="rw-segment rw-table-wrapper-responsive">
|
|
||||||
<table className="rw-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Id</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Created at</th>
|
|
||||||
<th>Updated at</th>
|
|
||||||
<th>Image</th>
|
|
||||||
<th>Bio</th>
|
|
||||||
<th> </th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{users.map((user) => (
|
|
||||||
<tr key={user.id}>
|
|
||||||
<td>{truncate(user.id)}</td>
|
|
||||||
<td>{truncate(user.email)}</td>
|
|
||||||
<td>{timeTag(user.createdAt)}</td>
|
|
||||||
<td>{timeTag(user.updatedAt)}</td>
|
|
||||||
<td>{truncate(user.image)}</td>
|
|
||||||
<td>{truncate(user.bio)}</td>
|
|
||||||
<td>
|
|
||||||
<nav className="rw-table-actions">
|
|
||||||
<Link
|
|
||||||
to={routes.user({ id: user.id })}
|
|
||||||
title={'Show user ' + user.id + ' detail'}
|
|
||||||
className="rw-button rw-button-small"
|
|
||||||
>
|
|
||||||
Show
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to={routes.editUser({ id: user.id })}
|
|
||||||
title={'Edit user ' + user.id}
|
|
||||||
className="rw-button rw-button-small rw-button-blue"
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Link>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
title={'Delete user ' + user.id}
|
|
||||||
className="rw-button rw-button-small rw-button-red"
|
|
||||||
onClick={() => onDeleteClick(user.id)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UsersList
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
import Users from 'src/components/Users'
|
|
||||||
|
|
||||||
export const QUERY = gql`
|
|
||||||
query USERS {
|
|
||||||
users {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
image
|
|
||||||
bio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
export const Loading = () => <div>Loading...</div>
|
|
||||||
|
|
||||||
export const Empty = () => {
|
|
||||||
return (
|
|
||||||
<div className="rw-text-center">
|
|
||||||
{'No users yet. '}
|
|
||||||
<Link to={routes.newUser()} className="rw-link">
|
|
||||||
{'Create one?'}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Success = ({ users }) => {
|
|
||||||
return <Users users={users} />
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
coolGuy()
|
// coolGuy()
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
|
|
||||||
const BlogLayout = ({ children }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<header>
|
|
||||||
<h1>Redwood Blog</h1>
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<Link to={routes.about()}>About</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link to={routes.home()}>Home</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<main>{children}</main>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BlogLayout
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import BlogLayout from './BlogLayout'
|
|
||||||
|
|
||||||
export const generated = () => {
|
|
||||||
return <BlogLayout />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Layouts/BlogLayout' }
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { render } from '@redwoodjs/testing'
|
|
||||||
|
|
||||||
import BlogLayout from './BlogLayout'
|
|
||||||
|
|
||||||
describe('BlogLayout', () => {
|
|
||||||
it('renders successfully', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<BlogLayout />)
|
|
||||||
}).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
const PartsLayout = (props) => {
|
|
||||||
return (
|
|
||||||
<div className="mt-8">
|
|
||||||
<main>{props.children}</main>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PartsLayout
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
import { Flash } from '@redwoodjs/web'
|
|
||||||
|
|
||||||
const PostsLayout = (props) => {
|
|
||||||
return (
|
|
||||||
<div className="rw-scaffold">
|
|
||||||
<Flash timeout={1000} />
|
|
||||||
<header className="rw-header">
|
|
||||||
<h1 className="rw-heading rw-heading-primary">
|
|
||||||
<Link to={routes.posts()} className="rw-link">
|
|
||||||
Posts
|
|
||||||
</Link>
|
|
||||||
</h1>
|
|
||||||
<Link to={routes.newPost()} className="rw-button rw-button-green">
|
|
||||||
<div className="rw-button-icon">+</div> New Post
|
|
||||||
</Link>
|
|
||||||
</header>
|
|
||||||
<main>{props.children}</main>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostsLayout
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Link, routes } from '@redwoodjs/router'
|
|
||||||
import BlogLayout from 'src/layouts/BlogLayout'
|
|
||||||
|
|
||||||
const AboutPage = () => {
|
|
||||||
return (
|
|
||||||
<BlogLayout>
|
|
||||||
<h1>AboutPage</h1>
|
|
||||||
<p>
|
|
||||||
Find me in <tt>./web/src/pages/AboutPage/AboutPage.js</tt>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
My default route is named <tt>about</tt>, link to me with `
|
|
||||||
<Link to={routes.about()}>About</Link>`
|
|
||||||
</p>
|
|
||||||
</BlogLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AboutPage
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import AboutPage from './AboutPage'
|
|
||||||
|
|
||||||
export const generated = () => {
|
|
||||||
return <AboutPage />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Pages/AboutPage' }
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { render } from '@redwoodjs/testing'
|
|
||||||
|
|
||||||
import AboutPage from './AboutPage'
|
|
||||||
|
|
||||||
describe('AboutPage', () => {
|
|
||||||
it('renders successfully', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<AboutPage />)
|
|
||||||
}).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import BlogLayout from 'src/layouts/BlogLayout'
|
|
||||||
import BlogPostCell from 'src/components/BlogPostCell'
|
|
||||||
|
|
||||||
const BlogPostPage = ({id}) => {
|
|
||||||
return (
|
|
||||||
<BlogLayout>
|
|
||||||
<BlogPostCell id={id}/>
|
|
||||||
</BlogLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BlogPostPage
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import BlogPostPage from './BlogPostPage'
|
|
||||||
|
|
||||||
export const generated = () => {
|
|
||||||
return <BlogPostPage />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Pages/BlogPostPage' }
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { render } from '@redwoodjs/testing'
|
|
||||||
|
|
||||||
import BlogPostPage from './BlogPostPage'
|
|
||||||
|
|
||||||
describe('BlogPostPage', () => {
|
|
||||||
it('renders successfully', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<BlogPostPage />)
|
|
||||||
}).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { Form, TextField, Submit, TextAreaField, FieldError, FormError } from '@redwoodjs/forms'
|
|
||||||
import { useMutation, Flash, useFlash } from '@redwoodjs/web'
|
|
||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
|
|
||||||
const CREATE_CONTACT = gql`
|
|
||||||
mutation CreateContactMutation($input: CreateContactInput!) {
|
|
||||||
createContact(input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const ContactPage = () => {
|
|
||||||
const formMethods = useForm()
|
|
||||||
const { addMessage } = useFlash()
|
|
||||||
const [create, {loading, error}] = useMutation(CREATE_CONTACT, {
|
|
||||||
onCompleted: () => {
|
|
||||||
addMessage('Thank you for your submission!', {
|
|
||||||
style: { backgroundColor: 'green', color: 'white', padding: '1rem' }
|
|
||||||
})
|
|
||||||
formMethods.reset()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const onSubmit = async (data) => {
|
|
||||||
try {
|
|
||||||
await create({ variables: { input: data } })
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<Flash timeout={2000} />
|
|
||||||
<Form onSubmit={onSubmit} validation={{ mode: 'onBlur' }} error={error} formMethods={formMethods}>
|
|
||||||
<FormError
|
|
||||||
error={error}
|
|
||||||
wrapperStyle={{ color: 'red', backgroundColor: 'lavenderblush' }}
|
|
||||||
/>
|
|
||||||
<label htmlFor="name">Name</label>
|
|
||||||
<TextField name="name" validation={{required: true}} errorClassName="error" />
|
|
||||||
<FieldError name="name" className="error" />
|
|
||||||
|
|
||||||
<label htmlFor="email">Email</label>
|
|
||||||
<TextField name="email" validation={{required: true}} errorClassName="error" />
|
|
||||||
<FieldError name="email" className="error" />
|
|
||||||
|
|
||||||
<label htmlFor="message">Message</label>
|
|
||||||
<TextAreaField name="message" validation={{required: true}} errorClassName="error" />
|
|
||||||
<FieldError name="message" className="error" />
|
|
||||||
|
|
||||||
<Submit disabled={loading}>Save</Submit>
|
|
||||||
</Form>
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ContactPage
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import ContactPage from './ContactPage'
|
|
||||||
|
|
||||||
export const generated = () => {
|
|
||||||
return <ContactPage />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Pages/ContactPage' }
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { render } from '@redwoodjs/testing'
|
|
||||||
|
|
||||||
import ContactPage from './ContactPage'
|
|
||||||
|
|
||||||
describe('ContactPage', () => {
|
|
||||||
it('renders successfully', () => {
|
|
||||||
expect(() => {
|
|
||||||
render(<ContactPage />)
|
|
||||||
}).not.toThrow()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import EditPartCell from 'src/components/EditPartCell'
|
|
||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
|
|
||||||
const EditPartPage = ({ id }) => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<EditPartCell id={id} />
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditPartPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import PostsLayout from 'src/layouts/PostsLayout'
|
|
||||||
import EditPostCell from 'src/components/EditPostCell'
|
|
||||||
|
|
||||||
const EditPostPage = ({ id }) => {
|
|
||||||
return (
|
|
||||||
<PostsLayout>
|
|
||||||
<EditPostCell id={id} />
|
|
||||||
</PostsLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditPostPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import EditUserCell from 'src/components/EditUserCell'
|
|
||||||
|
|
||||||
const EditUserPage = ({ id }) => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<EditUserCell id={id} />
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditUserPage
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import IdePartPage from './IdePartPage'
|
|
||||||
|
|
||||||
export const generated = () => {
|
|
||||||
return <IdePartPage />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default { title: 'Pages/IdePartPage' }
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import PartsLayout from 'src/layouts/PartsLayout'
|
|
||||||
import NewPart from 'src/components/NewPart'
|
|
||||||
|
|
||||||
const NewPartPage = () => {
|
|
||||||
return (
|
|
||||||
<PartsLayout>
|
|
||||||
<NewPart />
|
|
||||||
</PartsLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewPartPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import PostsLayout from 'src/layouts/PostsLayout'
|
|
||||||
import NewPost from 'src/components/NewPost'
|
|
||||||
|
|
||||||
const NewPostPage = () => {
|
|
||||||
return (
|
|
||||||
<PostsLayout>
|
|
||||||
<NewPost />
|
|
||||||
</PostsLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewPostPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import NewUser from 'src/components/NewUser'
|
|
||||||
|
|
||||||
const NewUserPage = () => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<NewUser />
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewUserPage
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import PartsLayout from 'src/layouts/PartsLayout'
|
|
||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import PartCell from 'src/components/PartCell'
|
|
||||||
|
|
||||||
const PartPage = ({ id }) => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<PartsLayout>
|
|
||||||
<PartCell id={id} />
|
|
||||||
</PartsLayout>
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PartPage
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import PartsLayout from 'src/layouts/PartsLayout'
|
|
||||||
import PartsCell from 'src/components/PartsCell'
|
|
||||||
|
|
||||||
const PartsPage = () => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<PartsLayout>
|
|
||||||
<PartsCell />
|
|
||||||
</PartsLayout>
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PartsPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import PostsLayout from 'src/layouts/PostsLayout'
|
|
||||||
import PostCell from 'src/components/PostCell'
|
|
||||||
|
|
||||||
const PostPage = ({ id }) => {
|
|
||||||
return (
|
|
||||||
<PostsLayout>
|
|
||||||
<PostCell id={id} />
|
|
||||||
</PostsLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostPage
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import PostsLayout from 'src/layouts/PostsLayout'
|
|
||||||
import PostsCell from 'src/components/PostsCell'
|
|
||||||
|
|
||||||
const PostsPage = () => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<PostsLayout>
|
|
||||||
<PostsCell />
|
|
||||||
</PostsLayout>
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostsPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import UserCell from 'src/components/UserCell'
|
|
||||||
|
|
||||||
const UserPage = ({ id }) => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<UserCell id={id} />
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UserPage
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import MainLayout from 'src/layouts/MainLayout'
|
|
||||||
import UsersCell from 'src/components/UsersCell'
|
|
||||||
|
|
||||||
const UsersPage = () => {
|
|
||||||
return (
|
|
||||||
<MainLayout>
|
|
||||||
<UsersCell />
|
|
||||||
</MainLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UsersPage
|
|
||||||
Reference in New Issue
Block a user