diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..c70f208 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,68 @@ + + +
+
+ + + +
+ +
+ +
+ + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..1b4ac33 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,89 @@ + + + + Home + + + +
+ + + + + + + + + + + + +
+ + diff --git a/src/routes/+page.ts b/src/routes/+page.ts new file mode 100644 index 0000000..a72419a --- /dev/null +++ b/src/routes/+page.ts @@ -0,0 +1,3 @@ +// since there's no dynamic data here, we can prerender +// it so that it gets served as a static asset in production +export const prerender = true; diff --git a/src/routes/CartModal.svelte b/src/routes/CartModal.svelte new file mode 100644 index 0000000..45c81d0 --- /dev/null +++ b/src/routes/CartModal.svelte @@ -0,0 +1,287 @@ + + +{#if $isOpen} +
+
+
+

Cart

+ + {#if $cart?.length > 0} +
    + {#each $cart as cartItem} +
  • +
    + Product +
    + +
    +
    + {cartItem.name}, {cartItem.size} +
    Nok {cartItem.price * cartItem.quantity}.00
    +
    + +
    + + +
    + +
    +
    +
    +
  • + {/each} +
+ {:else} +

(empty)

+ {/if} +
+ + {#if $cart?.length > 0} +
+

Summary

+ +

Subtotal: Nok {$subTotal}

+ +
+ {/if} +
+
+{/if} + + diff --git a/src/routes/Footer.svelte b/src/routes/Footer.svelte new file mode 100644 index 0000000..f467ed2 --- /dev/null +++ b/src/routes/Footer.svelte @@ -0,0 +1,109 @@ + + + + + diff --git a/src/routes/Header.svelte b/src/routes/Header.svelte new file mode 100644 index 0000000..e698b53 --- /dev/null +++ b/src/routes/Header.svelte @@ -0,0 +1,161 @@ + + +
+
+ + + + 🌍 + +
+ + + +
+ +
+
+ + diff --git a/src/routes/checkout/+page.svelte b/src/routes/checkout/+page.svelte new file mode 100644 index 0000000..ab8e537 --- /dev/null +++ b/src/routes/checkout/+page.svelte @@ -0,0 +1,131 @@ + + +

Checkout

+
+
+

Leveringsaddresse

+ +
+ +
+

Din ordre

+ + + +
+ +
+

Betalingsinformasjon

+ + +
+ +
+
+
+ + diff --git a/src/routes/checkout/DeliverySection.svelte b/src/routes/checkout/DeliverySection.svelte new file mode 100644 index 0000000..73ffcef --- /dev/null +++ b/src/routes/checkout/DeliverySection.svelte @@ -0,0 +1,42 @@ + + +
+ + + + + + + + +
+ +
+
+ + diff --git a/src/routes/checkout/OrderSection.svelte b/src/routes/checkout/OrderSection.svelte new file mode 100644 index 0000000..7ddff40 --- /dev/null +++ b/src/routes/checkout/OrderSection.svelte @@ -0,0 +1,137 @@ + + + + + + + + + + + + + {#if $cart.length} + {#each $cart as order} + + + + + + {/each} + {:else} + + + + + + {/if} + + + + + + + + + + + + + + + + + + + +
VarenavnAntallPris
+
+ {order.name} + Størrelse: {order.size} +
+
+ + Nok {order.quantity * order.price}
(ingen produkter)0Nok 0
Totalpris:Nok {$subTotal}
Frakt:Nok {shippingPrice}
Totalsum:Nok {$subTotal}
+ + + + diff --git a/src/routes/cookies/+page.svelte b/src/routes/cookies/+page.svelte new file mode 100644 index 0000000..923a4d2 --- /dev/null +++ b/src/routes/cookies/+page.svelte @@ -0,0 +1,44 @@ +
+

Cookies

+ +
+

+ Generelt + "Cookies" er små tekstfiler som lagres i nettleseren din når du laster opp en nettside. De brukes + hovedsakelig til å forbedre brukeropplevelsen og huske hvem du er, slik at varer i din handlekurv + forblir tilgjengelig. +

+ +

+ Planetposen + Vi samler informasjon om navigasjon, tidsbruk, besøkte sider, nettleserversjon med mer. Førsteparts + informasjonskapsler er nødvendige for at nettstedet vårt skal fungere. +

+ +

+ Hva slags informasjonskapsler bruker vi? + Vi bruker informasjonskapsler for å beholde varer i handlekurven og sikre betalinger. Disse informasjonskapslene + er nødvendige for at nettstedet vårt skal fungere normalt. +

+ +

+ Tredjepart informasjonskapsler + Planetposen.no lagrer ingen informasjonskapsler tilknyttet noen tredjepart. +

+ +

+ Hvordan unngå informasjonskapsler? + De fleste nettlesere (Google Chrome, Firefox, Safari osv.) er satt til å akseptere informasjonskapsler + automatisk, men du kan selv endre disse innstillingene slik at informasjonskapsler ikke aksepteres. + Vær oppmerksom på at begrenset tilgang til informasjonskapsler kan påvirke brukeropplevelsen din. + Hvordan du administrerer informasjonskapsler finner du vanligvis i "Hjelp"-funksjonen i nettleseren + din. +

+ +

Siden ble sist endret: 16.11.2022

+
+
+ + diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..8e96e53 --- /dev/null +++ b/src/routes/login/+page.svelte @@ -0,0 +1,84 @@ + + +
+

Logg på

+ +
+ + + + + + {#if displayMessage} +
+

{displayMessage}

+
+ {/if} +
+
+ + diff --git a/src/routes/orders/+page.server.ts b/src/routes/orders/+page.server.ts new file mode 100644 index 0000000..33f3b62 --- /dev/null +++ b/src/routes/orders/+page.server.ts @@ -0,0 +1,18 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch }) => { + let url = '/api/orders'; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const response = await res.json(); + console.log('orders length:', response?.orders); + + return { + orders: response?.orders || [] + }; +}; diff --git a/src/routes/orders/+page.svelte b/src/routes/orders/+page.svelte new file mode 100644 index 0000000..01cccb7 --- /dev/null +++ b/src/routes/orders/+page.svelte @@ -0,0 +1,54 @@ + + +
+

Orders

+
+ {#if attentionOrders?.length} +

⚠️ orders needing attention

+ + + {/if} + +

📬 pending orders

+ + +

📦 in transit

+ + +

🙅‍♀️ cancelled/returns

+ + +

🏠🎁 delivered orders

+ +
+
+ + diff --git a/src/routes/orders/OrdersTable.svelte b/src/routes/orders/OrdersTable.svelte new file mode 100644 index 0000000..468c94b --- /dev/null +++ b/src/routes/orders/OrdersTable.svelte @@ -0,0 +1,101 @@ + + +{#if orders?.length} + + + + + + + + + + + + + + {#each orders as order} + + + + + + + + + + + {/each} + +
AmountStatusOrder IDCustomerDateReceipt
NOK {order.order_sum} + + {order.order_id}{order.first_name} {order.last_name}{order?.created + ? new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format( + new Date(order.created) + ) + : ''} + 🧾 +
+{:else} + no orders +{/if} + + diff --git a/src/routes/orders/[id]/+page.server.ts b/src/routes/orders/[id]/+page.server.ts new file mode 100644 index 0000000..ba13079 --- /dev/null +++ b/src/routes/orders/[id]/+page.server.ts @@ -0,0 +1,22 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { IOrderResponse } from '$lib/interfaces/ApiResponse'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch, params }) => { + const { id } = params; + + let url = `/api/order/${id}`; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const orderResponse: IOrderResponse = await res.json(); + + if (orderResponse?.success == false || orderResponse?.order === undefined) { + throw Error(':('); + } + + return { order: orderResponse.order }; +}; diff --git a/src/routes/orders/[id]/+page.svelte b/src/routes/orders/[id]/+page.svelte new file mode 100644 index 0000000..1f9c32a --- /dev/null +++ b/src/routes/orders/[id]/+page.svelte @@ -0,0 +1,55 @@ + + +

Order: {order.orderid}

+
+ +

{orderSubTotal()}.00 Nok

+ + + + + + + +
+ + diff --git a/src/routes/orders/[id]/CustomerDetails.svelte b/src/routes/orders/[id]/CustomerDetails.svelte new file mode 100644 index 0000000..f265a27 --- /dev/null +++ b/src/routes/orders/[id]/CustomerDetails.svelte @@ -0,0 +1,36 @@ + + +
+

Customer

+ +
+ + diff --git a/src/routes/orders/[id]/OrderProducts.svelte b/src/routes/orders/[id]/OrderProducts.svelte new file mode 100644 index 0000000..68a4c57 --- /dev/null +++ b/src/routes/orders/[id]/OrderProducts.svelte @@ -0,0 +1,112 @@ + + +

Products

+ + + + + + + + + + + + + + {#each lineItems as product} + + + + + + + + + + + + {/each} + +
NameTypeQuantityTotal
+ {product.name} + +

{product.name}

+
{product.size}{product.quantity}Nok {product.price * product.quantity}
+ + diff --git a/src/routes/orders/[id]/OrderSummary.svelte b/src/routes/orders/[id]/OrderSummary.svelte new file mode 100644 index 0000000..3a40fd8 --- /dev/null +++ b/src/routes/orders/[id]/OrderSummary.svelte @@ -0,0 +1,50 @@ + + + + + diff --git a/src/routes/orders/[id]/PaymentDetails.svelte b/src/routes/orders/[id]/PaymentDetails.svelte new file mode 100644 index 0000000..f4aba75 --- /dev/null +++ b/src/routes/orders/[id]/PaymentDetails.svelte @@ -0,0 +1,36 @@ + + +
+

Payment details

+ +
+ + diff --git a/src/routes/orders/[id]/TrackingDetails.svelte b/src/routes/orders/[id]/TrackingDetails.svelte new file mode 100644 index 0000000..bcf3014 --- /dev/null +++ b/src/routes/orders/[id]/TrackingDetails.svelte @@ -0,0 +1,35 @@ + + +{#if shipping} +
+

Tracking

+ +
+{/if} + + diff --git a/src/routes/orders/[id]/styles-order-page.scss b/src/routes/orders/[id]/styles-order-page.scss new file mode 100644 index 0000000..f450210 --- /dev/null +++ b/src/routes/orders/[id]/styles-order-page.scss @@ -0,0 +1,59 @@ +@import '../../../styles/media-queries.scss'; + +h2 { + width: 100%; + font-size: 1.5rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); +} + +section { + display: flex; + flex-direction: column; + width: 100%; + + @include tablet { + width: 45%; + } +} + +.label, +.empty { + color: grey; +} + +ul { + list-style: none; + padding-left: 0; + margin-top: 0; +} + +li { + padding: 0.4rem 0; + display: flex; + align-items: center; + white-space: pre; +} + +span.name { + text-transform: capitalize; +} + +ul.property-list { + li span:first-of-type { + display: inline-block; + min-width: 30%; + margin-bottom: auto; + + @include mobile { + min-width: 40%; + } + } + + li span:last-of-type { + @include mobile { + min-width: 60%; + white-space: normal; + overflow-wrap: break-word; + } + } +} diff --git a/src/routes/privacy-policy/+page.svelte b/src/routes/privacy-policy/+page.svelte new file mode 100644 index 0000000..fbdbbba --- /dev/null +++ b/src/routes/privacy-policy/+page.svelte @@ -0,0 +1,171 @@ +
+

Personvernerklæring

+
+

+ 1. Introduksjon + Planetposen Midbøe (org.nr 994 749 765) ("PLANETPOSEN MIDBØE") er et norsk selskap som produserer + og selger håndsyde miljøvennlige gaveposer, som blant annet selges på nettsiden planetposen.no. +

+ +

+ Denne personvernerklæringen gir informasjon om personopplysningene Planetposen samler inn, + behandler og utleverer, formålene som personopplysningene behandles for og dine rettigheter i + denne forbindelse. Planetposen vil behandle dine personopplysninger i samsvar med de til + enhver tid gjeldende lover om personvern. +

+

+ Planetposen representert ved daglig leder og nettstedets utvikler er behandlingsansvarlig for + behandlingen av dine personopplysninger. Den behandlingsansvarlige bestemmer formål og midler + for behandlingen av personopplysninger. +

+

+ Hvis du har spørsmål knyttet til Planetposen behandling av dine personopplysninger eller denne + personvernerklæringen, vennligst kontakt oss ved å bruke følgende kontaktinformasjon: +

+ +

+ Kontaktperson: Leigh Midbøe
+ E-post: contact@planetposen.no +

+ +

+ 2. Personopplysningene vi behandler, formål og rettslig grunnlag + Planetposen behandler følgende personlige, formål og juridiske grunnlag: +

+ +

+ Nettbutikksalg, hvor navn, kontaktinformasjon, epost, kredittkort og betalingsinformasjon + behandles. Det rettslige grunnlaget for slik behandling er at behandlingen er nødvendig for + gjennomføring av en kjøpsavtale som ble inngått med deg da du foretok et kjøp i nettbutikken. +

+ +

+ Oppsett og administrasjon av kundekontoer på nettsiden, hvor navn, e-postadresse, ditt valgte + passord og kjøpshistorikk behandles. Behandlingen har hjemmel i en interesseavveining, hvor + vår berettigede interesse er å yte god kundeservice ved å tilby kundekontoer med oversikt over + kjøpshistorikk og tilgang til å oppdatere kontaktinformasjon og andre personopplysninger. Det + er frivillig å opprette en profil og profilen kan til enhver tid slettes av kunden. Ved å + sende en e-post til slettmeg@planetposen.no +
Replace with delete me from order history. +

+ +

+ For å få informasjon om bruken av nettsiden vår, og for å forbedre brukeropplevelsen, bruker + Planetposen informasjonskapsler og behandler din IP-adresse. Vi anonymiserer informasjonen som + samles inn gjennom informasjonskapsler, slik at du ikke kan identifiseres som en individuell + person basert på denne informasjonen. Du kan lese mer om vår bruk av informasjonskapsler her. +

+ +

+ Svar på henvendelser vi mottar, hvor vi behandler navn, e-postadresse og eventuelle + personopplysninger som inngår i henvendelsen. Behandlingen har hjemmel i en + interesseavveining, hvor vår berettigede interesse er å følge opp dine henvendelser. +

+ +

+ Overholdelse av lovpålagte krav og rettslige forpliktelser, slik som overholdelse av + lovpålagte forpliktelser etter norsk regnskapslovgivning og pålegg fra offentlige myndigheter. +

+ +

+ Sikre rettighetene til Planetposen, til å etablere, utøve eller forsvare rettskrav vi mener å + ha, eller som er rettet mot oss av kunder, leverandører, partnere, andre tredjeparter eller + offentlige myndigheter. +

+ +

+ 3. OppbevaringstidPlanetposen vil ikke lagre dine personopplysninger lenger + enn nødvendig for å oppfylle formålene som personopplysningene ble samlet inn for. Hvis + behandlingen er basert på ditt samtykke, vil behandlingen opphøre når du trekker tilbake + samtykket. Du kan når som helst trekke tilbake samtykket ditt. +

+ +

+ Personopplysningene vil likevel bli oppbevart så lenge det er nødvendig for at Planetposen + skal kunne oppfylle lovpålagte krav og juridiske forpliktelser, herunder lagringskrav etter + norsk regnskapslovgivning. +

+ +

+ 4. Beskyttelse av personopplysninger + Planetposen verdsetter ditt personvern og har iverksatt hensiktsmessige sikkerhetstiltak for å + beskytte dine personopplysninger mot brudd på personopplysninger og uautorisert tilgang og utlevering + av personopplysninger. +

+ +

+ 5. Utlevering av personopplysninger til tredjeparter + + Planetposen vil kun utlevere dine personopplysninger til tredjepart dersom vi har juridisk + grunnlag for å gjøre det. +

+ +

+ Planetposen kan bruke databehandlere til å samle inn, lagre eller på annen måte behandle + personopplysninger på våre vegne. Forholdet til slike databehandlere er styrt av + databehandleravtaler, som blant annet sikrer konfidensialitet og informasjonssikkerhet for + dine personopplysninger. Dine personopplysninger kan bli gjenstand for behandling eller + lagring utenfor EU/EØS. I slike tilfeller vil Planetposen sørge for at personopplysningene er + underlagt hensiktsmessige sikkerhetstiltak, ved å gå inn i EUs standardkontraktsklausuler for + slike overføringer, overføringer til selskaper underlagt Privacy Shield-sertifisering eller + overføringer til land godkjent av EU-kommisjonen. Ta kontakt med oss hvis du trenger mer + informasjon. +

+ +

+ Planetposen kan utlevere personopplysninger til offentlige myndigheter for å etterleve + lovpålagte krav eller pålegg fra offentlige myndigheter, f.eks. å overholde forpliktelser + etter norsk regnskaps- eller skattelovgivning. De relevante offentlige myndigheter vil være + behandlingsansvarlig for personopplysninger som utleveres i slike tilfeller. +

+ +

+ 6. Sosiale medieplattformer + Dersom du «liker» eller melder deg inn for å bli medlem av Planetposens Facebook-side eller Planetposens + profiler på andre sosiale medieplattformer, vil denne informasjonen bli delt med den aktuelle plattformen. + Det samme gjelder andre aktiviteter på Planetposens sosiale medieplattformer, som innhold du legger + ut og innlegg du liker. Den aktuelle plattformen er ansvarlig for personopplysningene den samler + inn og behandler. Ytterligere informasjon om hvordan disse plattformene behandler personopplysninger + finnes i den aktuelle plattformens personvernerklæring. +

+ +

+ 7. Dine rettigheter + + Du har rett til å be om innsyn, retting og sletting av personopplysningene Planetposen + behandler om deg, forutsatt at kriteriene for slike forespørsler er oppfylt. Du kan også be om + begrensning av behandlingen, protestere mot behandlingen og kreve dataportabilitet (rett til å + motta personopplysninger eller få personopplysninger overført i et passende format), der + kriteriene er oppfylt. Hvis behandlingen er basert på ditt samtykke, kan du når som helst + trekke tilbake samtykket ditt. +

+ +

+ Hvis du ønsker å utøve rettighetene dine, vennligst kontakt oss ved å bruke + kontaktinformasjonen angitt ovenfor i seksjon 1. +

+ +

+ Du har rett til å klage til Datatilsynet på vår behandling av dine personopplysninger. Vi + oppfordrer deg imidlertid til å rette eventuelle innsigelser mot Planetposens behandling av + personopplysninger til oss først. +

+ +

+ 8.Endringer i personvernreglene + Den til enhver tid gjeldende personvernerklæringen vil være tilgjengelig på nettstedet vårt. Personvernerklæringen + vil bli oppdatert på nettsiden ved eventuelle endringer i Planetposens behandling av personopplysninger. +

+ +

+ Ved endringer i behandlingen av personopplysninger som krever ditt samtykke, vil vi innhente + ditt samtykke før vi iverksetter slik behandling. +

+ +

Siden ble sist endret: 16.11.2022

+
+
+ + diff --git a/src/routes/receipt/[[id]]/+layout.server.ts b/src/routes/receipt/[[id]]/+layout.server.ts new file mode 100644 index 0000000..809f72c --- /dev/null +++ b/src/routes/receipt/[[id]]/+layout.server.ts @@ -0,0 +1,10 @@ +import validOrderId from '$lib/utils/validOrderId'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = ({ params }) => { + const { id } = params; + return { + id, + isValidReceipt: validOrderId(id) + }; +}; diff --git a/src/routes/receipt/[[id]]/+layout.svelte b/src/routes/receipt/[[id]]/+layout.svelte new file mode 100644 index 0000000..08263c5 --- /dev/null +++ b/src/routes/receipt/[[id]]/+layout.svelte @@ -0,0 +1,14 @@ + + +{#if isValidReceipt} + +{:else} + +{/if} diff --git a/src/routes/receipt/[[id]]/+page.server.ts b/src/routes/receipt/[[id]]/+page.server.ts new file mode 100644 index 0000000..e284737 --- /dev/null +++ b/src/routes/receipt/[[id]]/+page.server.ts @@ -0,0 +1,36 @@ +import { redirect } from '@sveltejs/kit'; +import validOrderId from '$lib/utils/validOrderId'; +import type { Actions, PageServerLoad } from './$types'; + +export const load: PageServerLoad = ({ params, url }) => { + const { id } = params; + const email = url.searchParams.get('email'); + + return { + id, + email + }; +}; + +export const actions: Actions = { + default: async ({ request }) => { + const data = await request.formData(); + + const orderId = data.get('order-id'); + const email = data.get('order-email'); + + // TODO replace with posting form (json) to backend to check?? + // also return statusCode from the backend directly. + if (validOrderId(String(orderId)) && email) { + const receiptUrl = `/receipt/${orderId}?email=${email}`; + throw redirect(303, receiptUrl); + } + + return { success: false }; + } +}; + +// could we have order-id and send email to matching +// the order. +// user enters their email and return invalid or not, +// should have some aggressive rate-limiting diff --git a/src/routes/receipt/[[id]]/+page.svelte b/src/routes/receipt/[[id]]/+page.svelte new file mode 100644 index 0000000..1057023 --- /dev/null +++ b/src/routes/receipt/[[id]]/+page.svelte @@ -0,0 +1,87 @@ + + +
+ + +

Takk for din bestilling!

+ +
+

+ A payment to PLANETPOSEN, AS will appear on your statement with order number: + {id}. +

+

Order receipt has been email to: {email}

+
+ +
+ {#each products as product} +

+ {product.name} x{product.quantity} + {product.currency} {product.price * product.quantity} +

+ {/each} +

+ Shipping + NOK 79 +

+ +

+ Total + NOK {subTotal(products)} +

+
+
+ + diff --git a/src/routes/receipt/[[id]]/ReceiptNotFound.svelte b/src/routes/receipt/[[id]]/ReceiptNotFound.svelte new file mode 100644 index 0000000..050e761 --- /dev/null +++ b/src/routes/receipt/[[id]]/ReceiptNotFound.svelte @@ -0,0 +1,68 @@ + + +
+ + +

Fant ikke din bestilling!

+ + {#if id?.length} +
+

Bestilling med id: {id} er ikke funnet i systemet vårt.

+
+ {/if} + +
+ + diff --git a/src/routes/receipt/[[id]]/styles-receipt-page.scss b/src/routes/receipt/[[id]]/styles-receipt-page.scss new file mode 100644 index 0000000..1ae2a6a --- /dev/null +++ b/src/routes/receipt/[[id]]/styles-receipt-page.scss @@ -0,0 +1,38 @@ +@import '../../../styles/media-queries.scss'; + +.order-confirmation { + margin: 6rem auto 0; + display: grid; + place-items: center; + padding: 0 0.5rem; + + @include mobile { + margin-top: 3rem; + } + + // @include pageMargin; + + @include tablet { + padding: 0 1rem; + } + + @include desktop { + width: 80%; + max-width: 800px; + } + + h1 { + font-size: 2rem; + margin-bottom: 0; + } +} + +.order-description { + padding: 1rem; + margin: 1rem 0; + text-align: center; + + .underline { + text-decoration: underline; + } +} diff --git a/src/routes/shop/+page.server.ts b/src/routes/shop/+page.server.ts new file mode 100644 index 0000000..3fb51ab --- /dev/null +++ b/src/routes/shop/+page.server.ts @@ -0,0 +1,15 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch }) => { + let url = '/api/products'; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const products = await res.json(); + + return products; +}; diff --git a/src/routes/shop/+page.svelte b/src/routes/shop/+page.svelte new file mode 100644 index 0000000..a7750a2 --- /dev/null +++ b/src/routes/shop/+page.svelte @@ -0,0 +1,41 @@ + + +
+

Nettbutikk

+ +
+ {#each products as product} + + {/each} +
+
+ + diff --git a/src/routes/shop/ProductTile.svelte b/src/routes/shop/ProductTile.svelte new file mode 100644 index 0000000..e379a55 --- /dev/null +++ b/src/routes/shop/ProductTile.svelte @@ -0,0 +1,104 @@ + + + +
+ {#if !large}

{product?.name}

{/if} + +
+ {product?.name} +
+ + {#if !large} +

{product?.subtext}

+ {/if} +
+
+ + diff --git a/src/routes/shop/[id]/+page.server.ts b/src/routes/shop/[id]/+page.server.ts new file mode 100644 index 0000000..42aad11 --- /dev/null +++ b/src/routes/shop/[id]/+page.server.ts @@ -0,0 +1,16 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch, params }) => { + const { id } = params; + + let url = `/api/product/${id}`; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const product = await res.json(); + return product; +}; diff --git a/src/routes/shop/[id]/+page.svelte b/src/routes/shop/[id]/+page.svelte new file mode 100644 index 0000000..714744f --- /dev/null +++ b/src/routes/shop/[id]/+page.svelte @@ -0,0 +1,109 @@ + + +
+ + +
+

{product.name}

+

{product.subtext}

+

{product.description}

+

NOK {selectedVariation?.price} (Ink. Moms)

+ + + + + +
+
+
+
+ + + diff --git a/src/routes/shop/[id]/SizesSection.svelte b/src/routes/shop/[id]/SizesSection.svelte new file mode 100644 index 0000000..c3d0e99 --- /dev/null +++ b/src/routes/shop/[id]/SizesSection.svelte @@ -0,0 +1,26 @@ +
+

Våre størrelser

+ +

Dette er størrelsene vi har

+ +

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut + labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi. +

+

+ ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit + esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, + sunt in culpa qui officia deserunt mollit anim id est laborum. +

+
+ + diff --git a/src/routes/sitemap.xml/+server.ts b/src/routes/sitemap.xml/+server.ts new file mode 100644 index 0000000..cdff146 --- /dev/null +++ b/src/routes/sitemap.xml/+server.ts @@ -0,0 +1,87 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { IProductResponse } from '$lib/interfaces/ApiResponse'; + +const domain = 'planet.schleppe.cloud'; +const pages: Array = [ + { + name: 'home', + modified: '2022-11-16T14:00:00.000Z' + }, + { + name: 'shop', + modified: '2022-11-16T14:00:00.000Z' + }, + { + name: 'privacy-policy', + modified: '2022-11-16T14:00:00.000Z' + }, + { + name: 'cookies', + modified: '2022-11-16T14:00:00.000Z' + }, + { + name: 'terms-and-condition', + modified: '2022-11-16T14:00:00.000Z' + } +]; + +interface ISitemapPage { + name: string; + modified: string; +} + +export async function GET() { + const body = await buildSitemap(); + const headers = { + 'Content-Type': 'application/xml', + 'Cache-Control': `max-age=0, s-max-age=${3600}` + }; + + return new Response(body, { headers }); +} + +function buildSitemapUrl(address: string, modified: string, frequency: string) { + return ` + https://${address} + ${modified} + ${frequency} +`; +} + +function sitemapPages(): string { + return pages + .map((page) => buildSitemapUrl(`https://${domain}/${page.name}`, page.modified, 'yearly')) + .join('\n'); +} + +async function sitemapShopPages(): Promise { + let url = `/api/products`; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const products: IProductResponse = await res.json(); + + return products?.products + ?.map((product) => + buildSitemapUrl( + `https://${domain}/shop/${product.product_no}`, + String(product.updated), + 'daily' + ) + ) + .join('\n'); +} + +async function buildSitemap(): Promise { + const generalPages = sitemapPages(); + const shopPages = await sitemapShopPages(); + + return ` + + ${generalPages} + ${shopPages} + `.trim(); +} diff --git a/src/routes/styles.css b/src/routes/styles.css new file mode 100644 index 0000000..f95624d --- /dev/null +++ b/src/routes/styles.css @@ -0,0 +1,100 @@ +:root { + --font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, + Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --font-mono: 'Fira Mono', monospace; + --background: #f3efec; + --color-bg-1: hsl(209, 36%, 86%); + --color-bg-2: hsl(224, 44%, 95%); + --color-theme-1: #18332f; + --color-theme-2: #40b3ff; + --color-text: #231f20; + --column-width: 42rem; + --column-margin-top: 4rem; + font-family: var(--font-body); + color: var(--color-text); +} + +body { + min-height: 100vh; + margin: 0; +} + +h1, +h2, +p { + font-weight: 400; +} + +p { + line-height: 1.5; +} + +a { + color: var(--color-theme-1); + color: var(--color-text); + text-decoration: none; + + -webkit-transition: -webkit-transform 0.15s linear; + transition: -webkit-transform 0.15s linear; + transition: transform 0.15s linear; + transition: transform 0.15s linear, -webkit-transform 0.15s linear; + -webkit-transform-origin: 50% 80%; + transform-origin: 50% 80%; +} + +a:hover { + text-decoration: underline; + transform: skew(-15deg); +} + +a.link { + border-bottom: 2px solid var(--color-text) !important; +} + +h1 { + font-size: 2rem; +} + +h2 { + font-size: 1rem; +} + +.no-scroll { + overflow: hidden; +} + +pre { + font-size: 16px; + font-family: var(--font-mono); + background-color: rgba(255, 255, 255, 0.45); + border-radius: 3px; + box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); + padding: 0.5em; + overflow-x: auto; + color: var(--color-text); +} + +.text-column { + display: flex; + max-width: 48rem; + flex: 0.6; + flex-direction: column; + justify-content: center; + margin: 0 auto; +} + +input, +button { + font-size: inherit; + font-family: inherit; +} + +button:focus:not(:focus-visible) { + outline: none; +} + +@media (min-width: 720px) { + h1 { + font-size: 2.4rem; + } +} diff --git a/src/routes/terms-and-conditions/+page.svelte b/src/routes/terms-and-conditions/+page.svelte new file mode 100644 index 0000000..940e794 --- /dev/null +++ b/src/routes/terms-and-conditions/+page.svelte @@ -0,0 +1,47 @@ +
+

Betingelser og vilkår

+
+

+ Leveranseinformasjon + Vi kommer tilbake til deg innen en uke etter bestilling med endelig leveringsdato. Estimert leveringsdato + er 2-5 uker fra bestillingen din ble sendt, avhengig av tilgjengeligheten på produktene i bestillingen + din. Vår transportpartner på innenlandsleveringer er Schencker, som leverer på døren på hverdager + mellom 15:00 og 21:00 norsk tid. +

+ +

+ Bestillings- og kontraktsprosess + Når vi mottar bestillingen din, vil en ordrebekreftelse automatisk bli sendt til din registrerte + e-post. Vennligst sjekk at den er i samsvar med bestillingen: Produktene du har bestilt og kostnadene + deres. +

+

+ Bestillingen er bindende når den er registrert i vår nettbutikk og betaling er foretatt eller + utstedt faktura. Vi er bundet av bestillingen din dersom den ikke avviker fra det vi tilbyr i + vår nettbutikk, markedsføring eller annet. +

+

+ Du har fortsatt rett til å angre på kjøpet i henhold til norsk lov; Angrerettloven, Det gir + deg rett til å angre på kjøpet ditt hos oss innen 14 dager etter mottak av varen. Du må + imidlertid betale returportoen ved å legge ved returskjemaet som er vedlagt + e-postbekreftelsen. Les mer om det her. +

+ +

+ Informasjon gitt i nettbutikken + Vi streber etter å alltid gi våre kunder korrekt og oppdatert informasjon om alle våre produkter. + Vi forbeholder oss imidlertid retten til at det kan oppstå trykkfeil, og at vi på grunn av dette + ikke er i stand til å levere i henhold til informasjonen gitt i vår nettbutikk, markedsføring eller + på annen måte. Husk at produktene våre i stor grad er laget av naturlige materialer og variasjoner + i mønsterfarge og utførelse vil forekomme. Dette gir ikke krav eller angrerett på kjøp. Videre + forbeholder vi oss retten til å kansellere din bestilling eller deler av din bestilling, dersom + det kjøpte produktet tas ut av produksjon eller endres med tanke på ytelse eller kvalitet. +

+ +

Siden ble sist endret: 16.11.2022

+
+
+ + diff --git a/src/routes/warehouse/+page.server.ts b/src/routes/warehouse/+page.server.ts new file mode 100644 index 0000000..e548827 --- /dev/null +++ b/src/routes/warehouse/+page.server.ts @@ -0,0 +1,17 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch }) => { + let url = '/api/warehouse'; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const warehouse = await res.json(); + + return { + products: warehouse?.warehouse + }; +}; diff --git a/src/routes/warehouse/+page.svelte b/src/routes/warehouse/+page.svelte new file mode 100644 index 0000000..f24413a --- /dev/null +++ b/src/routes/warehouse/+page.svelte @@ -0,0 +1,45 @@ + + +
+

Warehouse

+ +
+

Your products

+ + +
+ +
+ + diff --git a/src/routes/warehouse/WarehouseProductList.svelte b/src/routes/warehouse/WarehouseProductList.svelte new file mode 100644 index 0000000..9a4af64 --- /dev/null +++ b/src/routes/warehouse/WarehouseProductList.svelte @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + {#each products as product} + + + + + + + + + + + + {/each} + +
NameIn-Stockcreatedupdated
+ {product.name} + +

{product.name}

+

{product.variation_count} variation(s)

+
{product.sum_stock}{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format( + new Date(product.created || 0) + )}{new Intl.DateTimeFormat('nb-NO', { dateStyle: 'short', timeStyle: 'short' }).format( + new Date(product.updated || 0) + )}
+ + diff --git a/src/routes/warehouse/[id]/+page.server.ts b/src/routes/warehouse/[id]/+page.server.ts new file mode 100644 index 0000000..57c7901 --- /dev/null +++ b/src/routes/warehouse/[id]/+page.server.ts @@ -0,0 +1,18 @@ +import { dev } from '$app/environment'; +import { env } from '$env/dynamic/private'; +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch, params }) => { + const { id } = params; + + // let url = `/api/warehouse/product/${id}`; + let url = `/api/warehouse/${id}`; + if (dev || env.API_HOST) { + url = (env.API_HOST || 'http://localhost:30010').concat(url); + } + + const res = await fetch(url); + const product = await res.json(); + console.log('product::', product); + return product; +}; diff --git a/src/routes/warehouse/[id]/+page.svelte b/src/routes/warehouse/[id]/+page.svelte new file mode 100644 index 0000000..6dd9767 --- /dev/null +++ b/src/routes/warehouse/[id]/+page.svelte @@ -0,0 +1,91 @@ + + +

Product details

+
+ Product +
+

{product.name}

+

NOK {product.price}

+
+ +
+ {#if !edit} +
+
+ +

Details

+ + +

Variations

+ + +

Metadata

+
+

No metadata

+
+ +

Audit log

+
+

No logs

+
+ + diff --git a/src/routes/warehouse/[id]/DetailsSection.svelte b/src/routes/warehouse/[id]/DetailsSection.svelte new file mode 100644 index 0000000..ea32020 --- /dev/null +++ b/src/routes/warehouse/[id]/DetailsSection.svelte @@ -0,0 +1,146 @@ + + +
+
+
    +
  • + Name + {#if !edit} + {product.name} + {:else} + + {/if} +
  • + +
  • + Created + {product.created} +
  • + +
  • + Subtext + {#if !edit} + {product.subtext || '(empty)'} + {:else} + + {/if} +
  • + +
  • + Description + {#if !edit} + {product.description || '(empty)'} + {:else} + + {/if} +
  • + +
  • + Primary color + {#if !edit} + {product.primary_color || '(empty)'} + {:else} + + {/if} + {#if product.primary_color} +
    + {/if} +
  • + +
  • + Feature list + (empty) +
  • +
+
+ +
+ Image + Product +
+
+ + diff --git a/src/routes/warehouse/[id]/PricingSection.svelte b/src/routes/warehouse/[id]/PricingSection.svelte new file mode 100644 index 0000000..c3a695e --- /dev/null +++ b/src/routes/warehouse/[id]/PricingSection.svelte @@ -0,0 +1,257 @@ + + + + + + + + + + {#if editingVariationIndex >= 0} + + + {:else} + + + {/if} + + + + + {#if product?.variations?.length} + {#each product?.variations as variation, index} + {#if editingVariationIndex !== index} + + + + + + + + {:else} + + + + + + + + {/if} + {/each} + {/if} + +
PriceStockType/sizeSaveDelete
+ Nok {variation.price} + {#if variation.default_price} +
+ +
+ {/if} +
{variation.stock}{variation.size}
+ Nok + + {#if variation.default_price} +
+ +
+ {/if} +
+ + + +
+ +