Skip to content

Commit

Permalink
Interim commit of schema.org support.
Browse files Browse the repository at this point in the history
  • Loading branch information
amcaffee-ep committed Jan 21, 2025
1 parent 6e6c9d4 commit b863664
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 45 deletions.
29 changes: 15 additions & 14 deletions examples/commerce-essentials/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"@elasticpath/js-sdk": "5.0.0",
"@elasticpath/react-shopper-hooks": "0.14.0",
"@elasticpath/react-shopper-hooks": "0.14.6",
"@floating-ui/react": "^0.26.3",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
Expand Down Expand Up @@ -54,6 +54,7 @@
"react-dom": "^18.3.1",
"react-hook-form": "^7.49.0",
"react-toastify": "^9.1.3",
"schema-dts": "^1.1.2",
"server-only": "^0.0.1",
"tailwind-clip-path": "^1.0.0",
"tailwind-merge": "^2.0.0",
Expand All @@ -64,32 +65,32 @@
"@babel/core": "^7.18.10",
"@next/bundle-analyzer": "^14.0.0",
"@next/env": "^14.0.0",
"@playwright/test": "^1.49.1",
"@svgr/webpack": "^6.3.1",
"@tailwindcss/forms": "^0.5.7",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/node": "18.7.3",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@vitest/coverage-istanbul": "^0.34.5",
"autoprefixer": "^10.4.14",
"babel-loader": "^8.2.5",
"encoding": "^0.1.13",
"eslint": "^8.49.0",
"eslint-config-next": "^14.0.0",
"eslint-config-prettier": "^9.0.0",
"encoding": "^0.1.13",
"eslint-plugin-react": "^7.33.2",
"vite": "^4.2.1",
"vitest": "^0.34.5",
"@vitest/coverage-istanbul": "^0.34.5",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@playwright/test": "^1.49.1",
"lint-staged": "^13.0.3",
"postcss": "^8.4.30",
"prettier": "^3.0.3",
"prettier-eslint": "^15.0.1",
"prettier-eslint-cli": "^7.1.0",
"typescript": "^5.2.2",
"tailwindcss": "^3.3.3",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.30",
"prettier-plugin-tailwindcss": "^0.5.4",
"@tailwindcss/forms": "^0.5.7",
"tailwindcss-animate": "^1.0.7"
"tailwindcss": "^3.3.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.2.2",
"vite": "^4.2.1",
"vitest": "^0.34.5"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { parseProductResponse } from "@elasticpath/react-shopper-hooks";
import React from "react";

import { RecommendedProducts } from "../../../../components/recommendations/RecommendationProducts";
import ProductSchema from "../../../../components/product/schema/ProductSchema";

export const dynamic = "force-dynamic";

Expand Down Expand Up @@ -46,6 +47,7 @@ export default async function ProductPage({ params }: Props) {
key={"page_" + params.productId}
>
<ProductProvider>
<ProductSchema product={shopperProduct} />
<ProductDetailsComponent product={shopperProduct} />
<RecommendedProducts product={shopperProduct} />
</ProductProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { ShopperProduct } from "@elasticpath/react-shopper-hooks";
import type { Offer, Product, ProductGroup, WithContext, Thing } from "schema-dts";
interface IProductSchema {
product: ShopperProduct;
}

const ProductSchema = ({ product }: IProductSchema): JSX.Element => {
const {
response,
} = product;

let schema = {};

if (product.kind === "child-product") {

const productGroupSchema: ProductGroup = {
"@type": "ProductGroup",
"@id": product.baseProduct.response.attributes.slug,
productGroupID: product.baseProduct.response.attributes.sku,
name: product.baseProduct.response.attributes.name,
description: product.baseProduct.response.attributes.description,
sku: product.baseProduct.response.attributes.sku,
image: product.baseProduct.main_image?.link.href,
// @ts-ignore - support for custom options
variesBy: product.baseProduct.meta?.variations?.map((variation) => variation.name),
};

const offers: Offer[] = buildOffers(product);
const productSchema: Product = buildProduct(product, offers)
productSchema.isVariantOf = {
"@id": product.baseProduct.response.attributes.slug,
};
// @ts-ignore - support for custom options
productSchema.options = [];
// @ts-ignore - child_variations is supported but missing from the sdk type.
response.meta.child_variations.forEach((variation) => {
if (variation.name.toLowerCase() === "color") {
productSchema.color = variation.option.name;

} else if (variation.name.toLowerCase() === "size") {
productSchema.size = variation.option.name;
} else {
// @ts-ignore - support for custom options
productSchema.options.push({
"@type": "PropertyValue",
name: variation.name,
value: variation.option.name,
});
}
})

productGroupSchema.hasVariant = [productSchema];
schema = addContext(productGroupSchema);
} else if (product.kind === "simple-product") {

const offers: Offer[] = buildOffers(product);
const productSchema: Product = buildProduct(product, offers)

schema = addContext(productSchema);
}

return (
<script id="product-schema" type="application/ld+json"
dangerouslySetInnerHTML={{
__html: schema,
}} />
);
};

export default ProductSchema;

function addContext(productSchema: Product) {
return JsonLd<Product>({
"@context": "https://schema.org",
...productSchema,
});
}

export function JsonLd<T extends Thing>(json: WithContext<T>): string {
return JSON.stringify(json);
}

function buildProduct(product: ShopperProduct, offers: Offer[]): Product {
const response = product.response;
return {
"@type": "Product",
name: response.attributes.name,
description: response.attributes.description,
sku: response.attributes.sku,
mpn: response.attributes.manufacturer_part_num,
image: product.main_image?.link.href,
offers,
};
}

function buildOffers(product: ShopperProduct): Offer[] {
if (!product.response.attributes.price) {
return [];
}
return Object.keys(product.response.attributes.price).map(key => {
return {
"@type": "Offer",
price: product.response.attributes.price[key].amount,
priceCurrency: key,
};
});
}

44 changes: 13 additions & 31 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b863664

Please sign in to comment.