Feat: Refactor jsonld & method to update document title and description (#4)

* 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

* Implemented jsonld for product w/ variations

* Aligned Product responses between backend & frontend

* PageMeta for updating head meta values: title & description

Use on any page where we want to display a unique meta page title &
description

* Set document language to norwegian

* Linting
This commit is contained in:
2022-11-30 00:39:04 +01:00
committed by GitHub
parent 296cfb80a0
commit b2a15be12e
18 changed files with 103 additions and 61 deletions

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="no-NO">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />

View File

@@ -0,0 +1,9 @@
<script lang="ts">
export let title: string;
export let description: string;
</script>
<svelte:head>
<title>{title}</title>
<meta name="description" content="{description}" />
</svelte:head>

View File

@@ -1,4 +1,4 @@
import type IProduct from './IProduct';
import type { IProduct } from './IProduct';
import type { IOrder, IOrderSummary } from './IOrder';
export interface IProductResponse {
@@ -17,11 +17,11 @@ export interface IOrderSummaryResponse {
}
export interface IProductResponse {
success: boolean
product: IProduct
success: boolean;
product: IProduct;
}
export interface IProductsResponse {
success: boolean
products: Array<IProduct>
}
success: boolean;
products: Array<IProduct>;
}

View File

@@ -5,17 +5,21 @@ export interface IProduct {
description?: string;
image: string;
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;
sku_id: number;
price: number;
size: string;
stock: number;
default_price: boolean;
updated?: Date;
created?: Date;
}

View File

@@ -1,7 +1,7 @@
import type { IProduct } from '../interfaces/IProduct'
import type { IProduct } from '../interfaces/IProduct';
function structureProduct(product: IProduct) {
const output = product?.variations?.map(variation => {
const output = product?.variations?.map((variation) => {
return {
'@context': 'https://schema.org/',
'@type': 'Product',
@@ -23,16 +23,15 @@ function structureProduct(product: IProduct) {
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;
export default function generateProductJsonLd(product: IProduct): string {
return `<script type="application/ld+json">
${structureProduct(product)}
</script>
`;
}

View File

@@ -5,8 +5,8 @@
import Footer from './Footer.svelte';
import CartModal from './CartModal.svelte';
import { closeCart } from '$lib/cartStore';
import requestSessionCookie from '$lib/utils/requestSessionCookie';
import { getCookie } from '$lib/utils/cookie';
import requestSessionCookie from '$lib/utils/requestSessionCookie';
import { connectToCart, reconnectIfCartWSClosed } from '$lib/websocketCart';
import './styles.css';

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import PageMeta from '$lib/components/PageMeta.svelte';
import FrontText from '$lib/components/FrontText.svelte';
import FrontTextImage from '$lib/components/FrontTextImage.svelte';
import FrontTextImageBubble from '$lib/components/FrontTextImageBubble.svelte';
@@ -53,11 +54,7 @@
];
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<PageMeta title="Planetposen" description="Planetposen hjemmeside" />
<section class="frontpage">
<!-- {#each textImages as data}
<TextImageParralax {data} />

View File

@@ -132,13 +132,13 @@
}
// li[aria-current='page']::after, li:hover::after {
// content: '';
// width: 90%;
// height: 2px;
// position: absolute;
// bottom: 0;
// margin-left: 5%;
// background-color: var(--color-text);
// content: '';
// width: 90%;
// height: 2px;
// position: absolute;
// bottom: 0;
// margin-left: 5%;
// background-color: var(--color-text);
// }
nav a {
@@ -156,6 +156,6 @@
}
// a:hover {
// color: var(--color-theme-1);
// color: var(--color-theme-1);
// }
</style>

View File

@@ -1,14 +1,13 @@
<script lang="ts">
import OrderSection from './OrderSection.svelte';
import DeliverySection from './DeliverySection.svelte';
import PageMeta from '$lib/components/PageMeta.svelte';
import CheckoutButton from '$lib/components/Button.svelte';
import StripeCard from '$lib/components/StripeCard.svelte';
import ApplePayButton from '$lib/components/ApplePayButton.svelte';
import VippsHurtigkasse from '$lib/components/VippsHurtigkasse.svelte';
import { cart } from '$lib/cartStore';
import type { IProduct } from '$lib/interfaces/IProduct';
function postOrder(event: any) {
const formData = new FormData(event.target);
@@ -35,6 +34,11 @@
}
</script>
<PageMeta
title="Kasse"
description="Kasse for bestilling og betaling av produkter i handlekurven"
/>
<h1>Checkout</h1>
<form class="checkout" on:submit|preventDefault="{postOrder}">
<section id="delivery">

View File

@@ -1,3 +1,8 @@
<script lang="ts">
import PageMeta from '$lib/components/PageMeta.svelte';
</script>
<PageMeta title="Cookies" description="Beskrivelse av nettsidens bruk av cookies" />
<article>
<h1>Cookies</h1>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import Badge from '$lib/components/Badge.svelte';
import type BadgeType from '$lib/interfaces/BadgeType';
// import type BadgeType from '$lib/interfaces/BadgeType';
import type { IOrder } from '$lib/interfaces/IOrder';
export let order: IOrder;

View File

@@ -1,3 +1,11 @@
<script lang="ts">
import PageMeta from '$lib/components/PageMeta.svelte';
</script>
<PageMeta
title="Personvernerklæring"
description="Personvernerklæring for planetposen nettbutikk"
/>
<article>
<h1>Personvernerklæring</h1>
<section>

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import type { PageData } from './$types';
import ProductTile from './ProductTile.svelte';
import PageMeta from '$lib/components/PageMeta.svelte';
import type { IProduct } from '$lib/interfaces/IProduct';
export let data: PageData;
const products = data.products as Array<IProduct>;
</script>
<PageMeta title="Nettbutikk" description="Planetposen nettbutikk" />
<div class="page">
<h1>Nettbutikk</h1>

View File

@@ -13,7 +13,7 @@
}`}"
>
{#if !large}
<h3>{product?.name}</h3>
<h3>{product?.name}</h3>
{/if}
<div class="{`image-frame ${large ? 'large' : null}`}">

View File

@@ -1,5 +1,6 @@
import { dev } from '$app/environment';
import { env } from '$env/dynamic/private';
import generateProductJsonLd from '$lib/jsonld/product';
import type { IProductResponse } from '$lib/interfaces/ApiResponse';
import type { PageServerLoad } from './$types';
@@ -12,6 +13,11 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
}
const res = await fetch(url);
const product: IProductResponse = await res.json();
return product;
const productResponse: IProductResponse = await res.json();
const jsonld = generateProductJsonLd(productResponse?.product);
return {
product: productResponse?.product,
jsonld
};
};

View File

@@ -1,18 +1,15 @@
<script lang="ts">
import { onMount } from 'svelte'
import type { PageData } from './$types';
import ProductTile from '../ProductTile.svelte';
import ProductVariationSelect from '$lib/components/ProductVariationSelect.svelte';
import QuantitySelect from '$lib/components/QuantitySelect.svelte';
import SizesSection from './SizesSection.svelte';
import type { IProduct, IVariation } from '$lib/interfaces/IProduct';
import Button from '$lib/components/Button.svelte';
import generateProductJsonLd from '$lib/jsonld/product';
import type { PageData } from './$types';
import type { IProduct, IVariation } from '$lib/interfaces/IProduct';
export let data: PageData;
const product = data.product as IProduct;
import { addProductToCart } from '$lib/websocketCart';
console.log('shop product:', product);
function setSelectedVariation(event: CustomEvent) {
selectedVariation = event.detail;
@@ -30,13 +27,13 @@
}
function defaultVariation() {
return product.variations?.find(variation => variation.default_price)
return product.variations?.find((variation) => variation.default_price);
}
let jsonLd: HTMLElement;
let cooldownInputs = false;
let quantity = 1;
let selectedVariation: IVariation | undefined = defaultVariation()
let selectedVariation: IVariation | undefined = defaultVariation();
$: addProductButtonText = cooldownInputs
? `${quantity} produkt${quantity > 1 ? 'er' : ''} lagt til`
: `Legg til ${quantity} i handlekurven`;
@@ -71,9 +68,12 @@
</div>
<SizesSection />
{#if data?.jsonld}
{@html data.jsonld}
{/if}
<style lang="scss" module="scoped">
@import '../../../styles/media-queries.scss';
// @import "../styles/global.scss";
.product-container {
display: grid;

View File

@@ -1,3 +1,11 @@
<script lang="ts">
import PageMeta from '$lib/components/PageMeta.svelte';
</script>
<PageMeta
title="Betingelser & vilkår"
description="Beskrivelse av alle betingelser og vilkår tilknyttet bruk av planetposen nettbutikk"
/>
<article>
<h1>Betingelser og vilkår</h1>
<section>

View File

@@ -34,17 +34,17 @@
<td class="stock-column">{product?.sum_stock}</td>
<td class="date-column"
>{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
<td class="date-column">
{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
new Date(product.created || 0)
)}</td
>
)}
</td>
<td class="date-column"
>{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
<td class="date-column">
{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
new Date(product.updated || 0)
)}</td
>
)}
</td>
</tr>
{/each}
</tbody>