mirror of
https://github.com/KevinMidboe/planetposen-frontend.git
synced 2025-10-29 13:10:12 +00:00
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:
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="no-NO">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
|||||||
9
src/lib/components/PageMeta.svelte
Normal file
9
src/lib/components/PageMeta.svelte
Normal 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>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type IProduct from './IProduct';
|
import type { IProduct } from './IProduct';
|
||||||
import type { IOrder, IOrderSummary } from './IOrder';
|
import type { IOrder, IOrderSummary } from './IOrder';
|
||||||
|
|
||||||
export interface IProductResponse {
|
export interface IProductResponse {
|
||||||
@@ -17,11 +17,11 @@ export interface IOrderSummaryResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IProductResponse {
|
export interface IProductResponse {
|
||||||
success: boolean
|
success: boolean;
|
||||||
product: IProduct
|
product: IProduct;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProductsResponse {
|
export interface IProductsResponse {
|
||||||
success: boolean
|
success: boolean;
|
||||||
products: Array<IProduct>
|
products: Array<IProduct>;
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,10 @@ export interface IProduct {
|
|||||||
description?: string;
|
description?: string;
|
||||||
image: string;
|
image: string;
|
||||||
primary_color?: string;
|
primary_color?: string;
|
||||||
|
|
||||||
|
variation_count?: string;
|
||||||
|
sum_stock?: number;
|
||||||
|
|
||||||
updated?: Date;
|
updated?: Date;
|
||||||
created?: Date;
|
created?: Date;
|
||||||
variations?: IVariation[];
|
variations?: IVariation[];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { IProduct } from '../interfaces/IProduct'
|
import type { IProduct } from '../interfaces/IProduct';
|
||||||
|
|
||||||
function structureProduct(product: IProduct) {
|
function structureProduct(product: IProduct) {
|
||||||
const output = product?.variations?.map(variation => {
|
const output = product?.variations?.map((variation) => {
|
||||||
return {
|
return {
|
||||||
'@context': 'https://schema.org/',
|
'@context': 'https://schema.org/',
|
||||||
'@type': 'Product',
|
'@type': 'Product',
|
||||||
@@ -23,16 +23,15 @@ function structureProduct(product: IProduct) {
|
|||||||
itemCondition: 'https://schema.org/NewCondition',
|
itemCondition: 'https://schema.org/NewCondition',
|
||||||
availability: 'https://schema.org/InStock'
|
availability: 'https://schema.org/InStock'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
return JSON.stringify(output);
|
return JSON.stringify(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function generateProductJsonLd(product: IProduct): HTMLElement {
|
export default function generateProductJsonLd(product: IProduct): string {
|
||||||
const jsonldScript = document.createElement('script');
|
return `<script type="application/ld+json">
|
||||||
jsonldScript.setAttribute('type', 'application/ld+json');
|
${structureProduct(product)}
|
||||||
jsonldScript.textContent = structureProduct(product);
|
</script>
|
||||||
|
`;
|
||||||
return jsonldScript;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
import Footer from './Footer.svelte';
|
import Footer from './Footer.svelte';
|
||||||
import CartModal from './CartModal.svelte';
|
import CartModal from './CartModal.svelte';
|
||||||
import { closeCart } from '$lib/cartStore';
|
import { closeCart } from '$lib/cartStore';
|
||||||
import requestSessionCookie from '$lib/utils/requestSessionCookie';
|
|
||||||
import { getCookie } from '$lib/utils/cookie';
|
import { getCookie } from '$lib/utils/cookie';
|
||||||
|
import requestSessionCookie from '$lib/utils/requestSessionCookie';
|
||||||
import { connectToCart, reconnectIfCartWSClosed } from '$lib/websocketCart';
|
import { connectToCart, reconnectIfCartWSClosed } from '$lib/websocketCart';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import PageMeta from '$lib/components/PageMeta.svelte';
|
||||||
import FrontText from '$lib/components/FrontText.svelte';
|
import FrontText from '$lib/components/FrontText.svelte';
|
||||||
import FrontTextImage from '$lib/components/FrontTextImage.svelte';
|
import FrontTextImage from '$lib/components/FrontTextImage.svelte';
|
||||||
import FrontTextImageBubble from '$lib/components/FrontTextImageBubble.svelte';
|
import FrontTextImageBubble from '$lib/components/FrontTextImageBubble.svelte';
|
||||||
@@ -53,11 +54,7 @@
|
|||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<PageMeta title="Planetposen" description="Planetposen hjemmeside" />
|
||||||
<title>Home</title>
|
|
||||||
<meta name="description" content="Svelte demo app" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<section class="frontpage">
|
<section class="frontpage">
|
||||||
<!-- {#each textImages as data}
|
<!-- {#each textImages as data}
|
||||||
<TextImageParralax {data} />
|
<TextImageParralax {data} />
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import OrderSection from './OrderSection.svelte';
|
import OrderSection from './OrderSection.svelte';
|
||||||
import DeliverySection from './DeliverySection.svelte';
|
import DeliverySection from './DeliverySection.svelte';
|
||||||
|
import PageMeta from '$lib/components/PageMeta.svelte';
|
||||||
import CheckoutButton from '$lib/components/Button.svelte';
|
import CheckoutButton from '$lib/components/Button.svelte';
|
||||||
import StripeCard from '$lib/components/StripeCard.svelte';
|
import StripeCard from '$lib/components/StripeCard.svelte';
|
||||||
import ApplePayButton from '$lib/components/ApplePayButton.svelte';
|
import ApplePayButton from '$lib/components/ApplePayButton.svelte';
|
||||||
import VippsHurtigkasse from '$lib/components/VippsHurtigkasse.svelte';
|
import VippsHurtigkasse from '$lib/components/VippsHurtigkasse.svelte';
|
||||||
import { cart } from '$lib/cartStore';
|
import { cart } from '$lib/cartStore';
|
||||||
|
|
||||||
import type { IProduct } from '$lib/interfaces/IProduct';
|
|
||||||
|
|
||||||
function postOrder(event: any) {
|
function postOrder(event: any) {
|
||||||
const formData = new FormData(event.target);
|
const formData = new FormData(event.target);
|
||||||
|
|
||||||
@@ -35,6 +34,11 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<PageMeta
|
||||||
|
title="Kasse"
|
||||||
|
description="Kasse for bestilling og betaling av produkter i handlekurven"
|
||||||
|
/>
|
||||||
|
|
||||||
<h1>Checkout</h1>
|
<h1>Checkout</h1>
|
||||||
<form class="checkout" on:submit|preventDefault="{postOrder}">
|
<form class="checkout" on:submit|preventDefault="{postOrder}">
|
||||||
<section id="delivery">
|
<section id="delivery">
|
||||||
|
|||||||
@@ -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>
|
<article>
|
||||||
<h1>Cookies</h1>
|
<h1>Cookies</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Badge from '$lib/components/Badge.svelte';
|
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';
|
import type { IOrder } from '$lib/interfaces/IOrder';
|
||||||
|
|
||||||
export let order: IOrder;
|
export let order: IOrder;
|
||||||
|
|||||||
@@ -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>
|
<article>
|
||||||
<h1>Personvernerklæring</h1>
|
<h1>Personvernerklæring</h1>
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import ProductTile from './ProductTile.svelte';
|
import ProductTile from './ProductTile.svelte';
|
||||||
|
import PageMeta from '$lib/components/PageMeta.svelte';
|
||||||
import type { IProduct } from '$lib/interfaces/IProduct';
|
import type { IProduct } from '$lib/interfaces/IProduct';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
const products = data.products as Array<IProduct>;
|
const products = data.products as Array<IProduct>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<PageMeta title="Nettbutikk" description="Planetposen nettbutikk" />
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<h1>Nettbutikk</h1>
|
<h1>Nettbutikk</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
|
import generateProductJsonLd from '$lib/jsonld/product';
|
||||||
import type { IProductResponse } from '$lib/interfaces/ApiResponse';
|
import type { IProductResponse } from '$lib/interfaces/ApiResponse';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
@@ -12,6 +13,11 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const product: IProductResponse = await res.json();
|
const productResponse: IProductResponse = await res.json();
|
||||||
return product;
|
const jsonld = generateProductJsonLd(productResponse?.product);
|
||||||
|
|
||||||
|
return {
|
||||||
|
product: productResponse?.product,
|
||||||
|
jsonld
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte'
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
import ProductTile from '../ProductTile.svelte';
|
import ProductTile from '../ProductTile.svelte';
|
||||||
import ProductVariationSelect from '$lib/components/ProductVariationSelect.svelte';
|
import ProductVariationSelect from '$lib/components/ProductVariationSelect.svelte';
|
||||||
import QuantitySelect from '$lib/components/QuantitySelect.svelte';
|
import QuantitySelect from '$lib/components/QuantitySelect.svelte';
|
||||||
import SizesSection from './SizesSection.svelte';
|
import SizesSection from './SizesSection.svelte';
|
||||||
import type { IProduct, IVariation } from '$lib/interfaces/IProduct';
|
|
||||||
import Button from '$lib/components/Button.svelte';
|
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;
|
export let data: PageData;
|
||||||
const product = data.product as IProduct;
|
const product = data.product as IProduct;
|
||||||
import { addProductToCart } from '$lib/websocketCart';
|
import { addProductToCart } from '$lib/websocketCart';
|
||||||
console.log('shop product:', product);
|
|
||||||
|
|
||||||
function setSelectedVariation(event: CustomEvent) {
|
function setSelectedVariation(event: CustomEvent) {
|
||||||
selectedVariation = event.detail;
|
selectedVariation = event.detail;
|
||||||
@@ -30,13 +27,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function defaultVariation() {
|
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 cooldownInputs = false;
|
||||||
let quantity = 1;
|
let quantity = 1;
|
||||||
let selectedVariation: IVariation | undefined = defaultVariation()
|
let selectedVariation: IVariation | undefined = defaultVariation();
|
||||||
|
|
||||||
$: addProductButtonText = cooldownInputs
|
$: addProductButtonText = cooldownInputs
|
||||||
? `${quantity} produkt${quantity > 1 ? 'er' : ''} lagt til`
|
? `${quantity} produkt${quantity > 1 ? 'er' : ''} lagt til`
|
||||||
: `Legg til ${quantity} i handlekurven`;
|
: `Legg til ${quantity} i handlekurven`;
|
||||||
@@ -71,9 +68,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<SizesSection />
|
<SizesSection />
|
||||||
|
|
||||||
|
{#if data?.jsonld}
|
||||||
|
{@html data.jsonld}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="scss" module="scoped">
|
<style lang="scss" module="scoped">
|
||||||
@import '../../../styles/media-queries.scss';
|
@import '../../../styles/media-queries.scss';
|
||||||
// @import "../styles/global.scss";
|
|
||||||
|
|
||||||
.product-container {
|
.product-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -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>
|
<article>
|
||||||
<h1>Betingelser og vilkår</h1>
|
<h1>Betingelser og vilkår</h1>
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -34,17 +34,17 @@
|
|||||||
|
|
||||||
<td class="stock-column">{product?.sum_stock}</td>
|
<td class="stock-column">{product?.sum_stock}</td>
|
||||||
|
|
||||||
<td class="date-column"
|
<td class="date-column">
|
||||||
>{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
|
{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
|
||||||
new Date(product.created || 0)
|
new Date(product.created || 0)
|
||||||
)}</td
|
)}
|
||||||
>
|
</td>
|
||||||
|
|
||||||
<td class="date-column"
|
<td class="date-column">
|
||||||
>{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
|
{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format(
|
||||||
new Date(product.updated || 0)
|
new Date(product.updated || 0)
|
||||||
)}</td
|
)}
|
||||||
>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user