Skip to content

Commit

Permalink
scaffold products
Browse files Browse the repository at this point in the history
  • Loading branch information
xuorig committed Apr 4, 2020
1 parent 41b0510 commit d7d53dc
Show file tree
Hide file tree
Showing 18 changed files with 738 additions and 0 deletions.
26 changes: 26 additions & 0 deletions api/src/graphql/products.sdl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export const schema = gql`
type Product {
id: Int!
name: String!
description: String!
price_cents: Int!
createdAt: DateTime!
}
type Query {
products: [Product]
product(id: Int!): Product
}
input ProductInput {
name: String
description: String
price_cents: Int
}
type Mutation {
createProduct(input: ProductInput!): Product
updateProduct(id: Int!, input: ProductInput!): Product
deleteProduct(id: Int!): Product
}
`
30 changes: 30 additions & 0 deletions api/src/services/products/products.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { db } from 'src/lib/db'

export const products = () => {
return db.product.findMany()
}

export const product = ({ id }) => {
return db.product.findOne({
where: { id },
})
}

export const createProduct = ({ input }) => {
return db.product.create({
data: input,
})
}

export const updateProduct = ({ id, input }) => {
return db.product.update({
data: input,
where: { id },
})
}

export const deleteProduct = ({ id }) => {
return db.product.delete({
where: { id },
})
}
7 changes: 7 additions & 0 deletions api/src/services/products/products.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { products } from './products'

describe('products', () => {
it('returns true', () => {
expect(true).toBe(true)
})
})
4 changes: 4 additions & 0 deletions web/src/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { Router, Route } from '@redwoodjs/router'
const Routes = () => {
return (
<Router>
<Route path="/products/new" page={NewProductPage} name="newProduct" />
<Route path="/products/{id:Int}/edit" page={EditProductPage} name="editProduct" />
<Route path="/products/{id:Int}" page={ProductPage} name="product" />
<Route path="/products" page={ProductsPage} name="products" />
<Route path="/" page={HomePage} name="home" />
<Route notfound page={NotFoundPage} />
</Router>
Expand Down
47 changes: 47 additions & 0 deletions web/src/components/EditProductCell/EditProductCell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useMutation } from '@redwoodjs/web'
import { navigate, routes } from '@redwoodjs/router'
import ProductForm from 'src/components/ProductForm'

export const QUERY = gql`
query FIND_POST_BY_ID($id: Int!) {
product: product(id: $id) {
id
name
description
price_cents
createdAt
}
}
`
const UPDATE_POST_MUTATION = gql`
mutation UpdateProductMutation($id: Int!, $input: ProductInput!) {
updateProduct(id: $id, input: $input) {
id
}
}
`

export const Loading = () => <div>Loading...</div>

export const Success = ({ product }) => {
const [updateProduct, { loading, error }] = useMutation(UPDATE_POST_MUTATION, {
onCompleted: () => {
navigate(routes.products())
},
})

const onSave = (input, id) => {
updateProduct({ variables: { id, input } })
}

return (
<div className="bg-white border rounded-lg overflow-hidden">
<header className="bg-gray-300 text-gray-700 py-3 px-4">
<h2 className="text-sm font-semibold">Edit Product {product.id}</h2>
</header>
<div className="bg-gray-100 p-4">
<ProductForm product={product} onSave={onSave} error={error} loading={loading} />
</div>
</div>
)
}
39 changes: 39 additions & 0 deletions web/src/components/NewProduct/NewProduct.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useMutation } from '@redwoodjs/web'
import { navigate, routes } from '@redwoodjs/router'
import ProductForm from 'src/components/ProductForm'

const CREATE_POST_MUTATION = gql`
mutation CreateProductMutation($input: ProductInput!) {
createProduct(input: $input) {
id
}
}
`

const NewProduct = () => {
const [createProduct, { loading, error }] = useMutation(
CREATE_POST_MUTATION,
{
onCompleted: () => {
navigate(routes.products())
},
}
)

const onSave = (input) => {
createProduct({ variables: { input } })
}

return (
<div className="bg-white border rounded-lg overflow-hidden">
<header className="bg-gray-300 text-gray-700 py-3 px-4">
<h2 className="text-sm font-semibold">New Product</h2>
</header>
<div className="bg-gray-100 p-4">
<ProductForm onSave={onSave} loading={loading} error={error} />
</div>
</div>
)
}

export default NewProduct
83 changes: 83 additions & 0 deletions web/src/components/Product/Product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useMutation } from '@redwoodjs/web'
import { Link, routes, navigate } from '@redwoodjs/router'

const DELETE_POST_MUTATION = gql`
mutation DeleteProductMutation($id: Int!) {
deleteProduct(id: $id) {
id
}
}
`

