Current section
Files
Jump to
Current section
Files
src/storefront/products.gleam
import gleam/dynamic/decode
import gleam/json
import gleam/list
import gleam/option.{type Option, None, Some}
import storefront/fragments.{product_fragment}
import storefront/utils.{
type Connection, type Edge, type Image, type PriceRange, type ProductVariant,
type Seo, Connection, Edge, image_connection_decoder, image_decoder,
image_to_json, price_range_decoder, price_range_to_json,
product_variant_decoder, product_variant_to_json, remove_edges_and_nodes,
reshape_images, seo_decoder, seo_to_json,
}
// fn get_product(
// client: ShopifyClient,
// handle: String,
// ) -> Result(Option(Product), ShopifyError) {
// let query = get_product_query()
// let variables = json.object([#("handle", json.string(handle))])
// fetch(
// client: client,
// query: query,
// variables: Some(variables),
// decoder: shopify_product_operation_decoder(),
// )
// |> result.map(fn(resp) {
// case resp.body.data.product {
// Some(product) -> Some(reshape_product(product, True))
// None -> None
// }
// })
// }
// fn get_products(
// client: ShopifyClient,
// query: Option(String),
// reverse: Option(Bool),
// sort_key: Option(String),
// ) -> Result(Option(List(Product)), ShopifyError) {
// let main_query = get_products_query()
// let query = option.unwrap(query, "")
// let reverse = option.unwrap(reverse, False)
// let sort_key = option.unwrap(sort_key, "")
// let variables =
// json.object([
// #("query", json.string(query)),
// #("reverse", json.bool(reverse)),
// #("sortKey", json.string(sort_key)),
// ])
// fetch(
// client: client,
// query: main_query,
// variables: Some(variables),
// decoder: shopify_products_operation_decoder(),
// )
// |> result.map(fn(resp) {
// let products = resp.body.data.products
// case products {
// Some(product) -> Some(reshape_products(remove_edges_and_nodes(product)))
// None -> None
// }
// })
// }
// fn get_products_recommendations(
// client: ShopifyClient,
// product_id: String,
// ) -> Result(List(Product), ShopifyError) {
// {
// let query = get_product_recommendations_query()
// let variables = json.object([#("productId", json.string(product_id))])
// fetch(
// client: client,
// query: query,
// variables: Some(variables),
// decoder: shopify_product_recommendations_operation_decoder(),
// )
// |> result.map(fn(resp) {
// reshape_products(resp.body.data.product_recommendations)
// })
// }
// }
pub type ProductOption {
ProductOption(id: String, name: String, values: List(String))
}
const hidden_product_tags = "gleam-frontend-hidden"
pub fn product_option_decoder() -> decode.Decoder(ProductOption) {
use id <- decode.field("id", decode.string)
use name <- decode.field("name", decode.string)
use values <- decode.field("values", decode.list(decode.string))
decode.success(ProductOption(id:, name:, values:))
}
pub type Products {
Products(products: List(Product))
}
pub fn products_decoder() -> decode.Decoder(Products) {
use products <- decode.field("products", decode.list(product_decoder()))
decode.success(Products(products:))
}
pub type Product {
Product(
id: String,
handle: String,
available_for_sale: Bool,
title: String,
description: String,
description_html: String,
price_range: PriceRange,
featured_image: Image,
seo: Seo,
tags: List(String),
updated_at: String,
variants: List(ProductVariant),
images: List(Image),
)
}
pub fn product_to_json(product: Product) -> json.Json {
let Product(
id:,
handle:,
available_for_sale:,
title:,
description:,
description_html:,
price_range:,
featured_image:,
seo:,
tags:,
updated_at:,
variants:,
images:,
) = product
json.object([
#("id", json.string(id)),
#("handle", json.string(handle)),
#("availableForSale", json.bool(available_for_sale)),
#("title", json.string(title)),
#("description", json.string(description)),
#("descriptionHtml", json.string(description_html)),
#("priceRange", price_range_to_json(price_range)),
#("featuredImage", image_to_json(featured_image)),
#("seo", seo_to_json(seo)),
#("tags", json.array(tags, json.string)),
#("updatedAt", json.string(updated_at)),
#("variants", json.array(variants, product_variant_to_json)),
#("images", json.array(images, image_to_json)),
])
}
pub fn product_decoder() -> decode.Decoder(Product) {
use id <- decode.field("id", decode.string)
use handle <- decode.field("handle", decode.string)
use available_for_sale <- decode.field("available_for_sale", decode.bool)
use title <- decode.field("title", decode.string)
use description <- decode.field("description", decode.string)
use description_html <- decode.field("description_html", decode.string)
use price_range <- decode.field("price_range", price_range_decoder())
use featured_image <- decode.field("featured_image", image_decoder())
use seo <- decode.field("seo", seo_decoder())
use tags <- decode.field("tags", decode.list(decode.string))
use updated_at <- decode.field("updated_at", decode.string)
use variants <- decode.field(
"variants",
decode.list(product_variant_decoder()),
)
use images <- decode.field("images", decode.list(image_decoder()))
decode.success(Product(
id:,
handle:,
available_for_sale:,
title:,
description:,
description_html:,
price_range:,
featured_image:,
seo:,
tags:,
updated_at:,
variants:,
images:,
))
}
pub type ShopifyProduct {
ShopifyProduct(
id: String,
handle: String,
available_for_sale: Bool,
title: String,
description: String,
description_html: String,
price_range: PriceRange,
variants: Connection(ProductVariant),
featured_image: Image,
images: Connection(Image),
seo: Seo,
tags: List(String),
updated_at: String,
)
}
fn shopify_product_decoder() -> decode.Decoder(ShopifyProduct) {
use id <- decode.field("id", decode.string)
use handle <- decode.field("handle", decode.string)
use available_for_sale <- decode.field("available_for_sale", decode.bool)
use title <- decode.field("title", decode.string)
use description <- decode.field("description", decode.string)
use description_html <- decode.field("description_html", decode.string)
use price_range <- decode.field("price_range", price_range_decoder())
use variants <- decode.field("variants", product_variant_connection_decoder())
use featured_image <- decode.field("featured_image", image_decoder())
use images <- decode.field("images", image_connection_decoder())
use seo <- decode.field("seo", seo_decoder())
use tags <- decode.field("tags", decode.list(decode.string))
use updated_at <- decode.field("updated_at", decode.string)
decode.success(ShopifyProduct(
id:,
handle:,
available_for_sale:,
title:,
description:,
description_html:,
price_range:,
variants:,
featured_image:,
images:,
seo:,
tags:,
updated_at:,
))
}
// 2. Products
fn product_variant_connection_decoder() -> decode.Decoder(
Connection(ProductVariant),
) {
use edges <- decode.field(
"edges",
decode.list(product_variant_edges_decoder()),
)
decode.success(Connection(edges:))
}
fn product_variant_edges_decoder() -> decode.Decoder(Edge(ProductVariant)) {
use node <- decode.field("node", product_variant_decoder())
decode.success(Edge(node:))
}
pub type ShopifyCollection {
ShopifyCollection(
handle: String,
title: String,
description: String,
seo: Seo,
updated_at: String,
)
}
pub fn reshape_product(
product: ShopifyProduct,
filter_hidden_products: Bool,
) -> Product {
let result = {
let is_hidden = list.contains(product.tags, hidden_product_tags)
case filter_hidden_products && is_hidden {
True -> None
False -> {
let reshaped_image = reshape_images(product.images, product.title)
let reshaped_variants = remove_edges_and_nodes(product.variants)
Some(Product(
id: product.id,
handle: product.handle,
available_for_sale: product.available_for_sale,
title: product.title,
description: product.description,
description_html: product.description_html,
price_range: product.price_range,
featured_image: product.featured_image,
seo: product.seo,
tags: product.tags,
updated_at: product.updated_at,
images: reshaped_image,
variants: reshaped_variants,
))
}
}
}
let reshaped_image = reshape_images(product.images, product.title)
let reshaped_variants = remove_edges_and_nodes(product.variants)
option.unwrap(
result,
Product(
id: product.id,
handle: product.handle,
available_for_sale: product.available_for_sale,
title: product.title,
description: product.description,
description_html: product.description_html,
price_range: product.price_range,
featured_image: product.featured_image,
seo: product.seo,
tags: product.tags,
updated_at: product.updated_at,
images: reshaped_image,
variants: reshaped_variants,
),
)
}
pub fn reshape_products(products: List(ShopifyProduct)) -> List(Product) {
list.map(products, fn(product) { reshape_product(product, True) })
}
/// Queries
///
pub const get_product_query = "
query getProduct($handle: String!) {
product(handle: $handle) {
...product
}
}
"
<> product_fragment
pub const get_products_query = "
query getProducts($sortKey: ProductSortKeys, $reverse: Boolean, $query: String) {
products(sortKey: $sortKey, reverse: $reverse, query: $query, first: 100) {
edges {
node {
...product
}
}
}
}
"
<> product_fragment
pub const get_product_recommendations_query = "
query getProductRecommendations($productId: ID!) {
productRecommendations(productId: $productId) {
...product
}
}
"
<> product_fragment
/// Query operations types and decoders
pub type ShopifyGetProductOperation {
ShopifyProductOperation(
data: ShopifyProductOperationData,
variables: ShopifyProductOperationVariables,
)
}
pub fn shopify_product_operation_decoder() -> decode.Decoder(
ShopifyGetProductOperation,
) {
use data <- decode.field("data", shopify_product_operation_data_decoder())
use variables <- decode.field(
"variables",
shopify_product_operation_variables_decoder(),
)
decode.success(ShopifyProductOperation(data:, variables:))
}
pub type ShopifyProductOperationData {
ShopifyProductOperationData(product: Option(ShopifyProduct))
}
fn shopify_product_operation_data_decoder() -> decode.Decoder(
ShopifyProductOperationData,
) {
use product <- decode.field(
"product",
decode.optional(shopify_product_decoder()),
)
decode.success(ShopifyProductOperationData(product:))
}
pub type ShopifyProductOperationVariables {
ShopifyProductOperationVariables(handle: String)
}
fn shopify_product_operation_variables_decoder() -> decode.Decoder(
ShopifyProductOperationVariables,
) {
use handle <- decode.field("handle", decode.string)
decode.success(ShopifyProductOperationVariables(handle:))
}
pub type GetProductsOperation {
GetProductsOperation(
data: ShopifyProductsOperationData,
variables: ShopifyProductsOperationVariables,
)
}
pub fn get_products_operation_decoder() -> decode.Decoder(GetProductsOperation) {
use data <- decode.field("data", shopify_products_operation_data_decoder())
use variables <- decode.field(
"variables",
shopify_products_operation_variables_decoder(),
)
decode.success(GetProductsOperation(data:, variables:))
}
pub type ShopifyProductsOperationData {
ShopifyProductsOperationData(products: Option(Connection(ShopifyProduct)))
}
fn shopify_products_operation_data_decoder() -> decode.Decoder(
ShopifyProductsOperationData,
) {
use products <- decode.field(
"products",
decode.optional(shopify_product_connection_decoder()),
)
decode.success(ShopifyProductsOperationData(products:))
}
pub type ShopifyProductsOperationVariables {
ShopifyProductsOperationVariables(
query: String,
reverse: Bool,
sort_key: String,
)
}
fn shopify_products_operation_variables_decoder() -> decode.Decoder(
ShopifyProductsOperationVariables,
) {
use query <- decode.field("query", decode.string)
use reverse <- decode.field("reverse", decode.bool)
use sort_key <- decode.field("sortKey", decode.string)
decode.success(ShopifyProductsOperationVariables(query:, reverse:, sort_key:))
}
fn shopify_product_connection_decoder() -> decode.Decoder(
Connection(ShopifyProduct),
) {
use edges <- decode.field(
"edges",
decode.list(shopify_product_edges_decoder()),
)
decode.success(Connection(edges:))
}
fn shopify_product_edges_decoder() -> decode.Decoder(Edge(ShopifyProduct)) {
use node <- decode.field("node", shopify_product_decoder())
decode.success(Edge(node:))
}
pub type ShopifyProductRecommendationsOperation {
ShopifyProductRecommendationsOperation(
data: ShopifyProductRecommendationsOperationData,
variables: ShopifyProductRecommendationsOperationVariables,
)
}
fn shopify_product_recommendations_operation_decoder() -> decode.Decoder(
ShopifyProductRecommendationsOperation,
) {
use data <- decode.field(
"data",
shopify_product_recommendations_operation_data_decoder(),
)
use variables <- decode.field(
"variables",
shopify_product_recommendations_operation_variables_decoder(),
)
decode.success(ShopifyProductRecommendationsOperation(data:, variables:))
}
pub opaque type ShopifyProductRecommendationsOperationData {
ShopifyProductRecommendationsOperationData(
product_recommendations: List(ShopifyProduct),
)
}
fn shopify_product_recommendations_operation_data_decoder() -> decode.Decoder(
ShopifyProductRecommendationsOperationData,
) {
use product_recommendations <- decode.field(
"productRecommendations",
decode.list(shopify_product_decoder()),
)
decode.success(ShopifyProductRecommendationsOperationData(
product_recommendations:,
))
}
pub opaque type ShopifyProductRecommendationsOperationVariables {
ShopifyProductRecommendationsOperationVariables(product_id: String)
}
fn shopify_product_recommendations_operation_variables_decoder() -> decode.Decoder(
ShopifyProductRecommendationsOperationVariables,
) {
use product_id <- decode.field("productId", decode.string)
decode.success(ShopifyProductRecommendationsOperationVariables(product_id:))
}