From 296cfb80a03014ab8d7c137cd0cd2d85cf28a088 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 28 Nov 2022 22:19:32 +0100 Subject: [PATCH] Feat: JsonLd product metadata (#2) * Generates JSON ld structured metadata from a product & appends to head * Updated IProduct & IVariation interface * Added IProductResponse & IProductsResponse interfaces * Fixed sitemap urls having to many protocols --- src/lib/interfaces/ApiResponse.ts | 10 +++++ src/lib/interfaces/IProduct.ts | 19 ++++++---- src/lib/jsonld/product.ts | 38 +++++++++++++++++++ src/routes/checkout/+page.svelte | 2 +- src/routes/receipt/[[id]]/+page.svelte | 2 +- src/routes/shop/+page.server.ts | 3 +- src/routes/shop/+page.svelte | 2 +- src/routes/shop/ProductTile.svelte | 8 ++-- src/routes/shop/[id]/+page.server.ts | 3 +- src/routes/shop/[id]/+page.svelte | 14 +++++-- src/routes/sitemap.xml/+server.ts | 14 ++----- src/routes/warehouse/+page.svelte | 2 +- .../warehouse/WarehouseProductList.svelte | 6 +-- src/routes/warehouse/[id]/+page.svelte | 4 +- .../warehouse/[id]/DetailsSection.svelte | 2 +- .../warehouse/[id]/PricingSection.svelte | 11 +++--- src/routes/warehouse/[id]/edit/+page.svelte | 8 ++-- 17 files changed, 103 insertions(+), 45 deletions(-) create mode 100644 src/lib/jsonld/product.ts diff --git a/src/lib/interfaces/ApiResponse.ts b/src/lib/interfaces/ApiResponse.ts index b179200..76933c1 100644 --- a/src/lib/interfaces/ApiResponse.ts +++ b/src/lib/interfaces/ApiResponse.ts @@ -15,3 +15,13 @@ export interface IOrderSummaryResponse { success: boolean; order: IOrderSummary; } + +export interface IProductResponse { + success: boolean + product: IProduct +} + +export interface IProductsResponse { + success: boolean + products: Array +} \ No newline at end of file diff --git a/src/lib/interfaces/IProduct.ts b/src/lib/interfaces/IProduct.ts index a89a910..16e8245 100644 --- a/src/lib/interfaces/IProduct.ts +++ b/src/lib/interfaces/IProduct.ts @@ -1,16 +1,21 @@ -import type IProductVariation from './IProductVariation'; - -export default interface IProduct { +export interface IProduct { product_no: number; name: string; subtext?: string; description?: string; image: string; - currency: string; - variations?: IProductVariation[]; primary_color?: string; - variation_count?: string; - sum_stock?: number; updated?: Date; created?: Date; + variations?: IVariation[]; +} + +export interface IVariation { + sku_id: number; + price: number; + size: string; + stock: number; + default_price: boolean; + updated?: Date; + created?: Date; } diff --git a/src/lib/jsonld/product.ts b/src/lib/jsonld/product.ts new file mode 100644 index 0000000..6169d94 --- /dev/null +++ b/src/lib/jsonld/product.ts @@ -0,0 +1,38 @@ +import type { IProduct } from '../interfaces/IProduct' + +function structureProduct(product: IProduct) { + const output = product?.variations?.map(variation => { + return { + '@context': 'https://schema.org/', + '@type': 'Product', + name: `${product.name} - ${variation.size}`, + image: [product.image], + description: product.description, + sku: `${product.product_no}-${variation.sku_id}`, + productID: product.product_no, + mpn: product.product_no, + brand: { + '@type': 'Brand', + name: 'Planetposen' + }, + offers: { + '@type': 'Offer', + url: `https://planet.schleppe.cloud/shop/${product.product_no}`, + priceCurrency: 'NOK', + price: variation.price, + itemCondition: 'https://schema.org/NewCondition', + availability: 'https://schema.org/InStock' + } + } + }) + + return JSON.stringify(output); +} + +export default function generateProductJsonLd(product: IProduct): HTMLElement { + const jsonldScript = document.createElement('script'); + jsonldScript.setAttribute('type', 'application/ld+json'); + jsonldScript.textContent = structureProduct(product); + + return jsonldScript; +} diff --git a/src/routes/checkout/+page.svelte b/src/routes/checkout/+page.svelte index ab8e537..3dea9e0 100644 --- a/src/routes/checkout/+page.svelte +++ b/src/routes/checkout/+page.svelte @@ -7,7 +7,7 @@ import VippsHurtigkasse from '$lib/components/VippsHurtigkasse.svelte'; import { cart } from '$lib/cartStore'; - import type IProduct from '$lib/interfaces/IProduct'; + import type { IProduct } from '$lib/interfaces/IProduct'; function postOrder(event: any) { const formData = new FormData(event.target); diff --git a/src/routes/receipt/[[id]]/+page.svelte b/src/routes/receipt/[[id]]/+page.svelte index 1057023..2b88001 100644 --- a/src/routes/receipt/[[id]]/+page.svelte +++ b/src/routes/receipt/[[id]]/+page.svelte @@ -3,7 +3,7 @@ import { mockProducts } from '$lib/utils/mock'; import type { PageServerData } from './$types'; - import type IProduct from '$lib/interfaces/IProduct'; + import type { IProduct } from '$lib/interfaces/IProduct'; function subTotal(products: Array) { let total = 0; diff --git a/src/routes/shop/+page.server.ts b/src/routes/shop/+page.server.ts index 3fb51ab..e2602c7 100644 --- a/src/routes/shop/+page.server.ts +++ b/src/routes/shop/+page.server.ts @@ -1,5 +1,6 @@ import { dev } from '$app/environment'; import { env } from '$env/dynamic/private'; +import type { IProductsResponse } from '$lib/interfaces/ApiResponse'; import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ fetch }) => { @@ -9,7 +10,7 @@ export const load: PageServerLoad = async ({ fetch }) => { } const res = await fetch(url); - const products = await res.json(); + const products: IProductsResponse = await res.json(); return products; }; diff --git a/src/routes/shop/+page.svelte b/src/routes/shop/+page.svelte index a7750a2..21ac748 100644 --- a/src/routes/shop/+page.svelte +++ b/src/routes/shop/+page.svelte @@ -1,7 +1,7 @@
diff --git a/src/routes/sitemap.xml/+server.ts b/src/routes/sitemap.xml/+server.ts index cdff146..4ad1381 100644 --- a/src/routes/sitemap.xml/+server.ts +++ b/src/routes/sitemap.xml/+server.ts @@ -41,18 +41,16 @@ export async function GET() { return new Response(body, { headers }); } -function buildSitemapUrl(address: string, modified: string, frequency: string) { +function buildSitemapUrl(path: string, modified: string, frequency: string) { return ` - https://${address} + https://${domain}${path} ${modified} ${frequency} `; } function sitemapPages(): string { - return pages - .map((page) => buildSitemapUrl(`https://${domain}/${page.name}`, page.modified, 'yearly')) - .join('\n'); + return pages.map((page) => buildSitemapUrl(`/${page.name}`, page.modified, 'yearly')).join('\n'); } async function sitemapShopPages(): Promise { @@ -66,11 +64,7 @@ async function sitemapShopPages(): Promise { return products?.products ?.map((product) => - buildSitemapUrl( - `https://${domain}/shop/${product.product_no}`, - String(product.updated), - 'daily' - ) + buildSitemapUrl(`/shop/${product.product_no}`, String(product.updated), 'daily') ) .join('\n'); } diff --git a/src/routes/warehouse/+page.svelte b/src/routes/warehouse/+page.svelte index f24413a..f000eb3 100644 --- a/src/routes/warehouse/+page.svelte +++ b/src/routes/warehouse/+page.svelte @@ -1,8 +1,8 @@