Cristian Arcaroli
schema
shared
server
web_client
compile
web_client
generate Apollo
queries
generate SDL
compile server
webpack
bundle
@react class Products extends StatelessComponent {
type Props = Unit
def render() = {
val wrappedProductForm = AntForm.Form.create()(wrapperToClass(ProductForm))
LayoutContent(LayoutContent.Props())(style := js.Dynamic.literal(padding = "50px"))(
ProductDisplay(),
hr(style := js.Dynamic.literal(margin = "30px")),
Row(
Col(Col.Props(span = 24))(
h1("Insert a new product"),
React.createElement(wrappedProductForm, js.Dictionary())
)
)
)
}
}
@JSImport("antd", JSImport.Default)
@js.native
object AntInput extends js.Object {
val Input: js.Object = js.native
}
@react object Input extends ExternalComponentWithAttributes[input.tag.type] {
case class Props(addonAfter: UndefOr[String | ReactElement] = js.undefined,
addonBefore: UndefOr[String | ReactElement] = js.undefined,
defaultValue: UndefOr[String] = js.undefined,
disabled: Boolean = false,
...
...
)
override val component = AntInput.Input
}
val IdentifiableType = InterfaceType(
fields[RequestContext, Identifiable](
Field("id", StringType, resolve = _.value.id)))
val PictureType =
deriveObjectType[Unit, Picture](
ObjectTypeDescription("The product picture"),
DocumentField("url", "Picture CDN URL"))
val ProductType =
deriveObjectType[RequestContext, Product](
Interfaces(IdentifiableType),
AddFields(
Field("pictures", ListType(PictureType),
arguments = Argument("size", IntType) :: Nil,
resolve = c => c.ctx.pictureRepo.picturesByProduct(c.value.id))
)
)
interface Identifiable {
id: String!
}
"The product picture"
type Picture {
width: Int!
height: Int!
"Picture CDN URL"
url: String
}
type Product implements Identifiable {
id: String!
name: String!
description: String!
pictures(size: Int!): [Picture!]!
}
val QueryType = ObjectType("Query", fields[RequestContext, Any](
Field("products", ListType(ProductType),
description = Some("Returns a list of all available products."),
resolve = _.ctx.productRepo.products)
)
)
type Query {
"Returns a list of all available products."
products: [Product!]!
}
query AllProducts {
products {
id
name
description
pictures(size: 500) {
width
height
url
}
}
}
Query(AllProductsQuery) { result =>
if (result.loading) {
h1("Loading!")
} else if (result.error.isDefined) {
h1("Error: " + result.error.get.message)
} else {
div(
h1("Products display"),
Row(Row.Props(gutter = 16,
justify = "space-around",
align = "middle")
)(
result.data.get.products.map { product =>
Col(Col.Props(span = 6))(
renderProductCard(product)
)
}: _*
)
)
}
}
private def renderProductCard(product: AllProductsQuery.Data.Product) =
Card(Card.Props(
cover = img(src := product.pictures
.headOption
.flatMap(_.url)
.getOrElse("/assets/images/default_item.jpg"))()
))(style := js.Dynamic.literal(maxWidth = "240px"))(
CardMeta(
CardMeta.Props(title = span(product.name),
description = span(product.description)
)
)
)
Mutation(AddProductMutation, UpdateStrategy(refetchQueries = Seq("AllProducts"))) {
(addProduct, mutationStatus) =>
Form(Form.Props(onSubmit = (e: Event) => { handleSubmit(e, addProduct) }))(
FormItem(
form.getFieldDecorator("productName",
FieldDecoratorOptions(rules = Seq(ValidationRules(
required = true, message = "Cannot contain numbers or be empty.",
pattern = RegExp("^[^0-9]+$")))))(
Input(Input.Props())(placeholder := "Name",
autoComplete := "off")
)
),
FormItem(
form.getFieldDecorator("productDescription",
FieldDecoratorOptions(rules = Seq(ValidationRules(
required = true, message = "Cannot be empty."))))(
Input(Input.Props())(placeholder := "Description",
autoComplete := "off")
)
),
Button(Button.Props(`type` = "primary", htmlType = "submit"))("Add")
)
}