Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use product model, add offer jsonld #5

Merged
merged 1 commit into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// @ts-check

import { errorResponse, errorWithResponse, makeContext } from './util.js';
import getProductQueryCS from './queries/cs-product.js';
import getProductQueryCS, { adapter } from './queries/cs-product.js';
import getProductQueryCore from './queries/core-product.js';
import HTML_TEMPLATE from './templates/html.js';
import { resolveConfig } from './config.js';
Expand Down Expand Up @@ -40,10 +40,11 @@ async function fetchProductCS(sku, config) {

const json = await resp.json();
try {
const [product] = json.data.products;
if (!product) {
const [productData] = json.data.products;
if (!productData) {
throw errorWithResponse(404, 'could not find product', json.errors);
}
const product = adapter(productData);
return product;
} catch (e) {
console.error('failed to parse product: ', e);
Expand Down
135 changes: 104 additions & 31 deletions src/queries/cs-product.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,85 @@

import { gql } from '../util.js';

/**
* @param {any} productData
* @returns {Product}
*/
export const adapter = (productData) => {
const minPrice = productData.priceRange?.minimum ?? productData.price;
const maxPrice = productData.priceRange?.maximum ?? productData.price;

/** @type {Product} */
const product = {
sku: productData.sku,
name: productData.name,
metaTitle: productData.metaTitle,
metaDescription: productData.metaDescription,
metaKeyword: productData.metaKeyword,
description: productData.description,
url: productData.url,
urlKey: productData.urlKey,
shortDescription: productData.shortDescription,
addToCartAllowed: productData.addToCartAllowed,
inStock: productData.inStock,
externalId: productData.externalId,
images: productData.images ?? [],
attributes: productData.attributes ?? [],
options: (productData.options ?? []).map((option) => ({
id: option.id,
label: option.title,
// eslint-disable-next-line no-underscore-dangle
typename: option.values?.[0]?.__typename,
required: option.required,
multiple: option.multi,
items: (option.values ?? []).map((value) => ({
id: value.id,
label: value.title,
inStock: value.inStock,
type: value.type,
product: value.product
? {
sku: value.product.sku,
name: value.product.name,
prices: value.product.price ? {
regular: value.product.price.regular,
final: value.product.price.final,
visible: value.product.price.roles?.includes('visible'),
} : undefined,
}
: undefined,
quantity: value.quantity,
isDefault: value.isDefault,
})),
})),
prices: {
regular: {
// TODO: determine whether to use min or max
amount: minPrice.regular.amount.value,
currency: minPrice.regular.amount.currency,
maximumAmount: maxPrice.regular.amount.value,
minimumAmount: minPrice.regular.amount.value,
// TODO: add variant?
},
final: {
// TODO: determine whether to use min or max
amount: minPrice.final.amount.value,
currency: minPrice.final.amount.currency,
maximumAmount: maxPrice.final.amount.value,
minimumAmount: minPrice.final.amount.value,
// TODO: add variant?
},
visible: minPrice.roles?.includes('visible'),
},
};

return product;
};

export default ({ sku }) => gql`{
products(
skus: ["${sku}"]
) {
__typename
id
sku
name
Expand All @@ -30,111 +104,110 @@ export default ({ sku }) => gql`{
url
addToCartAllowed
inStock
externalId
images(roles: []) {
url
label
roles
__typename
}
attributes(roles: []) {
name
label
value
roles
__typename
}
... on SimpleProductView {
price {
final {
amount {
value
currency
__typename
}
__typename
}
regular {
amount {
value
currency
__typename
}
__typename
}
roles
__typename
}
__typename
}
... on ComplexProductView {
options {
__typename
id
title
required
multi
values {
id
title
... on ProductViewOptionValueProduct {
product {
sku
name
__typename
}
inStock
...on ProductViewOptionValueConfiguration {
__typename
}
... on ProductViewOptionValueSwatch {
__typename
type
value
}
... on ProductViewOptionValueProduct {
__typename
quantity
isDefault
product {
sku
name
price {
regular {
amount {
value
currency
}
}
final {
amount {
value
currency
}
}
roles
}
}
}
__typename
}
__typename
}
priceRange {
maximum {
final {
amount {
value
currency
__typename
}
__typename
}
regular {
amount {
value
currency
__typename
}
__typename
}
roles
__typename
}
minimum {
final {
amount {
value
currency
__typename
}
__typename
}
regular {
amount {
value
currency
__typename
}
__typename
}
roles
__typename
}
__typename
}
__typename
}
}
}`;
51 changes: 33 additions & 18 deletions src/templates/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,33 @@

import JSON_LD_TEMPLATE from './json-ld.js';

/**
* @param {string} name
* @param {string|boolean|number|undefined|null} [content]
* @returns {string}
*/
const metaContent = (name, content) => (content ? `<meta name="${name}" content="${content}">` : '');

/**
* @param {Product} product
*/
export default (product) => {
const {
sku,
name,
urlKey,
metaTitle,
metaDescription,
description,
images,
attributes,
options,
addToCartAllowed,
inStock,
metaKeyword,
externalId,
} = product;

const jsonLd = JSON_LD_TEMPLATE({
sku,
description: description ?? metaDescription,
image: images[0].url,
name,
// TODO: add following...
url: '',
brandName: '',
reviewCount: 0,
ratingValue: 0,
});

return /* html */`\
<!DOCTYPE html>
<html>
Expand All @@ -50,21 +53,26 @@ export default (product) => {
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${metaTitle || name}">
<meta name="twitter:image" content="${images[0].url}">
<meta name="keywords" content="${metaKeyword}">
<meta name="sku" content="${sku}">
<meta name="urlKey" content="${urlKey}">
${metaContent('externalId', externalId)}
${metaContent('addToCartAllowed', addToCartAllowed)}
${metaContent('inStock', inStock)}
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="/scripts/aem.js" type="module"></script>
<script src="/scripts/scripts.js" type="module"></script>
<link rel="stylesheet" href="/styles/styles.css">
<script type="application/ld+json">
${jsonLd}
${JSON_LD_TEMPLATE(product)}
</script>
</head>
<body>
<header></header>
<main>
<div>
<h1>${name}</h1>
<div class="product-gallery">
<div class="product-images">
<div>
${images.map((img) => `
<div>
Expand All @@ -89,13 +97,20 @@ export default (product) => {
${options.map((opt) => `
<div>
<div>${opt.id}</div>
<div>${opt.title}</div>
<div>${opt.label}</div>
<div>${opt.typename}</div>
<div>${opt.type ?? ''}</div>
<div>${opt.multiple ? 'multiple' : ''}</div>
<div>${opt.required === true ? 'required' : ''}</div>
</div>
${opt.values.map((val) => `
${opt.items.map((item) => `
<div>
<div>${val.id}</div>
<div>${val.title}</div>
<div>option</div>
<div>${item.id}</div>
<div>${item.label}</div>
<div>${item.value ?? ''}</div>
<div>${item.selected ? 'selected' : ''}</div>
<div>${item.inStock ? 'inStock' : ''}</div>
</div>`).join('\n')}`).join('\n')}
</div>
</div>
Expand Down
Loading