Compare commits

...

4 Commits

Author SHA1 Message Date
b56be97f86 Use env var API_HOST to direct where backend lives. 2024-03-03 19:23:20 +01:00
6d2550f2f3 Upload docker image to GHCR & deploy to kubernetes cluster 2024-03-03 19:11:25 +01:00
71e053297e Feat: Frontpage text content (#9)
* Front text blocks text content updated

* Health route

* Linting
2023-06-03 11:10:37 +02:00
63a1107427 Patch: Receipt page (#7)
* OrderSection receipts list of lineItems instead of always getting cart

* Hide express checkout behind feature flag

* Add torn receipt paper css look to order list

* Re-use OrderSection on receipt page

* Linting

* Reduced max font from 130->120%, now only applies scaling on div.main

Reducing relative font size for the largest screen width.
Scaling only applies to main container, not header and footer.

* Minor header size changes

* Set max-width to login input elements on desktop

* Prettier doesn't liek shorthand props
2023-03-28 18:35:14 +02:00
23 changed files with 313 additions and 128 deletions

View File

@@ -1,53 +1,130 @@
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: Lint and build project name: Build
platform: platform:
os: linux os: linux
arch: amd64 arch: amd64
steps: steps:
- name: Build project - name: Install dependencies
image: node:18 image: node:21-alpine3.17
commands: commands:
- yarn - yarn
- yarn build
- name: Lint project - name: Lint project
image: node:18 image: node:21-alpine3.17
commands: commands:
- yarn
- yarn lint - yarn lint
- name: Build
image: node:21-alpine3.17
commands:
- yarn build
--- ---
kind: pipeline kind: pipeline
type: docker type: docker
name: Compile docker image name: Publish
platform: platform:
os: linux os: linux
arch: amd64 arch: amd64
steps: steps:
- name: Build - name: Publish to ghcr
image: node:18 image: plugins/docker
commands: settings:
- yarn registry: ghcr.io
- yarn build repo: ghcr.io/kevinmidboe/${DRONE_REPO_NAME}
dockerfile: Dockerfile
depends_on: username:
- Lint and build project from_secret: GITHUB_USERNAME
password:
from_secret: GHCR_UPLOAD_TOKEN
tags:
- latest
- ${DRONE_COMMIT_SHA}
trigger: trigger:
branch:
- main
event: event:
include:
- push
exclude: exclude:
- pull_request - pull_request
branch:
- main
depends_on:
- Build
---
kind: pipeline
type: docker
name: Deploy
platform:
os: linux
arch: amd64
steps:
- name: Prepare kubernetes environment
image: alpine/k8s:1.25.15
environment:
VAULT_TOKEN:
from_secret: VAULT_TOKEN
VAULT_HOST:
from_secret: VAULT_HOST
commands:
- mkdir -p /root/.kube
- echo "IMAGE=ghcr.io/kevinmidboe/${DRONE_REPO_NAME}:${DRONE_COMMIT_SHA}" > /root/.kube/.env
- echo "NAMESPACE=${DRONE_REPO_NAME}" >> /root/.kube/.env
- 'curl -s
-H "X-Vault-Token: $VAULT_TOKEN"
$VAULT_HOST/v1/schleppe/data/kazan/_infra
| jq -r ".data.data.KUBE_CONFIG" > /root/.kube/config'
- 'curl -s
-H "X-Vault-Token: $VAULT_TOKEN"
$VAULT_HOST/v1/schleppe/data/kazan/_infra
| jq -cr ".data.data | .[\"ghcr-login-secret\"] | @base64" > /root/.kube/dockerconfig.json'
- echo "DOCKER_CONFIG=$(cat /root/.kube/dockerconfig.json)" >> /root/.kube/.env
- sed -i '/^$/!s/^/export /' /root/.kube/.env
volumes:
- name: kube-config
path: /root/.kube
- name: Deploy to kubernetes
image: alpine/k8s:1.25.15
commands:
- source /root/.kube/.env > /dev/null 2>&1
- cat .kubernetes/*.yml
| envsubst
| kubectl --kubeconfig=/root/.kube/config apply -f -
volumes:
- name: kube-config
path: /root/.kube
trigger:
event:
include:
- push
exclude:
- pull_request
branch:
- main
depends_on:
- Build
- Publish
volumes:
- name: kube-config
temp: {}
--- ---
kind: signature kind: signature
hmac: 84765f19d995d66f1d3409c4eddd1f68d1f2d297d65cd9e2612e6bb13e8ecb94 hmac: 1e803c7610cc5d3b586af3f10228a4a3477d877538813dee6c366c952771e3e0
... ...

View File

@@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: planet
labels:
name: planet

View File

@@ -0,0 +1,41 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: planet-frontend
name: planet-frontend
namespace: planet
spec:
progressDeadlineSeconds: 600
replicas: 2
revisionHistoryLimit: 10
selector:
matchLabels:
app: planet-frontend
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: planet-frontend
spec:
containers:
- image: ${IMAGE}
imagePullPolicy: Always
name: planet-frontend
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullSecrets:
- name: ghcr-login-secret
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30

19
.kubernetes/ingress.yml Normal file
View File

@@ -0,0 +1,19 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: planet-frontend-ingress
namespace: planet
spec:
ingressClassName: traefik
rules:
- host: planet.kazan.schleppe.cloud
http:
paths:
- backend:
service:
name: planet-frontend-service
port:
number: 80
path: /
pathType: Prefix

18
.kubernetes/service.yml Normal file
View File

@@ -0,0 +1,18 @@
---
apiVersion: v1
kind: Service
metadata:
labels:
app: planet-frontend
name: planet-frontend-service
namespace: planet
spec:
selector:
app: planet-frontend
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 3000
sessionAffinity: None

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Build the project
FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN yarn
RUN yarn build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/build build/
COPY --from=builder /app/node_modules node_modules/
COPY package.json .
EXPOSE 3000
ENV NODE_ENV=production
CMD [ "node", "build" ]

View File

@@ -12,6 +12,7 @@
"format": "prettier --plugin-search-dir . --write src" "format": "prettier --plugin-search-dir . --write src"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^4.0.1",
"@sveltejs/adapter-static": "1.0.0", "@sveltejs/adapter-static": "1.0.0",
"@sveltejs/kit": "1.0.1", "@sveltejs/kit": "1.0.1",
"@types/cookie": "0.5.1", "@types/cookie": "0.5.1",

View File

@@ -1,11 +1,13 @@
import { env } from '$env/dynamic/private';
import type { HandleFetch } from '@sveltejs/kit'; import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ request, fetch }) => { export const handleFetch: HandleFetch = async ({ request, fetch }) => {
const { origin } = new URL(request.url); const { origin } = new URL(request.url);
const host = env?.API_HOST || 'http://localhost:30010';
if (request.url.startsWith(`${origin}/api`)) { if (request.url.startsWith(`${origin}/api`)) {
// clone the original request, but change the URL // clone the original request, but change the URL
request = new Request(request.url.replace(origin, 'http://localhost:30010'), request); request = new Request(request.url.replace(origin, host), request);
} }
return fetch(request); return fetch(request);

View File

@@ -10,19 +10,20 @@
const textImages: Array<IFrontTextImage> = [ const textImages: Array<IFrontTextImage> = [
{ {
title: 'Vårt oppdrag', title: 'Our story',
text: 'The new fabulous museum at Kistefos, designed by world renowned architect Bjarke Ingels Group, BIG, opened Wednesday September 18th, 2019. The building has been named the top architectural museum project in the world to open in 2019, by both the Daily Telegraph and Bloomberg.', text: 'I started making fabric gift bags as a way to combat the wastefulness of traditional paper packaging. As a lifelong crafter and DIY enthusiast, I wanted to create a product that was both beautiful and sustainable. After experimenting with different materials and designs, I landed on the perfect formula for a reusable fabric gift bag that was both eco-friendly and functional.',
image: 'https://storage.googleapis.com/planetposen-images/front-kf-1.jpg' image: 'https://storage.googleapis.com/planetposen-images/front-kf-1.jpg'
}, },
{ {
title: 'Paper waste and the planet', title: 'Eco-Friendly Materials',
text: "As the 50th artwork to be included in the park, a site-specific new commission by French artist Pierre Huyghe (b. 1962, Paris) was opened on the 12th of June. The vast permanent work will be the artist's largest site-specific work to date and the most ambitious to ever be conceived for Kistefos.", text: 'Our bags are made from organic cotton or recycled fabric, both of which are sustainably sourced and responsibly produced. By choosing to use our bags instead of disposable paper or plastic packaging, you are making a positive impact on the environment and reducing your carbon footprint.',
imageRight: true, imageRight: true,
image: 'https://storage.googleapis.com/planetposen-images/front-kf-2.jpg' // image: 'https://storage.googleapis.com/planetposen-images/front-kf-2.jpg'
image: 'https://storage.googleapis.com/planetposen-images/bags_backyard-upscaled-2.jpeg'
}, },
{ {
title: 'Our goal', title: 'Gift Ideas',
text: 'The scenic sculpture park has an impressive collection of works by internationally renowned contemporary artists including Anish Kapoor, Jeppe Hein, Tony Cragg, Olafur Eliasson, Fernando Bottero and Elmgreen & Dragset. The sculpture park focus is sight specific and international contemporary works of art and is available all year.', text: "Whether you're looking for a birthday present, a wedding gift, or a holiday surprise, our fabric gift bags are the perfect way to add a personal touch to any occasion. Use our bags to wrap a variety of gifts, from jewelry and accessories to small electronics and gadgets. Not sure where to start? Check out our gallery for inspiration!",
image: 'https://storage.googleapis.com/planetposen-images/front-kf-3.jpg' image: 'https://storage.googleapis.com/planetposen-images/front-kf-3.jpg'
}, },
{ {
@@ -32,8 +33,8 @@
image: 'https://storage.googleapis.com/planetposen-images/front-bee-1.jpg' image: 'https://storage.googleapis.com/planetposen-images/front-bee-1.jpg'
}, },
{ {
title: 'Sculpture park of international standing', title: 'Gift Ideas',
text: 'The scenic sculpture park has an impressive collection of works by internationally renowned contemporary artists including Anish Kapoor, Jeppe Hein, Tony Cragg, Olafur Eliasson, Fernando Bottero and Elmgreen & Dragset. The sculpture park focus is sight specific and international contemporary works of art and is available all year.', text: '',
imageRight: false, imageRight: false,
image: 'https://storage.googleapis.com/planetposen-images/front-bee-2.jpg' image: 'https://storage.googleapis.com/planetposen-images/front-bee-2.jpg'
} }
@@ -42,12 +43,12 @@
const textTitle: Array<IFrontText> = [ const textTitle: Array<IFrontText> = [
{ {
title: 'Katy Vandekerckhove:', title: 'Katy Vandekerckhove:',
text: 'Kistefos was really a jewel on earth with high level art in fantastic surroundings', text: 'Give a gift that keeps on giving - our fabric gift bags are reusable, eco-friendly, and stylish.',
color: '#27615d' color: '#27615d'
}, },
{ {
title: 'Katy Vandekerckhove:', title: 'Katy Vandekerckhove:',
text: 'Kistefos was really a jewel on earth with high level art in fantastic surroundings', text: 'Wrap it up in style and sustainability with planetposen fabric gift bags.',
color: 'orange' color: 'orange'
} }
]; ];
@@ -104,7 +105,10 @@
<FrontText data="{textTitle[0]}" /> <FrontText data="{textTitle[0]}" />
<FrontTextImage data="{textImages[3]}" /> <FrontTextImage data="{textImages[3]}" />
<!--
<FrontTextImage data="{textImages[4]}" /> <FrontTextImage data="{textImages[4]}" />
-->
<FrontText data="{textTitle[1]}" /> <FrontText data="{textTitle[1]}" />
</section> </section>

View File

@@ -14,7 +14,7 @@
<footer> <footer>
<section> <section>
<h2>Personvern og vilkår</h2> <h1>Personvern og vilkår</h1>
<ul> <ul>
<li><LinkArrow /><a href="/terms-and-conditions">Betingelser og vilkår</a></li> <li><LinkArrow /><a href="/terms-and-conditions">Betingelser og vilkår</a></li>
<li><LinkArrow /><a href="/privacy-policy">Personvernerklæring</a></li> <li><LinkArrow /><a href="/privacy-policy">Personvernerklæring</a></li>
@@ -31,7 +31,7 @@
</section> </section>
<section> <section>
<h2>Kontakt</h2> <h1>Kontakt</h1>
<ul> <ul>
<li>Epost:&nbsp;<a class="link" href="mailto:post@planetposen.no">post@planetposen.no</a></li> <li>Epost:&nbsp;<a class="link" href="mailto:post@planetposen.no">post@planetposen.no</a></li>
@@ -41,6 +41,7 @@
Kode:&nbsp;<a Kode:&nbsp;<a
class="link" class="link"
target="_blank" target="_blank"
rel="noreferrer"
href="https://github.com/search?q=user%3Akevinmidboe+sort%3Aupdated+planetposen&type=repositories" href="https://github.com/search?q=user%3Akevinmidboe+sort%3Aupdated+planetposen&type=repositories"
>github.com</a >github.com</a
> >
@@ -60,10 +61,6 @@
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
h2 {
font-size: 2.5rem;
}
section { section {
width: 30%; width: 30%;
padding: 2rem; padding: 2rem;
@@ -97,7 +94,7 @@
flex-direction: column; flex-direction: column;
padding: 3rem 0.5rem; padding: 3rem 0.5rem;
h2 { h1 {
font-size: 1.8rem; font-size: 1.8rem;
} }

View File

@@ -0,0 +1 @@
export const GET = () => new Response('ok');

View File

@@ -7,7 +7,7 @@
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 ErrorStack from '$lib/components/ErrorStack.svelte'; import ErrorStack from '$lib/components/ErrorStack.svelte';
import { cart } from '$lib/cartStore'; import { cart, subTotal } from '$lib/cartStore';
import stripeApi from '$lib/stripe/index'; import stripeApi from '$lib/stripe/index';
import { OrderSubmitUnsuccessfullError } from '$lib/errors/OrderErrors'; import { OrderSubmitUnsuccessfullError } from '$lib/errors/OrderErrors';
import Loading from '$lib/components/loading/index.svelte'; import Loading from '$lib/components/loading/index.svelte';
@@ -28,6 +28,7 @@
let card: StripeCardElement; let card: StripeCardElement;
let form: HTMLFormElement; let form: HTMLFormElement;
let errors: string[] = []; let errors: string[] = [];
let showExpressCheckout = false;
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
let resolvePaymentPromise: (value: any) => void; let resolvePaymentPromise: (value: any) => void;
@@ -147,13 +148,15 @@
<form class="checkout" bind:this="{form}" on:submit|preventDefault="{postOrder}"> <form class="checkout" bind:this="{form}" on:submit|preventDefault="{postOrder}">
<div class="main"> <div class="main">
<section class="express-checkout" style="display: block;"> {#if showExpressCheckout}
<h2>Hurtigkasse</h2> <section class="express-checkout" style="display: block;">
<h2>Hurtigkasse</h2>
<ExpressSection /> <ExpressSection />
<p style="margin: 0 0 -0.5rem 0.5rem; text-align: left; color: rgba(0,0,0,0.5);">eller</p> <p style="margin: 0 0 -0.5rem 0.5rem; text-align: left; color: rgba(0,0,0,0.5);">eller</p>
</section> </section>
{/if}
<section id="delivery"> <section id="delivery">
<h2>Leveringsaddresse</h2> <h2>Leveringsaddresse</h2>
@@ -178,7 +181,7 @@
<aside class="sidebar"> <aside class="sidebar">
<section id="order"> <section id="order">
<h2>Din ordre</h2> <h2>Din ordre</h2>
<OrderSection /> <OrderSection lineItems="{$cart}" subTotal="{$subTotal}" />
</section> </section>
</aside> </aside>
</form> </form>

View File

@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import Vipps from '$lib/icons/Vipps.svelte'; import Vipps from '$lib/icons/Vipps.svelte';
import ShopPay from '$lib/icons/ShopPay.svelte';
import ApplePay from '$lib/icons/ApplePay.svelte'; import ApplePay from '$lib/icons/ApplePay.svelte';
import PayPal from '$lib/icons/PayPal.svelte'; import PayPal from '$lib/icons/PayPal.svelte';
import GooglePay from '$lib/icons/GooglePay.svelte'; import GooglePay from '$lib/icons/GooglePay.svelte';

View File

@@ -1,16 +1,9 @@
<script lang="ts"> <script lang="ts">
import OrderTotalSection from './OrderTotalSection.svelte'; import OrderTotalSection from './OrderTotalSection.svelte';
import QuantitySelect from '$lib/components/QuantitySelect.svelte'; import type ICart from '$lib/interfaces/ICart';
import { cart, subTotal } from '$lib/cartStore'; export let lineItems: ICart[];
import { decrementProductInCart, incrementProductInCart } from '$lib/websocketCart'; export let subTotal: number;
const shippingPrice = 75;
$: totalPrice = $subTotal + shippingPrice;
function lineItemClass(id: number) {
return `lineitem-${id}`;
}
</script> </script>
<div class="order-summary"> <div class="order-summary">
@@ -26,7 +19,7 @@
</thead> </thead>
<tbody data-order-summary-section="line-items"> <tbody data-order-summary-section="line-items">
{#each $cart as cartItem} {#each lineItems as lineItem}
<tr <tr
class="product" class="product"
data-product-id="6718367989809" data-product-id="6718367989809"
@@ -40,18 +33,18 @@
<img <img
alt="Black Googly Eye Puff Print Logo Tee - XS" alt="Black Googly Eye Puff Print Logo Tee - XS"
class="product-thumbnail__image" class="product-thumbnail__image"
src="{cartItem.image}" src="{lineItem.image}"
data-src="//cdn.shopify.com/s/files/1/0023/3789/8540/products/20220718_A24_GooglyEye_Tee_Black_15991x1gray_small.jpg?v=1659020903" data-src="//cdn.shopify.com/s/files/1/0023/3789/8540/products/20220718_A24_GooglyEye_Tee_Black_15991x1gray_small.jpg?v=1659020903"
/> />
</div> </div>
<span class="product-thumbnail__quantity" aria-hidden="true">{cartItem.quantity}</span <span class="product-thumbnail__quantity" aria-hidden="true">{lineItem.quantity}</span
> >
</div> </div>
</td> </td>
<th class="product__description" scope="row"> <th class="product__description" scope="row">
<span class="product__description__name order-summary__emphasis">{cartItem.name}</span> <span class="product__description__name order-summary__emphasis">{lineItem.name}</span>
<span class="product__description__variant order-summary__small-text" <span class="product__description__variant order-summary__small-text"
>{cartItem.size}</span >{lineItem.size}</span
> >
</th> </th>
<td class="product__quantity"> <td class="product__quantity">
@@ -59,7 +52,7 @@
</td> </td>
<td class="product__price"> <td class="product__price">
<p class="order-summary__emphasis skeleton-while-loading"> <p class="order-summary__emphasis skeleton-while-loading">
NOK {cartItem.quantity * cartItem.price} NOK {lineItem.quantity * lineItem.price}
</p> </p>
</td> </td>
</tr> </tr>
@@ -68,7 +61,7 @@
</tbody> </tbody>
</table> </table>
<OrderTotalSection /> <OrderTotalSection subTotal="{subTotal}" />
</div> </div>
<style lang="scss" module="scoped"> <style lang="scss" module="scoped">

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { subTotal } from '$lib/cartStore'; export let subTotal: number;
</script> </script>
<div class="total"> <div class="total">
@@ -19,7 +19,7 @@
class="order-summary__emphasis skeleton-while-loading" class="order-summary__emphasis skeleton-while-loading"
data-checkout-subtotal-price-target="4000" data-checkout-subtotal-price-target="4000"
> >
Nok {$subTotal} Nok {subTotal}
</span> </span>
</td> </td>
</tr> </tr>
@@ -58,7 +58,7 @@
<td class="price payment-due" data-presentment-currency="NOK"> <td class="price payment-due" data-presentment-currency="NOK">
<span class="payment-due__currency remove-while-loading">Nok</span> <span class="payment-due__currency remove-while-loading">Nok</span>
<span class="price skeleton-while-loading--lg" data-checkout-payment-due-target="4000"> <span class="price skeleton-while-loading--lg" data-checkout-payment-due-target="4000">
{$subTotal + 75} {subTotal + 75}
</span> </span>
</td> </td>
</tr> </tr>

View File

@@ -62,13 +62,14 @@
<style lang="scss" module="scoped"> <style lang="scss" module="scoped">
@import '../../styles/media-queries.scss'; @import '../../styles/media-queries.scss';
@include desktop { section {
section { max-width: 600px;
margin: 20% 2rem; margin: auto;
width: 60%;
@include desktop {
margin: 20% auto;
} }
} }
.signin-button { .signin-button {
margin-top: 2rem; margin-top: 2rem;
} }

View File

@@ -6,7 +6,7 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
const { id } = params; const { id } = params;
const res = await fetch(`/api/v1/order/${id}`); const res = await fetch(`/api/v1/order/${id}`);
const orderResponse = await res.json(); const orderResponse: IOrderDTO = await res.json();
if (orderResponse?.success == false || orderResponse?.order === undefined) { if (orderResponse?.success == false || orderResponse?.order === undefined) {
console.log('throwing error', orderResponse); console.log('throwing error', orderResponse);

View File

@@ -1,5 +1,5 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types'; import type { Actions } from './$types';
export const actions: Actions = { export const actions: Actions = {
default: async ({ request }) => { default: async ({ request }) => {
@@ -10,8 +10,6 @@ export const actions: Actions = {
const receiptUrl = `/receipt/${orderId}?email=${email}`; const receiptUrl = `/receipt/${orderId}?email=${email}`;
throw redirect(303, receiptUrl); throw redirect(303, receiptUrl);
return { success: false };
} }
}; };

View File

@@ -2,17 +2,11 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import CircleCheckmark from '$lib/components/loading/CircleCheckmark.svelte'; import CircleCheckmark from '$lib/components/loading/CircleCheckmark.svelte';
import CircleError from '$lib/components/loading/CircleError.svelte'; import CircleError from '$lib/components/loading/CircleError.svelte';
import OrderSection from '../../checkout/OrderSection.svelte';
import type { PageServerData } from './$types'; import type { IOrder } from '$lib/interfaces/IOrder';
import type { ILineItem, IOrder } from '$lib/interfaces/IOrder';
import CircleWarning from '$lib/components/loading/CircleWarning.svelte'; import CircleWarning from '$lib/components/loading/CircleWarning.svelte';
function subTotal(lineItems: Array<ILineItem> = []) {
let total = 0;
lineItems.forEach((lineItem) => (total = total + lineItem.price * lineItem.quantity));
return total;
}
let id: string; let id: string;
let email: string; let email: string;
let order: IOrder; let order: IOrder;
@@ -23,6 +17,8 @@
email = data.email || (data?.order?.customer?.email as string); email = data.email || (data?.order?.customer?.email as string);
order = data.order as IOrder; order = data.order as IOrder;
} }
$: subTotal = Math.round((order?.payment?.amount || 1) / 100);
</script> </script>
<section class="order-confirmation"> <section class="order-confirmation">
@@ -49,21 +45,9 @@
</div> </div>
<div class="order-receipt"> <div class="order-receipt">
{#each order?.lineItems as lineItem} <div class="receipt-box">
<p> <OrderSection lineItems="{order?.lineItems}" subTotal="{subTotal}" } />
<code>{lineItem.name} x{lineItem.quantity}</code> </div>
<code>NOK {lineItem.price * lineItem.quantity}</code>
</p>
{/each}
<p>
<code>Shipping</code>
<code>NOK 75</code>
</p>
<p>
<code>Total</code>
<code>NOK {subTotal(order?.lineItems)}</code>
</p>
</div> </div>
</section> </section>
@@ -75,30 +59,52 @@
} }
.order-receipt { .order-receipt {
background-color: #f7f7f7; --receipt_color: #f7f7f7;
max-width: 500px; --tearOffHeight: 8px;
background-color: var(--receipt_color);
max-width: 800px;
width: calc(100% - 4rem); width: calc(100% - 4rem);
padding: 2rem;
font-family: monospace; font-family: monospace;
position: relative;
p { /* Paper background effect */
margin: 0.8rem 0; .receipt-box {
display: flex; height: auto;
justify-content: space-between; overflow: hidden;
border-bottom: 1px solid lightgrey; padding: 1rem;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.05);
&:last-of-type { &::after {
padding-top: 1.5rem; content: '';
border-width: 2px; height: var(--tearOffHeight);
} position: absolute;
} left: 0;
right: 0;
code { bottom: calc(var(--tearOffHeight) * -1);
opacity: 0.4; background-color: var(--receipt_color);
font-size: 1rem; clip-path: polygon(
0% 0%,
&:first-of-type { 5% 100%,
font-weight: 600; 10% 0%,
15% 100%,
20% 0%,
25% 100%,
30% 0%,
35% 100%,
40% 0%,
45% 100%,
50% 0%,
55% 100%,
60% 0%,
65% 100%,
70% 0%,
75% 100%,
80% 0%,
85% 100%,
90% 0%,
95% 100%,
100% 0%
);
} }
} }
} }

