[wip] 🔥 Integrate Swell into the shop
Create a custom and internal API for fetching and updating content to Swell Admin API (using swell-node)
This commit is contained in:
@@ -16,6 +16,10 @@ VITE_API_URL_PROD="https://api.housesof.world"
|
|||||||
VITE_API_GRAPHQL_PATH="/graphql"
|
VITE_API_GRAPHQL_PATH="/graphql"
|
||||||
VITE_API_TOKEN="efa40490-152c-49d7-a75b-30a6427439b1"
|
VITE_API_TOKEN="efa40490-152c-49d7-a75b-30a6427439b1"
|
||||||
|
|
||||||
|
# Shop
|
||||||
|
VITE_SWELL_STORE_ID="houses-of"
|
||||||
|
VITE_SWELL_API_TOKEN="v3BiXcZP5jpmhL80i4eUy6iXxcpN9cIq"
|
||||||
|
|
||||||
# Analytics
|
# Analytics
|
||||||
VITE_ANALYTICS_KEY="c01e378821e6ba7bf9a9f947b107500bfcbd4ae8"
|
VITE_ANALYTICS_KEY="c01e378821e6ba7bf9a9f947b107500bfcbd4ae8"
|
||||||
VITE_ANALYTICS_URL="https://stats.flayks.com"
|
VITE_ANALYTICS_URL="https://stats.flayks.com"
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"svelte": "^3.44.1",
|
"svelte": "^3.44.1",
|
||||||
"svelte-check": "^2.2.8",
|
"svelte-check": "^2.2.8",
|
||||||
"svelte-preprocess": "^4.9.8",
|
"svelte-preprocess": "^4.9.8",
|
||||||
|
"swell-node": "^4.0.6",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.4.4"
|
"typescript": "^4.4.4"
|
||||||
},
|
},
|
||||||
|
|||||||
7
pnpm-lock.yaml
generated
7
pnpm-lock.yaml
generated
@@ -16,6 +16,7 @@ specifiers:
|
|||||||
svelte: ^3.44.1
|
svelte: ^3.44.1
|
||||||
svelte-check: ^2.2.8
|
svelte-check: ^2.2.8
|
||||||
svelte-preprocess: ^4.9.8
|
svelte-preprocess: ^4.9.8
|
||||||
|
swell-node: ^4.0.6
|
||||||
tslib: ^2.3.1
|
tslib: ^2.3.1
|
||||||
typescript: ^4.4.4
|
typescript: ^4.4.4
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ devDependencies:
|
|||||||
svelte: 3.44.1
|
svelte: 3.44.1
|
||||||
svelte-check: 2.2.8_sass@1.43.4+svelte@3.44.1
|
svelte-check: 2.2.8_sass@1.43.4+svelte@3.44.1
|
||||||
svelte-preprocess: 4.9.8_6627cbae993b0086cf4555994e082905
|
svelte-preprocess: 4.9.8_6627cbae993b0086cf4555994e082905
|
||||||
|
swell-node: 4.0.6
|
||||||
tslib: 2.3.1
|
tslib: 2.3.1
|
||||||
typescript: 4.4.4
|
typescript: 4.4.4
|
||||||
|
|
||||||
@@ -1437,6 +1439,11 @@ packages:
|
|||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/swell-node/4.0.6:
|
||||||
|
resolution: {integrity: sha512-9eAjxse63TL2J3R7RdyD3VoykSffkY/z4jpIpwqjGUhbJYhpqXwbAive2U+6dvdqxGdjovBM8siTeld2Ud9LVw==}
|
||||||
|
engines: {node: '>= v12.21.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/text-table/0.2.0:
|
/text-table/0.2.0:
|
||||||
resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}
|
resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}
|
||||||
dev: true
|
dev: true
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import Image from '$components/atoms/Image.svelte'
|
import Image from '$components/atoms/Image.svelte'
|
||||||
import Carousel from '$components/organisms/Carousel.svelte'
|
import Carousel from '$components/organisms/Carousel.svelte'
|
||||||
|
import { cartData, cartId } from '$utils/store';
|
||||||
|
|
||||||
export let product: any
|
export let product: any
|
||||||
|
export let productShop: any
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +53,33 @@
|
|||||||
ratio: 0.68,
|
ratio: 0.68,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling add to cart
|
||||||
|
*/
|
||||||
|
const addToCart = async () => {
|
||||||
|
// const addedReturn = await swell.cart.addItem({
|
||||||
|
// product_id: product.product_id,
|
||||||
|
// quantity: 1,
|
||||||
|
// })
|
||||||
|
|
||||||
|
const addedReturn = await fetch('/api/swell', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'addToCart',
|
||||||
|
cartId: $cartId,
|
||||||
|
productId: product.product_id,
|
||||||
|
quantity: 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (addedReturn.ok) {
|
||||||
|
const newCart = await addedReturn.json()
|
||||||
|
$cartData = newCart
|
||||||
|
console.log('Show mini product added to cart')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="poster-layout grid">
|
<section class="poster-layout grid">
|
||||||
@@ -62,11 +91,12 @@
|
|||||||
<div class="poster-layout__info">
|
<div class="poster-layout__info">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>{capitalizeFirstLetter(product.type)}</dt>
|
<dt>{capitalizeFirstLetter(product.type)}</dt>
|
||||||
<dd>{product.name} – 30€</dd>
|
<dd>{productShop.name} – {productShop.price}€</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<Button
|
<Button
|
||||||
text="Add to cart"
|
text="Add to cart"
|
||||||
color="pinklight"
|
color="pinklight"
|
||||||
|
on:click={addToCart}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
49
src/components/molecules/CartItem.svelte
Normal file
49
src/components/molecules/CartItem.svelte
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
export let item: any
|
||||||
|
|
||||||
|
// console.log(item)
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
// When changing item quantity
|
||||||
|
const updateQuantity = ({ target: { value }}: any) => {
|
||||||
|
dispatch('updatedQuantity', {
|
||||||
|
id: item.id,
|
||||||
|
quantity: Number(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// When removing item
|
||||||
|
const removeItem = () => {
|
||||||
|
dispatch('removed', item.id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<aside class="cart-item shadow-small">
|
||||||
|
<div class="cart-item__left">
|
||||||
|
<img
|
||||||
|
src={item.product.images[0].file.url}
|
||||||
|
width={200}
|
||||||
|
height={300}
|
||||||
|
alt={item.product.name}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="cart-item__right">
|
||||||
|
<h3>Poster</h3>
|
||||||
|
<p>{item.product.name} – {item.price_total}€</p>
|
||||||
|
|
||||||
|
{#if item && item.product}
|
||||||
|
<div class="select">
|
||||||
|
<select on:change={updateQuantity}>
|
||||||
|
{#each Array(5) as _, index}
|
||||||
|
<option value={index + 1} selected={item.quantity - 1 === index}>{index + 1}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button on:click={removeItem}>Remove</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getContext } from 'svelte'
|
|
||||||
// Components
|
|
||||||
import Button from '$components/atoms/Button.svelte'
|
|
||||||
import Image from '$components/atoms/Image.svelte'
|
|
||||||
import Select from './Select.svelte'
|
|
||||||
|
|
||||||
const { locations, shop } = getContext('global')
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<aside class="poster-cart">
|
|
||||||
<div class="poster-cart__left">
|
|
||||||
<img src="/images/issue-1.jpg" alt="">
|
|
||||||
</div>
|
|
||||||
<div class="poster-cart__right">
|
|
||||||
<h3>Poster</h3>
|
|
||||||
<p>Houses Of Melbourne – 30€</p>
|
|
||||||
<Select
|
|
||||||
name="quantity" id="filter_country"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
value: '1',
|
|
||||||
name: '1',
|
|
||||||
default: true,
|
|
||||||
selected: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
@@ -1,19 +1,96 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte'
|
||||||
import { fade, fly } from 'svelte/transition'
|
import { fade, fly } from 'svelte/transition'
|
||||||
import { cartOpen } from '$utils/store'
|
|
||||||
import { quartOut } from 'svelte/easing'
|
import { quartOut } from 'svelte/easing'
|
||||||
|
import { cartOpen, cartId, cartData, cartAmount } from '$utils/store'
|
||||||
// Components
|
// Components
|
||||||
import Button from '$components/atoms/Button.svelte'
|
import Button from '$components/atoms/Button.svelte'
|
||||||
import PosterCart from '$components/molecules/PosterCart.svelte'
|
import CartItem from '$components/molecules/CartItem.svelte'
|
||||||
|
|
||||||
|
let open = false
|
||||||
|
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// Cart already exists
|
||||||
|
if ($cartId && $cartId !== 'null') {
|
||||||
|
// Fetch stored cart
|
||||||
|
const existantCart = await fetch('/api/swell', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'fetchCart',
|
||||||
|
cartId: $cartId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (existantCart.ok) {
|
||||||
|
const cart = await existantCart.json()
|
||||||
|
$cartId = cart.id
|
||||||
|
$cartData = cart
|
||||||
|
console.log('fetched existant cart:', $cartId, $cartData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cart doesn't exists
|
||||||
|
else {
|
||||||
|
// Create a new cart and store it
|
||||||
|
const newCart = await fetch('/api/swell', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'createCart'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (newCart.ok) {
|
||||||
|
const cart = await newCart.json()
|
||||||
|
$cartId = cart.id
|
||||||
|
$cartData = cart
|
||||||
|
console.log('new cart:', localStorage.getItem('cartId'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// Closing the cart
|
// Closing the cart
|
||||||
const handleCloseCart = () => {
|
const handleCloseCart = () => {
|
||||||
$cartOpen = false
|
$cartOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Item quantity changed
|
||||||
|
const changedQuantity = async ({ detail: { id, quantity } }) => {
|
||||||
|
// Update item in cart
|
||||||
|
// const updatedCart = await swell.cart.updateItem(id, {
|
||||||
|
// quantity
|
||||||
|
// })
|
||||||
|
// if (updatedCart) {
|
||||||
|
// $cartData = updatedCart
|
||||||
|
// }
|
||||||
|
|
||||||
|
const updatedCart = await fetch('/api/swell', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'updateCartItem',
|
||||||
|
cartId: $cartId,
|
||||||
|
productId: id,
|
||||||
|
quantity: quantity
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if (updatedCart.ok) {
|
||||||
|
// const cart = await updatedCart.json()
|
||||||
|
// $cartData = cart
|
||||||
|
// console.log('updated cart:', $cartData.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item removed
|
||||||
|
const removedItem = async ({ detail: id }) => {
|
||||||
|
// Remove item from cart
|
||||||
|
// const removedItem = await swell.cart.removeItem(id)
|
||||||
|
// if (removedItem) {
|
||||||
|
// $cartData = removedItem
|
||||||
|
// }
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<aside class="cart"
|
{#if $cartOpen}
|
||||||
|
<aside class="cart shadow-box-dark"
|
||||||
transition:fly={{ x: 48, duration: 600, easing: quartOut }}
|
transition:fly={{ x: 48, duration: 600, easing: quartOut }}
|
||||||
>
|
>
|
||||||
<header class="cart__heading">
|
<header class="cart__heading">
|
||||||
@@ -22,24 +99,48 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="cart__content">
|
<div class="cart__content">
|
||||||
<PosterCart />
|
{#if $cartAmount > 0}
|
||||||
<PosterCart />
|
{#each $cartData.items as item}
|
||||||
|
<CartItem {item}
|
||||||
|
on:updatedQuantity={changedQuantity}
|
||||||
|
on:removed={removedItem}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<div class="cart__message shadow-small">
|
||||||
|
<div class="icon">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p>Your cart is empty</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="cart__total">
|
<footer class="cart__total">
|
||||||
<div class="cart__total--sum">
|
<div class="cart__total--sum">
|
||||||
<h3>Total</h3>
|
<h3>Total</h3>
|
||||||
<span>3 articles</span>
|
{#if $cartData}
|
||||||
<p>90€</p>
|
<span>{$cartAmount} item{$cartAmount > 1 ? 's' : ''}</span>
|
||||||
|
<p>{$cartData.sub_total ? $cartData.sub_total : 0}€</p>
|
||||||
|
{:else}
|
||||||
|
<span>0 item</span>
|
||||||
|
<p>0€</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="cart__total--checkout">
|
<div class="cart__total--checkout">
|
||||||
<p>Shipping will be calculated from the delivery address during the checkout process</p>
|
<p>Shipping will be calculated from the delivery address during the checkout process</p>
|
||||||
|
{#if $cartData && $cartAmount > 0 && $cartData.checkout_url}
|
||||||
|
<div transition:fly={{ y: 8, duration: 600, easing: quartOut }}>
|
||||||
<Button
|
<Button
|
||||||
|
url={$cartData && $cartData.checkout_url}
|
||||||
text="Checkout"
|
text="Checkout"
|
||||||
color="pink"
|
color="pink"
|
||||||
size="small"
|
size="small"
|
||||||
|
disabled={!$cartData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -47,3 +148,4 @@
|
|||||||
transition:fade={{ duration: 600, easing: quartOut }}
|
transition:fade={{ duration: 600, easing: quartOut }}
|
||||||
on:click={handleCloseCart}
|
on:click={handleCloseCart}
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
60
src/routes/api/swell.ts
Normal file
60
src/routes/api/swell.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { addToCart, createCart, fetchCart, getProduct, updateCartItem } from '$utils/swellFunctions'
|
||||||
|
|
||||||
|
|
||||||
|
// Block GET requests
|
||||||
|
export async function get ({ body, query }) {
|
||||||
|
return {
|
||||||
|
status: 403,
|
||||||
|
body: 'nope!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST request
|
||||||
|
*/
|
||||||
|
export async function post ({ headers, query, body, params, ...rest }) {
|
||||||
|
try {
|
||||||
|
const bodyParsed = JSON.parse(Buffer.from(body).toString())
|
||||||
|
const { action, cartId, productId } = bodyParsed
|
||||||
|
let result = {}
|
||||||
|
|
||||||
|
if (bodyParsed) {
|
||||||
|
switch (action) {
|
||||||
|
case 'getProduct': {
|
||||||
|
result = await getProduct(productId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'createCart': {
|
||||||
|
result = await createCart()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'fetchCart': {
|
||||||
|
result = await fetchCart(cartId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'addToCart': {
|
||||||
|
result = await addToCart(cartId, productId, bodyParsed.quantity)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'updateCartItem': {
|
||||||
|
result = await updateCartItem(cartId, productId, bodyParsed.quantity)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
return {
|
||||||
|
status: error.status || 500,
|
||||||
|
body: error.message || error.text || `Can't fetch query`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { cartOpen } from '$utils/store'
|
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
// Components
|
// Components
|
||||||
import Metas from '$components/Metas.svelte'
|
import Metas from '$components/Metas.svelte'
|
||||||
@@ -15,6 +14,7 @@
|
|||||||
export let locations: any
|
export let locations: any
|
||||||
export let posters: any
|
export let posters: any
|
||||||
export let product: any
|
export let product: any
|
||||||
|
export let productShop: any
|
||||||
|
|
||||||
let navEl: HTMLElement, introEl: HTMLElement
|
let navEl: HTMLElement, introEl: HTMLElement
|
||||||
let navObserver: IntersectionObserver
|
let navObserver: IntersectionObserver
|
||||||
@@ -47,10 +47,8 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<main class="shop-page">
|
<main class="shop-page">
|
||||||
{#if $cartOpen}
|
|
||||||
<Cart />
|
<Cart />
|
||||||
{/if}
|
|
||||||
<section class="shop-page__intro">
|
|
||||||
<section class="shop-page__intro" bind:this={introEl}>
|
<section class="shop-page__intro" bind:this={introEl}>
|
||||||
<a href="/" class="back">
|
<a href="/" class="back">
|
||||||
Back to Houses Of
|
Back to Houses Of
|
||||||
@@ -110,7 +108,10 @@
|
|||||||
<p class="description text-normal">{shop.about}</p>
|
<p class="description text-normal">{shop.about}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<PosterLayout {product} />
|
<PosterLayout
|
||||||
|
product={product}
|
||||||
|
productShop={productShop}
|
||||||
|
/>
|
||||||
|
|
||||||
<section class="shop-page__posters grid">
|
<section class="shop-page__posters grid">
|
||||||
<h3>View all of our available posters</h3>
|
<h3>View all of our available posters</h3>
|
||||||
@@ -137,6 +138,10 @@
|
|||||||
import { getRandomElement } from '$utils/functions'
|
import { getRandomElement } from '$utils/functions'
|
||||||
|
|
||||||
export async function load ({ page, fetch, session, stuff }) {
|
export async function load ({ page, fetch, session, stuff }) {
|
||||||
|
// Init Swell
|
||||||
|
// swell.init(import.meta.env.VITE_SWELL_STORE_ID, import.meta.env.VITE_SWELL_API_TOKEN)
|
||||||
|
|
||||||
|
// Get content from API
|
||||||
const res = await fetchAPI(`
|
const res = await fetchAPI(`
|
||||||
query {
|
query {
|
||||||
shop {
|
shop {
|
||||||
@@ -161,6 +166,7 @@
|
|||||||
name
|
name
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
|
product_id
|
||||||
photos_product {
|
photos_product {
|
||||||
directus_files_id {
|
directus_files_id {
|
||||||
id
|
id
|
||||||
@@ -182,22 +188,36 @@
|
|||||||
/**
|
/**
|
||||||
* Define product
|
* Define product
|
||||||
*/
|
*/
|
||||||
let product: any
|
const productAPI = (!page.params.type && !page.params.name)
|
||||||
|
|
||||||
if (!page.params.type && !page.params.name) {
|
|
||||||
// Get a random product
|
// Get a random product
|
||||||
product = data.posters[getRandomElement(data.posters)]
|
? data.posters[getRandomElement(data.posters)]
|
||||||
} else {
|
|
||||||
// Get the current product from slug
|
// Get the current product from slug
|
||||||
product = data.posters.find(({ location }: any) => location.slug === page.params.name)
|
: data.posters.find(({ location }: any) => location.slug === page.params.name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product data from Swell
|
||||||
|
*/
|
||||||
|
let productShopRes: any
|
||||||
|
|
||||||
|
const productShop = await fetch('/api/swell', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'getProduct',
|
||||||
|
productId: productAPI.product_id,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (productShop) {
|
||||||
|
productShopRes = await productShop.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
shop: data.shop,
|
shop: data.shop,
|
||||||
locations: data.location,
|
locations: data.location,
|
||||||
posters: data.posters,
|
posters: data.posters,
|
||||||
product,
|
product: productAPI,
|
||||||
|
productShop: productShopRes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
src/style/molecules/_cart-item.scss
Normal file
58
src/style/molecules/_cart-item.scss
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Cart item
|
||||||
|
.cart-item {
|
||||||
|
display: flex;
|
||||||
|
background-color: #fff;
|
||||||
|
color: $color-gray;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left Image
|
||||||
|
&__left {
|
||||||
|
margin-right: 20px;
|
||||||
|
width: 100px;
|
||||||
|
height: 150px;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
margin-right: 32px;
|
||||||
|
width: 124px;
|
||||||
|
height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px 0 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right {
|
||||||
|
// Poster Title
|
||||||
|
h3 {
|
||||||
|
font-family: $font-serif;
|
||||||
|
color: $color-secondary;
|
||||||
|
font-size: rem(20px);
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
font-size: rem(28px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Text
|
||||||
|
p {
|
||||||
|
font-size: rem(12px);
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 124px;
|
||||||
|
margin: 8px 0 20px;
|
||||||
|
|
||||||
|
@include bp (sm) {
|
||||||
|
font-size: rem(13px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,90 +33,60 @@
|
|||||||
font-size: rem(48px);
|
font-size: rem(48px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Close
|
// Close button
|
||||||
a {
|
button {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-right: -12px;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
transition: color 0.4s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $color-secondary-bright;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poster Cart
|
// Content
|
||||||
.poster-cart {
|
&__content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message
|
||||||
|
&__message {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #fff;
|
|
||||||
color: $color-gray;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
border-radius: 6px;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left Image
|
|
||||||
&__left {
|
|
||||||
margin-right: 20px;
|
|
||||||
width: 100px;
|
|
||||||
height: 150px;
|
|
||||||
|
|
||||||
@include bp (sm) {
|
|
||||||
margin-right: 32px;
|
|
||||||
width: 124px;
|
|
||||||
height: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
padding: 24px;
|
||||||
border-radius: 6px 0 0 6px;
|
background: #fff;
|
||||||
}
|
color: $color-gray;
|
||||||
}
|
border-radius: 6px;
|
||||||
|
|
||||||
&__right {
|
|
||||||
// Poster Title
|
|
||||||
h3 {
|
|
||||||
font-family: $font-serif;
|
|
||||||
color: $color-secondary;
|
|
||||||
font-size: rem(20px);
|
|
||||||
|
|
||||||
@include bp (sm) {
|
|
||||||
font-size: rem(28px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Text
|
|
||||||
p {
|
|
||||||
font-size: rem(12px);
|
|
||||||
line-height: 1.4;
|
|
||||||
max-width: 124px;
|
|
||||||
margin: 8px 0 20px;
|
|
||||||
|
|
||||||
@include bp (sm) {
|
|
||||||
font-size: rem(13px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
&__total {
|
&__total {
|
||||||
color: $color-gray;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding-top: 20px;
|
margin: 24px 0;
|
||||||
|
color: $color-gray;
|
||||||
|
|
||||||
|
@include bp (md) {
|
||||||
|
margin: 32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Sum
|
// Sum
|
||||||
&--sum {
|
&--sum {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
margin-bottom: 16px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
margin-bottom: 17px;
|
|
||||||
border-bottom: 1px solid #E1D0C0;
|
border-bottom: 1px solid #E1D0C0;
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
padding-bottom: 12px;
|
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@@ -129,18 +99,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
font-size: rem(12px);
|
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
|
font-size: rem(12px);
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
font-size: rem(13px);
|
font-size: rem(13px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
|
margin-left: auto;
|
||||||
color: $color-secondary;
|
color: $color-secondary;
|
||||||
font-family: $font-serif;
|
font-family: $font-serif;
|
||||||
font-size: rem(26px);
|
font-size: rem(26px);
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
font-size: rem(32px);
|
font-size: rem(32px);
|
||||||
@@ -153,16 +123,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
max-width: 180px;
|
||||||
|
margin-right: auto;
|
||||||
font-size: rem(11px);
|
font-size: rem(11px);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
max-width: 180px;
|
|
||||||
margin-right: auto;
|
|
||||||
|
|
||||||
@include bp (sm) {
|
@include bp (sm) {
|
||||||
|
max-width: 304px;
|
||||||
font-size: rem(12px);
|
font-size: rem(12px);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
max-width: 190px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
@import "molecules/issue";
|
@import "molecules/issue";
|
||||||
@import "molecules/newsletter-form";
|
@import "molecules/newsletter-form";
|
||||||
@import "molecules/poster";
|
@import "molecules/poster";
|
||||||
|
@import "molecules/cart-item";
|
||||||
|
|
||||||
// Organisms
|
// Organisms
|
||||||
@import "organisms/locations";
|
@import "organisms/locations";
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
import { writable } from 'svelte/store'
|
import { writable, derived } from 'svelte/store'
|
||||||
|
|
||||||
// Shop
|
|
||||||
export const cartOpen = writable(false)
|
/**
|
||||||
export const cartAmount = writable(3)
|
* Shop
|
||||||
|
*/
|
||||||
|
/** Open Cart state */
|
||||||
|
export const cartOpen = writable(true)
|
||||||
|
|
||||||
|
/** Current Cart ID */
|
||||||
|
export const cartId = writable(null)
|
||||||
|
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
if (localStorage.getItem('cartId')) {
|
||||||
|
console.log('existant', localStorage.getItem('cartId'))
|
||||||
|
cartId.set(localStorage.getItem('cartId'))
|
||||||
|
}
|
||||||
|
cartId.subscribe(value => localStorage.setItem('cartId', value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Raw Cart data */
|
||||||
|
export const cartData = writable(null)
|
||||||
|
|
||||||
|
/** Amount of products present in cart */
|
||||||
|
export const cartAmount = derived(cartData, ($cart) => {
|
||||||
|
return $cart && $cart.item_quantity > 0 ? $cart.item_quantity : 0
|
||||||
|
})
|
||||||
102
src/utils/swellFunctions.ts
Normal file
102
src/utils/swellFunctions.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import swell from 'swell-node'
|
||||||
|
|
||||||
|
// Init Swell
|
||||||
|
swell.init(import.meta.env.VITE_SWELL_STORE_ID, import.meta.env.VITE_SWELL_API_TOKEN)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a product
|
||||||
|
*/
|
||||||
|
export const getProduct = async (id: string) => {
|
||||||
|
const product = await swell.get(`/products/${id}`)
|
||||||
|
|
||||||
|
if (product) {
|
||||||
|
return product
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a cart
|
||||||
|
*/
|
||||||
|
export const createCart = async () => {
|
||||||
|
const cart = await swell.post('/carts', {})
|
||||||
|
|
||||||
|
if (cart) {
|
||||||
|
return cart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve cart
|
||||||
|
*/
|
||||||
|
export const fetchCart = async (cartId: string) => {
|
||||||
|
const cart = await swell.get('/carts/{id}', {
|
||||||
|
id: cartId,
|
||||||
|
expand: [
|
||||||
|
'items.product',
|
||||||
|
'items.variant',
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (cart) {
|
||||||
|
return cart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add product to cart
|
||||||
|
*/
|
||||||
|
export const addToCart = async (cartId: string, productId: string, quantity: number) => {
|
||||||
|
// TODO: Update current product quantity if adding again, otherwise add new product to existing items
|
||||||
|
const updatedCart = await swell.put('/carts/{id}', {
|
||||||
|
id: cartId,
|
||||||
|
items: [{
|
||||||
|
product_id: productId,
|
||||||
|
quantity: quantity,
|
||||||
|
expand: [
|
||||||
|
'items.product',
|
||||||
|
'items.variant',
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (updatedCart) {
|
||||||
|
return updatedCart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update cart item
|
||||||
|
*/
|
||||||
|
export const updateCartItem = async (cartId: string, productId: string, quantity: number) => {
|
||||||
|
// Fetch current cart data
|
||||||
|
const currentCart = await fetchCart(cartId)
|
||||||
|
|
||||||
|
// Update cart
|
||||||
|
// const itemToUpdate = currentCart.items.find((item: any) => item.id === productId)
|
||||||
|
// itemToUpdate.quantity = quantity
|
||||||
|
|
||||||
|
// Updated items with replacing new item quantity
|
||||||
|
const updatedCartItems = currentCart.items.map((item: any) => {
|
||||||
|
console.log(item)
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
// const updatedCart = await swell.put('/carts/{id}', {
|
||||||
|
// id: cartId,
|
||||||
|
// items: updatedItems,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// console.log(updatedCart)
|
||||||
|
|
||||||
|
return currentCart
|
||||||
|
// if (updatedCart) {
|
||||||
|
// return {}
|
||||||
|
// // return updatedCart
|
||||||
|
// }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user