const Product = ({ product }) => {
const [deleteProduct] = useMutation(DELETE_POST_MUTATION, {
onCompleted: () => {
navigate(routes.products())
location.reload()
},
})

const onDeleteClick = (id) => {
if (confirm('Are you sure you want to delete product ' + id + '?')) {
deleteProduct({ variables: { id } })
}
}

return (
<>
<div className="bg-white border rounded-lg overflow-hidden">
<header className="bg-gray-300 text-gray-700 py-3 px-4">
<h2 className="text-sm font-semibold">Product {product.id} Detail</h2>
</header>
<table className="w-full text-sm">
<tbody>
<tr className="odd:bg-gray-100 even:bg-white border-t">
<td className="font-semibold p-3 text-right md:w-1/5">id</td>
<td className="p-3">{product.id}</td>
</tr>
<tr className="odd:bg-gray-100 even:bg-white border-t">
<td className="font-semibold p-3 text-right md:w-1/5">name</td>
<td className="p-3">{product.name}</td>
</tr>
<tr className="odd:bg-gray-100 even:bg-white border-t">
<td className="font-semibold p-3 text-right md:w-1/5">description</td>
<td className="p-3">{product.description}</td>
</tr>
<tr className="odd:bg-gray-100 even:bg-white border-t">
<td className="font-semibold p-3 text-right md:w-1/5">price_cents</td>
<td className="p-3">{product.price_cents}</td>
</tr>
<tr className="odd:bg-gray-100 even:bg-white border-t">
<td className="font-semibold p-3 text-right md:w-1/5">createdAt</td>
<td className="p-3">{product.createdAt}</td>
</tr>

</tbody>
</table>
</div>
<nav className="my-4 mx-2 text-center">
<ul>
<li className="inline-block ml-2">
<Link
to={routes.editProduct({ id: product.id })}
className="text-xs bg-blue-600 text-white hover:bg-blue-700 rounded px-4 py-2 uppercase font-semibold tracking-wide"
>
Edit
</Link>
</li>
<li className="inline-block ml-2">
<a
href="#"
className="text-xs bg-red-600 text-white hover:bg-red-700 rounded px-4 py-2 uppercase font-semibold tracking-wide"
onClick={() => onDeleteClick(product.id)}
>
Delete
</a>
</li>
</ul>
</nav>
</>
)
}

export default Product
21 changes: 21 additions & 0 deletions web/src/components/ProductCell/ProductCell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Product from 'src/components/Product'

export const QUERY = gql`
query FIND_POST_BY_ID($id: Int!) {
product: product(id: $id) {
id
name
description
price_cents
createdAt
}
}
`

export const Loading = () => <div>Loading...</div>

export const Empty = () => <div>Product not found</div>

export const Success = ({ product }) => {
return <Product product={product} />
}
90 changes: 90 additions & 0 deletions web/src/components/ProductForm/ProductForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
Form,
FormError,
FieldError,
Label,
TextField,
Submit,
} from '@redwoodjs/web'

const CSS = {
label: 'block mt-6 text-gray-700 font-semibold',
labelError: 'block mt-6 font-semibold text-red-700',
input:
'block mt-2 w-full p-2 border border-gray-300 text-gray-700 rounded focus:outline-none focus:border-gray-500',
inputError:
'block mt-2 w-full p-2 border border-red-700 text-red-900 rounded focus:outline-none',
errorMessage: 'block mt-1 font-semibold uppercase text-xs text-red-700',
}

const ProductForm = (props) => {
const onSubmit = (data) => {
props.onSave(data, props?.product?.id)
}

return (
<div className="text-sm -mt-4">
<Form onSubmit={onSubmit} error={props.error}>
<FormError
error={props.error}
wrapperClassName="p-4 bg-red-100 text-red-700 border border-red-300 rounded mb-4"
titleClassName="font-semibold"
listClassName="mt-2 list-disc list-inside"
/>

<Label
name="name"
className={CSS.label}
errorClassName={CSS.labelError}
/>
<TextField
name="name"
defaultValue={props.product?.name}
className={CSS.input}
errorClassName={CSS.inputError}
validation={{ required: true }}
/>
<FieldError name="name" className={CSS.errorMessage} />

<Label
name="description"
className={CSS.label}
errorClassName={CSS.labelError}
/>
<TextField
name="description"
defaultValue={props.product?.description}
className={CSS.input}
errorClassName={CSS.inputError}
validation={{ required: true }}
/>
<FieldError name="description" className={CSS.errorMessage} />

<Label
name="price_cents"
className={CSS.label}
errorClassName={CSS.labelError}
/>
<TextField
name="price_cents"
defaultValue={props.product?.price_cents}
className={CSS.input}
errorClassName={CSS.inputError}
validation={{ required: true }}
/>
<FieldError name="price_cents" className={CSS.errorMessage} />

<div className="mt-8 text-center">
<Submit
disabled={props.loading}
className="bg-blue-600 text-white hover:bg-blue-700 text-xs rounded px-4 py-2 uppercase font-semibold tracking-wide"
>
Save
</Submit>
</div>
</Form>
</div>
)
}

export default ProductForm

0 comments on commit d7d53dc

Please sign in to comment.