Skip to content

Commit

Permalink
Add prices block
Browse files Browse the repository at this point in the history
  • Loading branch information
herzog31 committed Dec 12, 2024
1 parent a23662d commit 2255cfa
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 84 deletions.
58 changes: 31 additions & 27 deletions src/content/queries/cs-product.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ import { gql, parseRating, parseSpecialToDate } from '../../utils/product.js';
* @returns {Product}
*/
export const adapter = (config, productData) => {
let minPrice = productData.priceRange?.minimum ?? productData.price;
let maxPrice = productData.priceRange?.maximum ?? productData.price;

if (minPrice == null) {
minPrice = maxPrice;
} else if (maxPrice == null) {
maxPrice = minPrice;
}
/** @type {Product} */
const product = {
sku: productData.sku,
Expand All @@ -45,7 +37,12 @@ export const adapter = (config, productData) => {
attributes: productData.attributes ?? [],
attributeMap: Object.fromEntries((productData.attributes ?? [])
.map(({ name, value }) => [name, value])),
options: (productData.options ?? []).map((option) => ({
// eslint-disable-next-line no-underscore-dangle
type: productData.__typename === 'SimpleProductView' ? 'simple' : 'complex',
};

if (productData.options) {
product.options = productData.options.map((option) => ({
id: option.id,
label: option.title,
// eslint-disable-next-line no-underscore-dangle
Expand All @@ -62,7 +59,7 @@ export const adapter = (config, productData) => {
? {
sku: value.product.sku,
name: value.product.name,
prices: value.product.price ? {
price: value.product.price ? {
regular: value.product.price.regular,
final: value.product.price.final,
visible: value.product.price.roles?.includes('visible'),
Expand All @@ -72,25 +69,31 @@ export const adapter = (config, productData) => {
quantity: value.quantity,
isDefault: value.isDefault,
})),
})),
prices: (minPrice && maxPrice) ? {
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,
}));
}

if (productData.price) {
product.price = {
regular: productData.price.regular,
final: productData.price.final,
visible: productData.price.roles?.includes('visible'),
};
}

if (productData.priceRange) {
product.priceRange = {
minimum: {
regular: productData.priceRange.minimum.regular,
final: productData.priceRange.minimum.final,
visible: productData.priceRange.minimum.roles?.includes('visible'),
},
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,
maximum: {
regular: productData.priceRange.maximum.regular,
final: productData.priceRange.maximum.final,
visible: productData.priceRange.maximum.roles?.includes('visible'),
},
visible: minPrice.roles?.includes('visible'),
} : null,
};
};
}

if (config.attributeOverrides?.product) {
Object.entries(config.attributeOverrides.product).forEach(([key, value]) => {
Expand Down Expand Up @@ -122,6 +125,7 @@ export default ({ sku, imageRoles = [] }) => gql`{
products(
skus: ["${sku}"]
) {
__typename
id
sku
name
Expand Down
45 changes: 26 additions & 19 deletions src/content/queries/cs-variants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ import { gql, parseRating, parseSpecialToDate } from '../../utils/product.js';
* @returns {Variant[]}
*/
export const adapter = (config, variants) => variants.map(({ selections, product }) => {
const minPrice = product.priceRange?.minimum ?? product.price;
const maxPrice = product.priceRange?.maximum ?? product.price;

/** @type {Variant} */
const variant = {
name: product.name,
Expand All @@ -34,25 +31,34 @@ export const adapter = (config, variants) => variants.map(({ selections, product
attributeMap: Object.fromEntries((product.attributes ?? [])
.map(({ name, value }) => [name, value])),
externalId: product.externalId,
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,
},
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,
},
},
selections: (selections ?? []).sort(),
// eslint-disable-next-line no-underscore-dangle
type: product.__typename === 'SimpleProductView' ? 'simple' : 'complex',
};

if (product.price) {
variant.price = {
regular: product.price.regular,
final: product.price.final,
visible: product.price.roles?.includes('visible'),
};
}

if (product.priceRange) {
variant.priceRange = {
minimum: {
regular: product.priceRange.minimum.regular,
final: product.priceRange.minimum.final,
visible: product.priceRange.minimum.roles?.includes('visible'),
},
maximum: {
regular: product.priceRange.maximum.regular,
final: product.priceRange.maximum.final,
visible: product.priceRange.maximum.roles?.includes('visible'),
},
};
}

if (config.attributeOverrides?.variant) {
Object.entries(config.attributeOverrides.variant).forEach(([key, value]) => {
variant.attributeMap[key] = variant.attributeMap[value] ?? variant[key];
Expand Down Expand Up @@ -84,6 +90,7 @@ export default ({ sku, imageRoles = [] }) => gql`
variants {
selections
product {
__typename
name
sku
inStock
Expand Down
66 changes: 55 additions & 11 deletions src/templates/html/HTMLTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class HTMLTemplate {
* @param {number|undefined} max
* @returns {string}
*/
static priceRange = (min, max) => (min !== max ? ` (${min} - ${max})` : '');
static priceRange = (min, max) => (min !== max ? `${min} - ${max}` : `${min}`);

/**
* @param {string} str
Expand Down Expand Up @@ -108,7 +108,7 @@ ${HTMLTemplate.metaName('twitter:card', 'summary_large_image')}
${HTMLTemplate.metaName('twitter:title', product.name)}
${HTMLTemplate.metaName('twitter:description', product.metaDescription)}
${HTMLTemplate.metaName('twitter:label1', 'Price')}
${HTMLTemplate.metaName('twitter:data1', product.prices?.final?.amount)}
${HTMLTemplate.metaName('twitter:data1', product.price?.final?.amount?.value ?? product.priceRange?.minimum?.final?.amount?.value)}
${HTMLTemplate.metaName('twitter:label2', 'Availability')}
${HTMLTemplate.metaName('twitter:data2', product.inStock ? 'In stock' : 'Out of stock')}`;
}
Expand All @@ -126,8 +126,9 @@ ${HTMLTemplate.metaName('externalId', product.externalId)}
${HTMLTemplate.metaName('addToCartAllowed', product.addToCartAllowed)}
${HTMLTemplate.metaName('inStock', product.inStock ? 'true' : 'false')}
${HTMLTemplate.metaProperty('product:availability', product.inStock ? 'In stock' : 'Out of stock')}
${HTMLTemplate.metaProperty('product:price.amount', product.prices?.final?.amount)}
${HTMLTemplate.metaProperty('product:price.currency', product.prices?.final?.currency)}`;
${HTMLTemplate.metaProperty('product:price.amount', product.price?.final?.amount?.value ?? product.priceRange?.minimum?.final?.amount?.value)}
${HTMLTemplate.metaProperty('product:price.currency', product.price?.final?.amount?.currency ?? product.priceRange?.minimum?.final?.amount?.currency)}
${HTMLTemplate.metaProperty('product:type', product.type)}`;
}

/**
Expand Down Expand Up @@ -169,6 +170,38 @@ ${HTMLTemplate.indent(this.renderJSONLD(), 2)}
</head>`;
}

/**
* Render product price block
* @param {Product} product
* @returns {string}
*/
renderProductPrices(product) {
const { price, priceRange } = product;
const hasFinal = price
? price.final?.amount?.value < price.regular?.amount?.value
: priceRange?.minimum?.final.amount.value < priceRange?.minimum?.regular.amount.value;
const isRange = !!priceRange
&& priceRange.minimum?.final?.amount?.value !== priceRange.maximum?.final?.amount?.value;

const regularPrice = price ? price.regular?.amount : priceRange.minimum?.regular?.amount;
const finalPrice = price ? price.final?.amount : priceRange.minimum?.final?.amount;

return /* html */ `\
<div class="product-prices">
<div>
<div>Regular</div>
<div>${regularPrice.value} ${regularPrice.currency}</div>
${isRange ? `<div>${priceRange.maximum?.regular?.amount?.value} ${priceRange.maximum?.regular?.amount?.currency}</div>` : ''}
</div>
${hasFinal ? /* html */ `\
<div>
<div>${finalPrice.value} ${finalPrice.currency}</div>
<div>${price ? price.final?.amount?.value : priceRange.minimum?.final?.amount?.value}</div>
${isRange ? `<div>${priceRange.maximum?.final?.amount?.value} ${priceRange.maximum?.final?.amount?.currency}</div>` : ''}
</div>` : ''}
</div>`;
}

/**
* Create the product images
* @param {Image[]} images
Expand Down Expand Up @@ -235,7 +268,7 @@ ${attributes.map((attr) => /* html */`\
* @returns {string}
*/
renderProductOptions(options) {
return options.length > 0 ? /* html */ `\
return options?.length > 0 ? /* html */ `\
<div class="product-options">
${options.map((opt) => /* html */ `\
<div>
Expand Down Expand Up @@ -302,13 +335,23 @@ ${HTMLTemplate.indent(this.renderProductItems(opt.items), 2)}`).join('\n')}

/**
* Create the variant prices
* @param {Pick<Prices, 'regular' | 'final'>} prices
* @param {Variant} variant
* @returns {string}
*/
renderVariantPrices(prices) {
return /* html */ `\
<div>Regular: ${prices.regular?.amount} ${prices.regular?.currency}${HTMLTemplate.priceRange(prices.regular?.minimumAmount, prices.regular?.maximumAmount)}</div>
<div>Final: ${prices.final?.amount} ${prices.final?.currency}${HTMLTemplate.priceRange(prices.final?.minimumAmount, prices.final?.maximumAmount)}</div>`;
renderVariantPrices(variant) {
if (variant.price) {
const { price } = variant;
return /* html */ `\
<div>Regular: ${price.regular?.amount?.value} ${price.regular?.amount?.currency}</div>
<div>Final: ${price.final?.amount?.value} ${price.final?.amount?.currency}</div>`;
}
if (variant.priceRange) {
const { minimum, maximum } = variant.priceRange;
return /* html */ `\
<div>Regular: ${HTMLTemplate.priceRange(minimum?.regular?.amount?.value, maximum?.regular?.amount?.value)} ${minimum?.regular?.amount?.currency}</div>
<div>Final: ${HTMLTemplate.priceRange(minimum?.final?.amount?.value, maximum?.final?.amount?.value)} ${minimum?.final?.amount?.currency}</div>`;
}
return '';
}

/**
Expand All @@ -328,7 +371,7 @@ ${this.variants.map((v) => /* html */`\
<div>${v.name}</div>
<div>${v.description}</div>
<div>${v.inStock ? 'inStock' : ''}</div>
${v.prices ? HTMLTemplate.indent(this.renderVariantPrices(v.prices), 4) : ''}
${v.price || v.priceRange ? HTMLTemplate.indent(this.renderVariantPrices(v), 4) : ''}
<div>
${HTMLTemplate.indent(this.renderVariantImages(v.images), 6)}
</div>
Expand Down Expand Up @@ -389,6 +432,7 @@ ${HTMLTemplate.indent(this.renderHead(), 2)}
<h1>${name}</h1>
${description ? `<p>${description}</p>` : ''}
${HTMLTemplate.indent(this.renderProductImages(images), 8)}
${HTMLTemplate.indent(this.renderProductPrices(this.product), 8)}
${HTMLTemplate.indent(this.renderProductAttributes(attributes), 8)}
${HTMLTemplate.indent(this.renderProductOptions(options), 8)}
${HTMLTemplate.indent(this.renderProductVariants(), 8)}
Expand Down
32 changes: 20 additions & 12 deletions src/templates/json/JSONTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,21 @@ export class JSONTemplate {
const configurableProduct = this.variants?.length > 0;
const offers = configurableProduct ? this.variants : [this.product];
return offers.map((v) => {
const { prices: variantPrices } = v;
const offerUrl = this.constructProductURL(configurableProduct ? v : undefined);
const mpn = this.constructMPN(configurableProduct ? v : undefined);
const finalPrice = variantPrices?.final?.amount;
const regularPrice = variantPrices?.regular?.amount;

let finalPrice;
let regularPrice;
let currency;
if (v.price) {
finalPrice = v.price.final.amount.value;
regularPrice = v.price.regular.amount.value;
currency = v.price.final.amount.currency;
} else if (v.priceRange) {
finalPrice = v.priceRange.minimum.final.amount.value;
regularPrice = v.priceRange.minimum.regular.amount.value;
currency = v.priceRange.minimum.final.amount.currency;
}

const offer = {
'@type': 'Offer',
Expand All @@ -140,31 +150,29 @@ export class JSONTemplate {
image: v.images?.[0]?.url ?? image,
availability: v.inStock ? 'InStock' : 'OutOfStock',
price: finalPrice,
priceCurrency: variantPrices?.final?.currency,
priceCurrency: currency,
gtin: v.attributeMap.gtin,
priceValidUntil: v.specialToDate,
aggregateRating: this.renderRating(v),
};

if (variantPrices) {
if (finalPrice < regularPrice) {
offer.priceSpecification = this.renderOffersPriceSpecification(v);
}
if (finalPrice < regularPrice) {
offer.priceSpecification = this.renderOffersPriceSpecification(regularPrice, currency);
}

return pruneUndefined(offer);
});
}

/**
* @param {Variant} variant
* @param {number} regularPrice
* @param {string} currency
*/
renderOffersPriceSpecification(variant) {
const { prices: { regular: { amount, currency } } } = variant;
renderOffersPriceSpecification(regularPrice, currency) {
return {
'@type': 'UnitPriceSpecification',
priceType: 'https://schema.org/ListPrice',
price: amount,
price: regularPrice,
priceCurrency: currency,
};
}
Expand Down
Loading

0 comments on commit 2255cfa

Please sign in to comment.