View File

@@ -4,7 +4,6 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import CircleLoading from '$lib/components/loading/CircleLoading.svelte'; import CircleLoading from '$lib/components/loading/CircleLoading.svelte';
import { buildApiUrl } from '$lib/utils/apiUrl'; import { buildApiUrl } from '$lib/utils/apiUrl';
import type { PageServerData } from './$types';
const { data } = $page; const { data } = $page;
const id = data?.id as string; const id = data?.id as string;
@@ -22,7 +21,7 @@
goto(url); goto(url);
} }
function checkOrder() { async function checkOrder() {
const url = buildApiUrl(`/api/v1/order/${id}`); const url = buildApiUrl(`/api/v1/order/${id}`);
return fetch(url) return fetch(url)
.then((resp) => resp.json()) .then((resp) => resp.json())

View File

@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import ProductTile from '$lib/components/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';
@@ -98,7 +97,7 @@
.details { .details {
.name { .name {
font-size: 2rem; font-size: 2em;
} }
.description { .description {

View File

@@ -14,7 +14,7 @@
color: var(--color-text); color: var(--color-text);
} }
html { body .app main {
font-size: 100%; font-size: 100%;
} }
@@ -56,11 +56,11 @@ a.link {
} }
h1 { h1 {
font-size: 2rem; font-size: 2em;
} }
h2 { h2 {
font-size: 1rem; font-size: 1em;
} }
.no-scroll { .no-scroll {
@@ -104,12 +104,12 @@ button:focus:not(:focus-visible) {
} }
@media screen and (min-width: 1500px) { @media screen and (min-width: 1500px) {
html { body .app main {
font-size: 110%; font-size: 110%;
} }
} }
@media screen and (min-width: 2000px) { @media screen and (min-width: 2000px) {
html { body .app main {
font-size: 130%; font-size: 125%;
} }
} }

View File

@@ -1,3 +1,4 @@
import adapter from '@sveltejs/adapter-node';
import preprocess from 'svelte-preprocess'; import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
@@ -7,6 +8,7 @@ const config = {
preprocess: preprocess(), preprocess: preprocess(),
kit: { kit: {
adapter: adapter(),
csrf: { csrf: {
checkOrigin: false checkOrigin: false
} }