All sveltekit library files

This commit is contained in:
2022-11-28 20:02:55 +01:00
parent 502efef964
commit c76732e6e7
36 changed files with 1742 additions and 0 deletions

21
src/lib/cartStore.ts Normal file
View File

@@ -0,0 +1,21 @@
import { writable, get, derived } from 'svelte/store';
import type { Writable, Readable } from 'svelte/store';
export const cart: Writable<any> = writable([]);
export const isOpen: Writable<boolean> = writable(false);
export const count: Readable<number> = derived(cart, ($cart) => $cart.length || 0);
export const subTotal: Readable<number> = derived(cart, ($cart) => {
let total = 0;
$cart.forEach((cartItem) => (total += cartItem.price * cartItem.quantity));
return total;
});
export function toggleCart() {
isOpen.update((state) => !state);
}
export function closeCart() {
isOpen.set(false);
}

View File

@@ -0,0 +1,188 @@
<script lang="ts">
function getApplePaySession(validationURL: string) {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ validationURL })
};
return fetch('/api/applepay/validateSession', options).then((resp) => resp.json());
}
function makeApplePayPaymentTransaction(payment: object) {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ payment })
};
return fetch('/api/applepay/pay', options).then((resp) => resp.json());
}
function createPaymentRequest() {
const paymentRequest = {
countryCode: 'NO',
currencyCode: 'NOK',
shippingMethods: [
{
label: 'Free Shipping',
amount: 0.0,
identifier: 'free',
detail: 'Delivers in five business days'
},
{
label: 'Express Shipping',
amount: 5.0,
identifier: 'express',
detail: 'Delivers in two business days'
}
],
lineItems: [
{
label: 'Shipping',
amount: 0.0
}
],
total: {
label: 'Apple Pay Example',
amount: 8.99
},
supportedNetworks: ['amex', 'discover', 'masterCard', 'visa'],
merchantCapabilities: ['supports3DS'],
requiredShippingContactFields: ['postalAddress', 'email']
};
const session = new ApplePaySession(6, paymentRequest);
session.onvalidatemerchant = (event) => {
console.log('Validate merchante');
console.log('event: ', event);
const validationURL = event.validationURL;
this.getApplePaySession(validationURL).then((response) => {
console.log('response from getApplePaySession:', response);
session.completeMerchantValidation(response);
});
};
session.onpaymentauthorized = (event) => {
this.makeApplePayPaymentTransaction(event.payment).then((response) => {
console.log('response from pay:', response);
if (response.approved) session.completePayment(ApplePaySession.STATUS_SUCCESS);
else session.completePayment(ApplePaySession.STATUS_FAILURE);
});
};
session.begin();
}
</script>
<div
class="apple-pay-button-with-text apple-pay-button-black-with-text"
on:click="{createPaymentRequest}"
>
<span class="text">Hurtigkasse med</span>
<svg
x="116.59"
y="7.92"
height="15"
width="35"
class="logo"
viewBox="0 0 105 43"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<title>Apple Logo</title>
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g fill="#FFF">
<path
d="M19.4028,5.5674 C20.6008,4.0684 21.4138,2.0564 21.1998,0.0004 C19.4458,0.0874 17.3058,1.1574 16.0668,2.6564 C14.9538,3.9414 13.9688,6.0374 14.2258,8.0074 C16.1948,8.1784 18.1618,7.0244 19.4028,5.5674"
></path>
<path
d="M21.1772,8.3926 C18.3182,8.2226 15.8872,10.0156 14.5212,10.0156 C13.1552,10.0156 11.0642,8.4786 8.8022,8.5196 C5.8592,8.5626 3.1282,10.2276 1.6342,12.8746 C-1.4378,18.1696 0.8232,26.0246 3.8112,30.3376 C5.2622,32.4716 7.0102,34.8206 9.3142,34.7366 C11.4912,34.6506 12.3442,33.3266 14.9902,33.3266 C17.6352,33.3266 18.4042,34.7366 20.7082,34.6936 C23.0972,34.6506 24.5922,32.5586 26.0422,30.4226 C27.7072,27.9906 28.3882,25.6426 28.4312,25.5126 C28.3882,25.4706 23.8232,23.7186 23.7812,18.4676 C23.7382,14.0706 27.3652,11.9786 27.5362,11.8496 C25.4882,8.8196 22.2872,8.4786 21.1772,8.3926"
></path>
<path
d="M85.5508,43.0381 L85.5508,39.1991 C85.8628,39.2421 86.6158,39.2871 87.0158,39.2871 C89.2138,39.2871 90.4558,38.3551 91.2108,35.9581 L91.6548,34.5371 L83.2428,11.2321 L88.4368,11.2321 L94.2958,30.1421 L94.4068,30.1421 L100.2668,11.2321 L105.3278,11.2321 L96.6048,35.7141 C94.6078,41.3291 92.3208,43.1721 87.4828,43.1721 C87.1048,43.1721 85.8838,43.1271 85.5508,43.0381"
></path>
<path
d="M42.6499,19.3555 L48.3549,19.3555 C52.6829,19.3555 55.1469,17.0255 55.1469,12.9855 C55.1469,8.9455 52.6829,6.6375 48.3769,6.6375 L42.6499,6.6375 L42.6499,19.3555 Z M49.6869,2.4425 C55.9009,2.4425 60.2289,6.7265 60.2289,12.9625 C60.2289,19.2225 55.8129,23.5285 49.5309,23.5285 L42.6499,23.5285 L42.6499,34.4705 L37.6779,34.4705 L37.6779,2.4425 L49.6869,2.4425 Z"
></path>
<path
d="M76.5547,25.7705 L76.5547,23.9715 L71.0287,24.3275 C67.9207,24.5275 66.3007,25.6815 66.3007,27.7015 C66.3007,29.6545 67.9887,30.9195 70.6287,30.9195 C74.0027,30.9195 76.5547,28.7665 76.5547,25.7705 M61.4617,27.8345 C61.4617,23.7285 64.5917,21.3755 70.3627,21.0205 L76.5547,20.6425 L76.5547,18.8675 C76.5547,16.2705 74.8457,14.8495 71.8057,14.8495 C69.2967,14.8495 67.4777,16.1375 67.0997,18.1125 L62.6167,18.1125 C62.7497,13.9615 66.6567,10.9435 71.9387,10.9435 C77.6207,10.9435 81.3267,13.9175 81.3267,18.5345 L81.3267,34.4705 L76.7327,34.4705 L76.7327,30.6305 L76.6217,30.6305 C75.3127,33.1395 72.4267,34.7145 69.2967,34.7145 C64.6807,34.7145 61.4617,31.9625 61.4617,27.8345"
></path>
</g>
</g>
</svg>
</div>
<style lang="scss" module="scoped">
@supports not (-webkit-appearance: -apple-pay-button) {
.apple-pay-button-with-text {
display: none !important;
}
}
.apple-pay-button-with-text {
cursor: pointer;
--apple-pay-scale: 1; /* (height / 32) */
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 5px;
padding: 0.75rem 1rem;
width: fit-content;
box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
}
.apple-pay-button-with-text,
svg g {
transition: all 0.3s ease;
}
.apple-pay-button-black-with-text {
background-color: black;
color: white;
white-space: nowrap;
border: 2px solid black;
&:hover {
background-color: white;
color: black;
g {
fill: black;
}
}
}
.apple-pay-button-with-text > .text {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
'Open Sans', 'Helvetica Neue', sans-serif, Helvetica, sans-serif;
font-size: calc(1em * var(--apple-pay-scale));
font-weight: 300;
line-height: 1;
align-self: center;
margin-right: calc(2px * var(--apple-pay-scale));
}
.apple-pay-button-with-text > .logo {
width: 50px;
/*width: calc(35px * var(--scale));*/
height: 100%;
background-size: 100% 60%;
background-repeat: no-repeat;
background-position: 0 50%;
margin-top: calc(2px * var(--apple-pay-scale));
margin-left: calc(2px * var(--apple-pay-scale));
border: none;
}
</style>

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import BadgeType from '../interfaces/BadgeType';
const badgeIcons = {
[BadgeType.INFO]: '⏳',
[BadgeType.PENDING]: '📦',
[BadgeType.WARNING]: '⚠️',
[BadgeType.SUCCESS]: '✓',
[BadgeType.ERROR]: 'X'
};
export let title = 'Info';
export let type: BadgeType = BadgeType.INFO;
export let icon: string = badgeIcons[type];
$: badgeClass = `badge ${type}`;
</script>
<div class="{badgeClass}">
<span>{title}</span>
<span class="icon">{icon}</span>
</div>
<style lang="scss" module="scoped">
.badge {
padding: 0.2rem 0.5rem;
border-radius: 6px;
text-align: center;
width: fit-content;
font-size: 0.95rem;
background-color: rgb(235, 238, 241);
color: rgb(84, 89, 105);
margin: 0.2rem;
.icon {
margin-left: 0.3rem;
font-size: 0.8rem;
}
&.success {
background-color: rgb(215, 247, 194);
color: rgb(0, 105, 8);
}
&.error {
background-color: rgb(255, 231, 242);
color: rgb(179, 9, 60);
}
&.warning {
background-color: rgb(252, 237, 185);
color: rgb(168, 44, 0);
}
&.pending {
background-color: lightblue;
color: darkslateblue;
}
}
</style>

View File

@@ -0,0 +1,42 @@
<script lang="ts">
export let text = 'Bestill';
export let type: string | null = null;
export let active = false;
</script>
<button on:click type="{type}" class="{active ? 'active' : null}">{text}</button>
<style lang="scss" module="scoped">
button {
webkit-font-smoothing: antialiased;
align-items: flex-start;
display: block;
appearance: auto;
background-clip: border-box;
background-color: var(--color-theme-1);
border: 2px solid var(--color-theme-1);
color: white;
cursor: pointer;
display: block;
font-size: 18px;
font-weight: 500;
height: 48px;
letter-spacing: 1.2px;
line-height: 14px;
margin: 0;
min-width: 140px;
padding: 10px 30px;
text-align: center;
transition: all 0.3s ease;
&:hover,
&.active {
padding: 12px 32px;
transform: translateX(-2px);
background-color: white;
color: var(--color-theme-1);
border-color: var(--color-theme-1);
}
}
</style>

View File

@@ -0,0 +1,55 @@
<script lang="ts">
import { count, toggleCart, isOpen } from '../cartStore';
import IconCart from '../icons/IconCart.svelte';
</script>
<div id="cart" class="{$isOpen && 'open'}" on:click="{() => toggleCart()}">
{#if $count > 0}
<span>{$count}</span>
{/if}
<IconCart />
</div>
<style lang="scss" module="scoped">
#cart {
padding: 0.3rem 0.6rem;
display: grid;
place-items: center;
// background-color: #E6E0DC;
color: #18332f;
// border: 1px solid rgba(0,0,0,0.1);
z-index: 99;
transition: all 0.25s ease;
&:hover,
&.open {
cursor: pointer;
background-color: #18332f;
color: #e6e0dc;
border: 1px solid rgba(0, 0, 0, 0.1);
}
display: flex;
align-items: center;
border-radius: 6px;
margin-right: 0.5rem;
border: 1px solid transparent;
span {
margin-right: 0.75rem;
font-size: 1rem;
margin-top: -3px;
}
}
:global(#cart svg) {
max-width: 20px;
width: 20px;
fill: #18332f;
transition: fill 0.25s ease;
}
:global(#cart:hover svg, #cart.open svg) {
fill: #e6e0dc;
}
</style>

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import type IFrontText from '$lib/interfaces/IFrontText';
export let data: IFrontText;
</script>
<div class="text-container">
<blockquote style="{`color: ${data.color || 'black'}`}">
<p class="title">{data.title}</p>
<p class="text">{data.text}</p>
</blockquote>
</div>
<style lang="scss" module="scoped">
@import '../../styles/media-queries.scss';
.text-container {
position: relative;
blockquote {
max-width: 75%;
margin: 12.5%;
padding: 2rem;
display: flex;
flex-direction: column;
justify-content: center;
@include mobile {
margin: 10% 0;
}
.title {
text-align: center;
font-size: 1.3rem;
margin-bottom: 1.5rem;
@include desktop {
font-size: 2rem;
}
}
.text {
text-align: center;
font-size: 2rem;
margin-top: 0;
@include desktop {
font-size: 4rem;
}
&::before {
content: '\201C';
position: relative;
left: 0;
display: inline;
}
&::after {
content: '\201D';
position: absolute;
}
}
}
}
</style>

View File

@@ -0,0 +1,78 @@
<script lang="ts">
import type IFrontTextImage from '$lib/interfaces/IFrontTextImage';
export let data: IFrontTextImage;
</script>
<div class="parallax-container">
<div class="image-layout" style="{data?.imageRight && 'order: 1'}">
<img src="{data?.image}" alt="mushroom" />
</div>
<div class="text-container">
<h2>{data?.title}</h2>
<span>{data?.text}</span>
</div>
</div>
<style lang="scss">
@import '../../styles/media-queries.scss';
.parallax-container {
align-items: center;
box-sizing: border-box;
display: grid;
width: 100%;
height: fit-content;
// height: 75vh;
min-height: 800px;
place-items: center;
grid-template-columns: 1fr;
@include desktop {
grid-template-columns: repeat(2, 1fr);
padding: 0 2rem;
}
}
.image-layout {
position: relative;
img {
width: 100%;
}
}
.text-container {
-webkit-box-flex: 0;
box-sizing: border-box;
color: rgb(0, 0, 0);
display: block;
flex-basis: 75%;
flex-grow: 0;
flex-shrink: 0;
font-size: 23px;
font-weight: 400;
line-height: 31.999903px;
padding-left: 1rem;
h2 {
font-size: 3rem;
line-height: 1;
font-weight: 500;
}
span {
margin-top: 1.2rem;
}
@include desktop {
margin-left: 99.3125px;
width: 595.875px;
max-width: 75%;
padding-left: 24px;
padding-right: 24px;
}
}
</style>

View File

@@ -0,0 +1,98 @@
<section>
<div class="text-part">
<h2>Å stoppe tapet av biologisk mangfold globalt</h2>
<p>
En million arter av dyr, planter og sopp er nå truet med utryddelse. Biomangfold er viktig for
alt liv på jorden fordi det bidrar til stabile økosystemer. Naturmangfoldet kan bevares
gjennom mer kunnskap og vern.
</p>
</div>
<div class="image-part">
<div class="image-mask">
<img src="https://i.imgur.com/WWbfhiZ.jpg" alt="bee inspection" />
</div>
</div>
</section>
<style lang="scss" module="scoped">
@import '../../styles/media-queries.scss';
section {
display: grid;
grid-template-columns: 1fr;
align-items: center;
width: 100%;
margin: 40% 2rem;
grid-template-columns: 1fr;
@include tablet {
grid-template-columns: 1fr 1fr;
grid-gap: 10%;
margin: 20% 2rem;
.text-part {
max-width: 480px;
justify-self: end;
h2 {
font-size: 2.5rem;
margin-bottom: 2.5rem;
}
}
.image-part {
width: 100%;
justify-self: start !important;
max-width: calc(100vw / 3) !important;
max-height: calc(100vw / 3) !important;
}
}
.text-part {
text-align: center;
padding: 0 1rem;
h2 {
font-size: 2.25rem;
margin-bottom: 2.25rem;
}
p {
font-size: 1.25rem;
}
}
.image-part {
position: relative;
height: 80vw;
max-width: 80vw;
width: 100%;
justify-self: center;
.image-mask {
background-image: url(data:image/gif;base64,R0lGODlhCAAFAPUAAHmAR4OKUIOJUo2SW46VWY+TYZeeYZecZJicaZyecZ6kZ5+ka6GobKGkcaOpcKKocqSpcaeqdqitdaSmeKanfqmueauueqqsfamufK2xfq2uhK2wgK+ygq6zgq+zha+0ha+1h7CzhbGyhrKzhrC0hLG1h7G3irK3igAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAAIf8LSW1hZ2VNYWdpY2sOZ2FtbWE9MC45MDkwOTEALAAAAAAIAAUAAAYlQBPIExqJNJRTicTZXCaJTydjiTQQBUxFAlkcBoKHg6EwEAKAIAA7);
background-size: cover;
mask-repeat: no-repeat;
mask-size: cover;
mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjgwIiBoZWlnaHQ9IjY4MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNjQzLjMzIDE3Ni43NzRjNDkuMjQyIDE0MC4zMiA2NC41MzQgMzM3LjI1NC01NS4yMTYgNDE4LjI4LTExOS43NDkgODEuMDI3LTM2MC4zNTMgMTAyLjk4NC00OTguNjE4LTQuODg4cy05NS4wODgtNDAwLjkyNS0xMi44Mi00ODMuOWM4Mi4yNjgtODIuOTc1IDI0NS4wOTQtNzUuNTQ0IDMyMy4zNS04Ni45OUM0NzguMjgzIDcuODMgNTk0LjA5IDM2LjQ1NCA2NDMuMzMgMTc2Ljc3NHoiLz48L3N2Zz4=');
height: 100%;
width: 100%;
top: 0;
left: 0;
position: absolute;
}
img {
height: 100%;
object-fit: cover;
width: 100%;
left: 0;
top: 0;
position: absolute;
max-width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,61 @@
<script lang="ts">
export let id: string | null = null;
export let label: string;
export let value: string | number;
export let type = 'text';
export let name: string | null = null;
export let required = true;
export let autocomplete: string | null = null;
export let autocapitalize: string | null = null;
function typeAction(node: HTMLInputElement) {
node.type = type;
}
</script>
<label id="{id}" class="{required ? 'required' : null}">
<span>{label}</span>
<input
bind:value="{value}"
name="{name || id}"
use:typeAction
required="{required}"
autocomplete="{autocomplete}"
autocapitalize="{autocapitalize}"
enterkeyhint="next"
/>
</label>
<style lang="scss" module="scoped">
label {
display: flex;
flex-direction: column;
span {
margin-left: 2px;
font-size: 1.3rem;
}
&.required span::after {
content: '*';
color: #e02b27;
font-size: 1rem;
margin-left: 0.3rem;
}
}
input {
border: 2px solid grey;
border-radius: 0px;
padding: 0.5rem;
font-size: 1rem;
box-sizing: border-box;
width: 100%;
&:active,
&:focus,
&:valid {
border-color: var(--text-color);
}
}
</style>

View File

@@ -0,0 +1,15 @@
<svg
class="inline-block relative -top-1 w-10 h-10 mr-8 -stroke-green-boulogne"
width="8"
height="8"
viewBox="0 0 8 8"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fill-rule="evenodd" stroke-linejoin="round">
<g stroke="#8BA17F">
<path
d="M4.534.93a.189.189 0 000 .267l2.614 2.614H.397a.189.189 0 100 .378h6.75l-2.88 2.88a.189.189 0 10.267.268l3.203-3.204a.189.189 0 000-.266L4.801.93a.189.189 0 00-.267 0"
></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 482 B

View File

@@ -0,0 +1,69 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import type IProductVariation from '$lib/interfaces/IProductVariation';
const dispatch = createEventDispatcher();
export let variations: IProductVariation[];
let selectedVariation: IProductVariation;
function defaultVariationIndex(variations: IProductVariation[]) {
return variations.findIndex((variation) => variation?.default_price || false);
}
function emitSelectedVariation(_selectedVariation: IProductVariation) {
selectedVariation = _selectedVariation;
dispatch('selected', _selectedVariation);
}
onMount(() => {
if (!variations) return;
const defaultVariation = variations[defaultVariationIndex(variations) || 0];
emitSelectedVariation(defaultVariation);
});
</script>
<ul>
{#each variations as variation}
<li
class="{`variation ${variation.sku_id === selectedVariation?.sku_id && 'selected'}`}"
on:click="{() => emitSelectedVariation(variation)}"
>
<span>Størrelse: {variation.size}</span>
<span>NOK {variation.price}</span>
</li>
{/each}
</ul>
<style lang="scss">
ul {
list-style-type: none;
padding-left: 0;
max-width: 400px;
li.variation {
padding: 1rem 1rem;
margin: 0.5rem 0;
border: 2px solid rgba(0, 0, 0, 0.3);
display: flex;
justify-content: space-between;
transition: all 0.15s ease;
&:hover {
cursor: pointer;
border: 2px solid rgba(0, 0, 0, 0.6);
}
&.selected {
border-color: black;
}
p {
padding: 0;
margin: 0;
}
}
}
</style>

View File

@@ -0,0 +1,137 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function increment() {
if (value >= Number(input?.max)) return;
value = value + 1;
dispatch('increment');
}
function decrement() {
if (value <= 1) return;
value = value - 1;
dispatch('decrement');
}
export let value: number;
export let disabled = false;
export let hideButtons = false;
export let small = false;
let max = 50;
let input: HTMLInputElement;
</script>
<div
class="{`quantity-picker ${hideButtons ? 'hide-buttons' : null} ${small ? 'small' : null} ${
disabled ? 'disabled' : null
}`}"
>
<span class="button" on:click="{decrement}">-</span>
<input
bind:this="{input}"
type="number"
bind:value="{value}"
on:input
disabled="{disabled}"
min="1"
max="{max}"
/>
<span class="button" on:click="{increment}">+</span>
</div>
<style lang="scss" module="scoped">
@import '../../styles/media-queries.scss';
.quantity-picker {
display: inline-flex;
flex-direction: row;
align-items: center;
width: max-content;
height: 2.5rem;
border: 2px solid grey;
font-size: 1.5rem;
&.disabled {
pointer-events: none;
}
@include mobile {
height: 2.5rem;
font-size: 1.2rem;
line-height: 0;
.button {
width: 1rem;
height: 1rem;
}
}
&.hide-buttons {
@include mobile {
.button {
display: none !important;
}
}
}
@include mobile {
&.small {
height: 1.75rem;
.button {
width: 1.75rem;
height: 1.75rem;
font-size: 1.2rem;
}
input {
font-size: 1rem;
padding: 0;
}
}
}
.button {
width: 2.5rem;
height: 2.5rem;
border: unset;
padding: 0;
display: flex;
justify-content: center;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
align-items: center;
touch-action: manipulation;
&:hover {
border: 2px solid #000;
margin-left: -2px;
margin-right: -2px;
}
}
}
input {
max-width: 1.5rem;
text-align: center;
-webkit-appearance: none;
border: none;
padding: 0 0.5rem;
font-size: 1.1rem;
background-color: inherit;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
&[type='number'] {
-moz-appearance: textfield;
}
}
</style>

View File

@@ -0,0 +1,114 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { loadStripe } from '@stripe/stripe-js/pure';
import type { Stripe } from '@stripe/stripe-js';
import { cart } from '../cartStore';
function mountCard() {
const elements = stripe.elements();
const options = {
hidePostalCode: true,
style: {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '18px',
'::placeholder': {
color: '#aab7c4'
}
}
}
};
card = elements.create('card', options);
card.mount(cardElement);
}
// function makeIntent() {
// let url = "/api/payment/stripe";
// if (window.location.href.includes("localhost"))
// url = "http://localhost:30010".concat(url);
// fetch(url, {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify({
// cart: $cart
// })
// })
// .then((resp) => resp.json())
// .then((data) => (clientSecret = data.paymentIntent.clientSecret));
// }
function pay() {
stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card,
billing_details: {
name: 'Kevin Testost'
}
}
})
.then((result) => {
if (result.error) {
confirmDiag.innerText = result.error.message || 'Unexpected payment ERROR!';
} else {
if (result.paymentIntent.status === 'succeeded') {
confirmDiag.innerText = 'Confirmed transaction!';
}
}
});
}
async function initStripe() {
window.addEventListener('submit-stripe-payment', pay, false);
loadStripe.setLoadParameters({ advancedFraudSignals: false });
stripe = await loadStripe('pk_test_YiU5HewgBoClZCwHdhXhTxUn');
mountCard();
// makeIntent();
}
onMount(() => initStripe());
// onDestroy(() => window.removeEventListener('submit-stripe-payment', null))
let stripe: Stripe;
let card;
let cardElement: HTMLElement;
let clientSecret: string;
let confirmDiag: HTMLElement;
</script>
<div class="card">
<div bind:this="{cardElement}"></div>
</div>
<div class="stripe-feedback" bind:this="{confirmDiag}"></div>
<style lang="scss" module="scoped">
@import '../../styles/media-queries.scss';
.card {
// padding: 1rem;
margin: 0 0.5rem;
border: 2px solid black;
@include desktop {
max-width: 75%;
}
}
.stripe-feedback {
margin: 0.5rem;
}
:global(.card .StripeElement) {
padding: 0.5rem;
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<div aria-role="button" on:click>
<svg fill="none" height="44" viewBox="0 0 250 44" width="250" xmlns="http://www.w3.org/2000/svg"
><path
d="m0 8c0-2.80026 0-4.20039.544967-5.26995.479363-.94081 1.244273-1.70572 2.185083-2.185083 1.06956-.544967 2.46969-.544967 5.26995-.544967h234c2.8 0 4.2 0 5.27.544967.941.479363 1.706 1.244273 2.185 2.185083.545 1.06956.545 2.46969.545 5.26995v28c0 2.8003 0 4.2004-.545 5.27-.479.9408-1.244 1.7057-2.185 2.185-1.07.545-2.47.545-5.27.545h-234c-2.80026 0-4.20039 0-5.26995-.545-.94081-.4793-1.70572-1.2442-2.185083-2.185-.544967-1.0696-.544967-2.4697-.544967-5.27z"
fill="#ff5b24"></path><g fill="#fff"
><path
clip-rule="evenodd"
d="m100.25 20.0884c-.7202-2.7477-2.4686-3.8384-4.8553-3.8384-1.9336 0-4.3605 1.0907-4.3605 3.7172 0 1.6968 1.1723 3.0304 3.0852 3.374l1.8103.323c1.2344.2221 1.5842.6869 1.5842 1.3132 0 .7071-.7612 1.111-1.8926 1.111-1.4809 0-2.4067-.5252-2.5508-2.0001l-2.6124.4042c.4112 2.8483 2.9619 4.0204 5.2658 4.0204 2.1808 0 4.5051-1.2528 4.5051-3.778 0-1.7174-1.0492-2.9696-3.0034-3.3337l-1.9951-.3633c-1.1111-.202-1.4812-.7475-1.4812-1.2727 0-.6668.7198-1.0907 1.7073-1.0907 1.255 0 2.1395.4239 2.1806 1.8179zm-58.075 4.3229 2.7149-7.8584h3.1884l-4.7314 11.6565h-2.3654l-4.7315-11.6563h3.1884zm16.6834-4.5249c0 .9292-.7406 1.5756-1.6047 1.5756-.864 0-1.6043-.6464-1.6043-1.5756 0-.9294.7403-1.5756 1.6043-1.5756.8641 0 1.6049.6462 1.6049 1.5756zm.4936 4.1213c-1.0699 1.3734-2.2013 2.3229-4.1967 2.323-2.036 0-3.6204-1.2121-4.8546-2.9897-.4939-.7275-1.255-.889-1.8105-.5051-.5142.3637-.6374 1.1314-.1645 1.7982 1.7073 2.5656 4.0729 4.0602 6.8294 4.0602 2.5305 0 4.5055-1.2119 6.0481-3.2322.5759-.7473.5553-1.515 0-1.9393-.5144-.4044-1.2755-.2624-1.8512.4849zm7.098-1.6568c0 2.384 1.3989 3.6367 2.9625 3.6367 1.4809 0 3.0034-1.1719 3.0034-3.6367 0-2.4244-1.5225-3.5959-2.9831-3.5959-1.5839 0-2.9828 1.1111-2.9828 3.5959zm0-4.1815v-1.5964h-2.9004v15.677h2.9004v-5.576c.9669 1.2932 2.2217 1.8389 3.6409 1.8389 2.6541 0 5.2459-2.0608 5.2459-6.3031 0-4.061-2.6948-5.9596-4.9989-5.9596-1.8309 0-3.0855.8281-3.8879 1.9192zm13.9275 4.1815c0 2.384 1.3987 3.6367 2.9623 3.6367 1.4809 0 3.0032-1.1719 3.0032-3.6367 0-2.4244-1.5223-3.5959-2.9828-3.5959-1.584 0-2.9829 1.1111-2.9829 3.5959zm0-4.1815v-1.5964h-.0002-2.9004v15.677h2.9004v-5.576c.967 1.2932 2.2217 1.8389 3.641 1.8389 2.6539 0 5.2459-2.0608 5.2459-6.3031 0-4.061-2.6948-5.9596-4.999-5.9596-1.8309 0-3.0854.8281-3.8877 1.9192z"
fill-rule="evenodd"></path><path
d="m118.384 28.25v-13.0349h-2.321v5.4199h-6.342v-5.4199h-2.33v13.0349h2.33v-5.6548h6.342v5.6548zm11.39-9.8733h-2.24v5.7542c0 1.4904-.795 2.3938-2.25 2.3938-1.337 0-1.96-.7408-1.96-2.2764v-5.8716h-2.24v6.4136c0 2.3125 1.265 3.6584 3.424 3.6584 1.508 0 2.475-.6413 2.953-1.7705h.154v1.5718h2.159zm2.546 9.8733h2.24v-5.7361c0-1.3911 1.012-2.2944 2.466-2.2944.38 0 .976.0632 1.147.1264v-2.0415c-.207-.0542-.605-.0903-.93-.0903-1.274 0-2.34.7227-2.611 1.6983h-.153v-1.5357h-2.159zm8.662-12.2942v2.4841h-1.563v1.7434h1.563v5.4471c0 1.906.903 2.6648 3.17 2.6648.434 0 .849-.0362 1.175-.0994v-1.7163c-.271.0271-.443.0452-.759.0452-.94 0-1.355-.4427-1.355-1.4273v-4.9141h2.114v-1.7434h-2.114v-2.4841zm7.523.7678c.741 0 1.346-.5871 1.346-1.3278 0-.7317-.605-1.3279-1.346-1.3279-.732 0-1.337.5962-1.337 1.3279 0 .7407.605 1.3278 1.337 1.3278zm-1.111 11.5264h2.231v-9.8733h-2.231zm9.059 3.7578c2.881 0 4.706-1.4363 4.706-3.7036v-9.9275h-2.159v1.5808h-.153c-.542-1.0749-1.717-1.7434-3.063-1.7434-2.52 0-4.083 1.9692-4.083 4.9954 0 2.9629 1.554 4.896 4.047 4.896 1.373 0 2.448-.5782 3.017-1.6441h.154v1.8789c0 1.2556-.894 1.9964-2.43 1.9964-1.229 0-2.023-.4427-2.168-1.1382h-2.249c.18 1.6982 1.843 2.8093 4.381 2.8093zm0-5.6819c-1.563 0-2.448-1.2104-2.448-3.1255 0-1.906.885-3.1164 2.448-3.1164 1.554 0 2.502 1.2104 2.502 3.1255 0 1.906-.939 3.1164-2.502 3.1164zm9.745-3.9023h-.153v-7.8679h-2.24v13.6943h2.24v-3.4688l.795-.7949 3.17 4.2637h2.71l-4.209-5.6367 3.938-4.2366h-2.601zm10.442 5.989c1.291 0 2.375-.5601 2.926-1.5176h.154v1.355h2.159v-6.7478c0-2.0867-1.427-3.3152-3.966-3.3152-2.348 0-3.983 1.1021-4.164 2.8274h2.114c.207-.6775.912-1.0478 1.942-1.0478 1.21 0 1.861.551 1.861 1.5356v.8491l-2.548.1536c-2.403.1355-3.748 1.1743-3.748 2.9448 0 1.8066 1.364 2.9629 3.27 2.9629zm.659-1.7253c-.994 0-1.707-.4969-1.707-1.346 0-.822.587-1.2827 1.842-1.364l2.232-.1536v.804c0 1.1743-1.012 2.0596-2.367 2.0596zm6.855-5.4922c0 1.4814.894 2.3486 2.791 2.7822l1.753.4065c.939.2168 1.364.5781 1.364 1.1382 0 .7497-.786 1.2646-1.915 1.2646-1.121 0-1.816-.4516-2.033-1.1833h-2.213c.172 1.7795 1.725 2.8454 4.191 2.8454s4.21-1.2556 4.21-3.1435c0-1.4544-.876-2.2583-2.764-2.6919l-1.744-.3884c-.993-.2349-1.463-.5872-1.463-1.1473 0-.7317.768-1.2285 1.788-1.2285 1.048 0 1.708.4517 1.87 1.1472h2.105c-.163-1.7795-1.635-2.8093-3.984-2.8093-2.33 0-3.956 1.2376-3.956 3.0081zm9.809 0c0 1.4814.894 2.3486 2.791 2.7822l1.752.4065c.94.2168 1.364.5781 1.364 1.1382 0 .7497-.785 1.2646-1.915 1.2646-1.12 0-1.815-.4516-2.032-1.1833h-2.213c.171 1.7795 1.725 2.8454 4.191 2.8454s4.21-1.2556 4.21-3.1435c0-1.4544-.877-2.2583-2.765-2.6919l-1.743-.3884c-.994-.2349-1.463-.5872-1.463-1.1473 0-.7317.768-1.2285 1.788-1.2285 1.048 0 1.708.4517 1.87 1.1472h2.105c-.163-1.7795-1.635-2.8093-3.984-2.8093-2.33 0-3.956 1.2376-3.956 3.0081zm16.656 4.354c-.326.7407-1.093 1.1562-2.204 1.1562-1.473 0-2.421-1.0478-2.476-2.719v-.1174h6.902v-.7136c0-3.0984-1.689-4.9683-4.508-4.9683-2.863 0-4.643 1.9963-4.643 5.167 0 3.1616 1.753 5.0947 4.661 5.0947 2.331 0 3.984-1.1201 4.39-2.8996zm-2.295-5.6187c1.346 0 2.231.9485 2.277 2.448h-4.653c.1-1.4814 1.039-2.448 2.376-2.448z"
></path></g
></svg
>
</div>
</template>
<style lang="scss" scoped>
div {
display: inline-block;
width: max-content;
height: 44px;
border-radius: 7px;
cursor: pointer;
border: 2px solid #ff5b24;
background-color: #ff5b24;
path {
transition: all 0.3s ease;
fill: #ff5b24;
}
g path {
fill: white;
}
&:hover {
path {
fill: white;
}
g,
g path {
fill: #ff5b24;
}
}
}
</style>

View File

@@ -0,0 +1,34 @@
<svg width="49" height="48" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<g transform="translate(24,24)">
<g class="CheckSuccess-checkGroup" opacity="0" style="animation-delay: 700ms">
<path
class="CheckSuccess-check"
fill="none"
d="M-10 1.5c0 0 6.5 6 6.5 6c0 0 13.5-13 13.5-13"
stroke="#24B47E"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="28 28"
stroke-dashoffset="28"
style="animation-delay: 700ms"></path>
</g>
</g>
<path
class="CheckSuccess-circle"
fill="none"
stroke="#24B47E"
stroke-width="2"
d="M23 0c0 12.7-10.3 23-23 23c-12.7 0-23-10.3-23-23c0-12.7 10.3-23 23-23c12.7 0 23 10.3 23 23"
stroke-linecap="round"
stroke-dashoffset="145"
stroke-dasharray="145 145"
stroke-linejoin="round"
stroke-miterlimit="1"
transform="translate(24,24) rotate(-35)"
style="animation-delay: 700ms"></path>
</svg>
<style lang="scss" module="scoped">
@import './circle-feedback.scss';
</style>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,34 @@
<svg width="49" height="48" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<g transform="translate(24,24)">
<g class="CheckError-checkGroup" opacity="0" style="animation-delay: 700ms">
<path
class="CheckError-cross"
fill="none"
d="M -10 -10 l 20 20 M -10 10 l 20 -20"
stroke="#FF6245"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="28 28"
stroke-dashoffset="28"
style="animation-delay: 700ms"></path>
</g>
</g>
<path
class="CheckError-circle"
fill="none"
stroke="#FF6245"
stroke-width="2"
d="M23 0c0 12.7-10.3 23-23 23c-12.7 0-23-10.3-23-23c0-12.7 10.3-23 23-23c12.7 0 23 10.3 23 23"
stroke-linecap="round"
stroke-dashoffset="145"
stroke-dasharray="145 145"
stroke-linejoin="round"
stroke-miterlimit="1"
transform="translate(24,24) rotate(-35)"
style="animation-delay: 700ms"></path>
</svg>
<style lang="scss" module="scoped">
@import './circle-feedback.scss';
</style>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,34 @@
<svg width="49" height="48" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<g transform="translate(24,24)">
<g class="CheckWarning-checkGroup" opacity="0" style="animation-delay: 700ms">
<path
class="CheckWarning-exclamation"
fill="none"
d="M 0 -12 v 15 m 0 6 v 3"
stroke="#FFC107"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
stroke-dasharray="28 28"
stroke-dashoffset="28"
style="animation-delay: 700ms"></path>
</g>
</g>
<path
class="CheckWarning-circle"
fill="none"
stroke="#FFC107"
stroke-width="2"
d="M23 0c0 12.7-10.3 23-23 23c-12.7 0-23-10.3-23-23c0-12.7 10.3-23 23-23c12.7 0 23 10.3 23 23"
stroke-linecap="round"
stroke-dashoffset="145"
stroke-dasharray="145 145"
stroke-linejoin="round"
stroke-miterlimit="1"
transform="translate(24,24) rotate(-35)"
style="animation-delay: 700ms"></path>
</svg>
<style lang="scss" module="scoped">
@import './circle-feedback.scss';
</style>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,14 @@
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 768 768"
>
<title></title>
<g id="icomoon-ignore"> </g>
<path
fill="inherit"
d="M352 672c0-17.664-7.2-33.696-18.752-45.248s-27.584-18.752-45.248-18.752-33.696 7.2-45.248 18.752-18.752 27.584-18.752 45.248 7.2 33.696 18.752 45.248 27.584 18.752 45.248 18.752 33.696-7.2 45.248-18.752 18.752-27.584 18.752-45.248zM704 672c0-17.664-7.2-33.696-18.752-45.248s-27.584-18.752-45.248-18.752-33.696 7.2-45.248 18.752-18.752 27.584-18.752 45.248 7.2 33.696 18.752 45.248 27.584 18.752 45.248 18.752 33.696-7.2 45.248-18.752 18.752-27.584 18.752-45.248zM231.072 224h466.24l-43.872 230.112c-1.472 7.296-5.312 13.6-10.624 18.176-5.76 4.992-13.216 7.872-22.016 7.712h-311.488c-7.424 0.096-14.432-2.272-20.032-6.496-6.080-4.576-10.528-11.232-12.128-19.296zM32 64h101.76l27.136 135.648c3.456 13.984 16.064 24.352 31.104 24.352h39.072l-12.8-64h-26.272c-17.664 0-32 14.336-32 32 0 1.696 0.128 3.36 0.384 4.96 0.128 0.896 0.32 1.824 0.544 2.688l53.472 267.104c4.768 24.032 18.24 44.256 36.48 57.952 16.672 12.544 37.44 19.616 59.328 19.296h310.592c23.936 0.48 46.56-8.352 63.84-23.264 15.808-13.632 27.136-32.416 31.52-53.856l51.264-268.864c3.296-17.376-8.064-34.112-25.44-37.44-2.080-0.416-4.16-0.608-5.984-0.576h-517.76l-26.88-134.272c-3.008-14.784-15.904-25.728-31.36-25.728h-128c-17.664 0-32 14.336-32 32s14.336 32 32 32z"
></path>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,15 @@
<svg
on:click
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 768 768"
>
<title></title>
<g id="icomoon-ignore"> </g>
<path
fill="inherit"
d="M576 224v416c0 8.832-3.552 16.8-9.376 22.624s-13.792 9.376-22.624 9.376h-320c-8.832 0-16.8-3.552-22.624-9.376s-9.376-13.792-9.376-22.624v-416zM544 160v-32c0-26.496-10.784-50.56-28.128-67.872s-41.376-28.128-67.872-28.128h-128c-26.496 0-50.56 10.784-67.872 28.128s-28.128 41.376-28.128 67.872v32h-128c-17.664 0-32 14.336-32 32s14.336 32 32 32h32v416c0 26.496 10.784 50.56 28.128 67.872s41.376 28.128 67.872 28.128h320c26.496 0 50.56-10.784 67.872-28.128s28.128-41.376 28.128-67.872v-416h32c17.664 0 32-14.336 32-32s-14.336-32-32-32zM288 160v-32c0-8.832 3.552-16.8 9.376-22.624s13.792-9.376 22.624-9.376h128c8.832 0 16.8 3.552 22.624 9.376s9.376 13.792 9.376 22.624v32z"
></path>
</svg>

After

Width:  |  Height:  |  Size: 895 B

View File

@@ -0,0 +1,184 @@
@-webkit-keyframes fadeIn {
0% {
opacity: 0;
}
13.3% {
opacity: 0;
}
15% {
opacity: 1;
}
to {
opacity: 1;
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
13.3% {
opacity: 0;
}
15% {
opacity: 1;
}
to {
opacity: 1;
}
}
@-webkit-keyframes drawCheckmark {
0% {
stroke-dashoffset: 28px;
}
15% {
stroke-dashoffset: 28px;
-webkit-animation-timing-function: cubic-bezier(1, 0, 0, 1);
animation-timing-function: cubic-bezier(1, 0, 0, 1);
}
55% {
stroke-dashoffset: 0px;
}
to {
stroke-dashoffset: 0px;
}
}
@keyframes drawCheckmark {
0% {
stroke-dashoffset: 28px;
}
15% {
stroke-dashoffset: 28px;
-webkit-animation-timing-function: cubic-bezier(1, 0, 0, 1);
animation-timing-function: cubic-bezier(1, 0, 0, 1);
}
55% {
stroke-dashoffset: 0px;
}
to {
stroke-dashoffset: 0px;
}
}
@-webkit-keyframes checkmarkCircleShimmer {
0% {
-webkit-transform: translate(24px, 24px) rotate(-35deg);
transform: translate(24px, 24px) rotate(-35deg);
}
15% {
-webkit-animation-timing-function: cubic-bezier(1, 0, 0, 1);
animation-timing-function: cubic-bezier(1, 0, 0, 1);
-webkit-transform: translate(24px, 24px) rotate(-35deg);
transform: translate(24px, 24px) rotate(-35deg);
}
75% {
-webkit-transform: translate(24px, 24px) rotate(325deg);
transform: translate(24px, 24px) rotate(325deg);
}
to {
-webkit-transform: translate(24px, 24px) rotate(325deg);
transform: translate(24px, 24px) rotate(325deg);
}
}
@keyframes checkmarkCircleShimmer {
0% {
-webkit-transform: translate(24px, 24px) rotate(-35deg);
transform: translate(24px, 24px) rotate(-35deg);
}
15% {
-webkit-animation-timing-function: cubic-bezier(1, 0, 0, 1);
animation-timing-function: cubic-bezier(1, 0, 0, 1);
-webkit-transform: translate(24px, 24px) rotate(-35deg);
transform: translate(24px, 24px) rotate(-35deg);
}
75% {
-webkit-transform: translate(24px, 24px) rotate(325deg);
transform: translate(24px, 24px) rotate(325deg);
}
to {
-webkit-transform: translate(24px, 24px) rotate(325deg);
transform: translate(24px, 24px) rotate(325deg);
}
}
.CheckSuccess-checkGroup,
.CheckError-checkGroup,
.CheckWarning-checkGroup {
-webkit-animation: fadeIn 1s linear both;
animation: fadeIn 1s linear both;
}
.CheckSuccess-check,
.CheckError-cross,
.CheckWarning-exclamation {
-webkit-animation: drawCheckmark 1s linear both;
animation: drawCheckmark 1s linear both;
}
.CheckSuccess-circle,
.CheckError-circle,
.CheckWarning-circle {
-webkit-animation: checkmarkCircleShimmer 1s linear both, drawCircle 1s linear both;
animation: checkmarkCircleShimmer 1s linear both, drawCircle 1s linear both;
}
@-webkit-keyframes drawCircle {
0% {
stroke-dashoffset: 145px;
}
8.35% {
stroke-dashoffset: 145px;
-webkit-animation-timing-function: cubic-bezier(1, 0, 0, 1);
animation-timing-function: cubic-bezier(1, 0, 0, 1);
}
38.35% {
stroke-dashoffset: 0px;
}
to {
stroke-dashoffset: 0px;
}
}
@keyframes drawCircle {
0% {
stroke-dashoffset: 145px;
}
8.35% {
stroke-dashoffset: 145px;
-webkit-animation-timing-function: cubic-bezier(1, 0, 0, 1);
animation-timing-function: cubic-bezier(1, 0, 0, 1);
}
38.35% {
stroke-dashoffset: 0px;
}
to {
stroke-dashoffset: 0px;
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

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

View File

@@ -0,0 +1,9 @@
enum BadgeType {
SUCCESS = 'success',
WARNING = 'warning',
ERROR = 'error',
PENDING = 'pending',
INFO = 'info'
}
export default BadgeType;

View File

@@ -0,0 +1,5 @@
export default interface IFrontText {
title: string;
text: string;
color: string;
}

View File

@@ -0,0 +1,6 @@
export default interface IFrontTextImage {
title: string;
text: string;
image: string;
imageRight?: boolean;
}

View File

@@ -0,0 +1,69 @@
// import type IProduct from './IProduct';
// import type BadgeType from './BadgeType';
export interface IStripePayment {
amount: number;
currency: string;
}
export interface IOrderSummary {
created: Date;
email: string;
first_name: string;
last_name: string;
order_id: string;
order_sum: number;
status: string;
}
export interface IOrder {
customer: ICustomer;
lineItems: ILineItem[];
orderid: string;
shipping: IShipping;
status: string;
updated?: Date;
created?: Date;
}
export interface ICustomer {
city: string;
customer_no: string;
email: string;
firstname: string;
lastname: string;
streetaddress: string;
zipcode: number;
}
export interface ILineItem {
image: string;
name: string;
price: number;
quantity: number;
size: string;
}
export interface IShipping {
company: string;
tracking_code: string;
tracking_link: string;
user_notified: null;
}
export interface IOrdersLineitem {
orders_lineitem_id: string;
order_id: string;
product_no: number;
product_sku_no: number;
quantity: number;
created?: Date;
updated?: Date;
}
export interface ITracking {
orderId: string;
trackingCode: string;
trackingCompany: string;
trackingLink: string;
}

View File

@@ -0,0 +1,16 @@
import type IProductVariation from './IProductVariation';
export default interface IProduct {
product_no: number;
name: string;
subtext?: string;
description?: string;
image: string;
currency: string;
variations?: IProductVariation[];
primary_color?: string;
variation_count?: string;
sum_stock?: number;
updated?: Date;
created?: Date;
}

View File

@@ -0,0 +1,9 @@
export default interface IProductVariation {
sku_id: number | null;
price: number;
size: string | null;
stock: number;
default_price: boolean;
updated?: Date;
created?: Date;
}

24
src/lib/utils/cookie.ts Normal file
View File

@@ -0,0 +1,24 @@
export function getCookie(name: string): string | object {
if (!document) return;
const cookies = document.cookie.split(';').reduce((acc, cookieString) => {
const [key, value] = cookieString.split('=').map((s) => s.trim());
if (key && value) {
acc[key] = decodeURIComponent(value);
}
return acc;
}, {});
return name ? cookies[name] || '' : cookies;
}
export function setCookie(name: string, value: string, options = {}) {
if (!document) return;
document.cookie = `${name}=${encodeURIComponent(value)}${Object.keys(options).reduce(
(acc, key) => {
return acc + `;${key.replace(/([A-Z])/g, ($1) => '-' + $1.toLowerCase())}=${options[key]}`;
},
''
)}`;
}

87
src/lib/utils/mock.ts Normal file
View File

@@ -0,0 +1,87 @@
import generateUUID from './uuid';
import type IProduct from '../interfaces/IProduct';
import type { IOrder, ICustomer } from '../interfaces/IOrders';
import BadgeType from '../interfaces/BadgeType';
const productNames = ["Who's Who", 'Lullaby', 'The Buried Life', 'The Illegitimate'];
const images = [
'https://cdn-fsly.yottaa.net/551561a7312e580499000a44/www.joann.com/v~4b.100/dw/image/v2/AAMM_PRD/on/demandware.static/-/Sites-joann-product-catalog/default/dw4a83425c/images/hi-res/18/18163006.jpg?sw=556&sh=680&sm=fit&yocs=7x_7C_7D_',
'https://cdn-fsly.yottaa.net/55d09df20b53443653002f02/www.joann.com/v~4b.ed/dw/image/v2/AAMM_PRD/on/demandware.static/-/Sites-joann-product-catalog/default/dwc10a651e/images/hi-res/alt/17767534Alt1.jpg?sw=350&sh=350&sm=fit&yocs=f_',
'https://cdn-fsly.yottaa.net/55d09df20b53443653002f02/www.joann.com/v~4b.ed/dw/image/v2/AAMM_PRD/on/demandware.static/-/Sites-joann-product-catalog/default/dw3f43e4d8/images/hi-res/alt/18995779ALT1.jpg?sw=350&sh=350&sm=fit&yocs=f_',
'https://cdn-fsly.yottaa.net/551561a7312e580499000a44/www.joann.com/v~4b.100/dw/image/v2/AAMM_PRD/on/demandware.static/-/Sites-joann-product-catalog/default/dw029904bd/images/hi-res/alt/18162834alt1.jpg?sw=350&sh=350&sm=fit&yocs=7x_7C_7D_',
'https://adrianbrinkerhoff.imgix.net/AdrianBrinkerhoff-MatthewThompson-103.jpg?auto=compress%2Cformat&bg=%23FFFFFF&crop=focalpoint&fit=crop&fp-x=0.5&fp-y=0.5&h=431&q=90&w=310&s=018ae410aa6b64e6c9c5ca6bb18a1137',
'https://adrianbrinkerhoff.imgix.net/AdrianBrinkerhoff-MatthewThompson-166.jpg?auto=compress%2Cformat&bg=%23FFFFFF&crop=focalpoint&fit=crop&fp-x=0.5&fp-y=0.5&h=431&q=90&w=310&s=50a1f0fb259452fb84453ee4216dd4f1',
'https://adrianbrinkerhoff.imgix.net/AdrianBrinkerhoff-MatthewThompson-108.jpg?auto=compress%2Cformat&bg=%23FFFFFF&crop=focalpoint&fit=crop&fp-x=0.5&fp-y=0.5&h=431&q=90&w=310&s=b4a75bdea66974a4f766ded52bfe9ba0',
'https://adrianbrinkerhoff.imgix.net/AdrianBrinkerhoff-MatthewThompson-32.jpg?auto=compress%2Cformat&bg=%23FFFFFF&crop=focalpoint&fit=crop&fp-x=0.5&fp-y=0.5&h=431&q=90&w=310&s=9199c53ea58a923373f7bcce1145193e'
];
const statusText = {
[BadgeType.INFO]: 'Pending',
[BadgeType.SUCCESS]: 'Succeeded',
[BadgeType.WARNING]: 'Warning',
[BadgeType.PENDING]: 'In transit',
[BadgeType.ERROR]: 'Error'
};
const statusTypes = [
BadgeType.INFO,
BadgeType.SUCCESS,
BadgeType.WARNING,
BadgeType.PENDING,
BadgeType.ERROR
];
function mockCustomer(): ICustomer {
const customer: ICustomer = {
email: 'kevin.midboe@gmail.com',
firstName: 'kevin',
lastName: 'midbøe',
streetAddress: 'Schleppegrells gate 18',
zipCode: '0556',
city: 'Oslo'
};
customer.fullName = `${customer.firstName} ${customer.lastName}`;
return customer;
}
export function mockOrder(id: string | null = null): IOrder {
const products = mockProducts(4);
const status = statusTypes[Math.floor(Math.random() * statusTypes.length)];
return {
uuid: id || generateUUID(),
products,
customer: mockCustomer(),
payment: {
amount: Math.round(Math.random() * 800),
currency: 'NOK'
},
createdDate: new Date(),
updatedDate: new Date(),
status: {
type: status,
text: statusText[status]
}
};
}
export function mockOrders(count: number): Array<IOrder> {
return Array.from(Array(count)).map(() => mockOrder());
}
export function mockProduct(): IProduct {
return {
uuid: generateUUID(),
name: productNames[Math.floor(Math.random() * productNames.length)],
price: Math.floor(Math.random() * 999),
quantity: Math.floor(Math.random() * 4) + 1,
currency: 'NOK',
image: images[Math.floor(Math.random() * images.length)]
};
}
export function mockProducts(count: number): Array<IProduct> {
return Array.from(Array(count)).map(() => mockProduct());
}

View File

@@ -0,0 +1,11 @@
import { dev } from '$app/environment';
export default async function requestSessionCookie() {
let url = '/api';
if (dev) {
url = 'http://localhost:30010'.concat(url);
}
await fetch(url);
return true;
}

8
src/lib/utils/uuid.ts Normal file
View File

@@ -0,0 +1,8 @@
export default function generateUUID(len = 8) {
const hex = '0123456789abcdef';
let output = '';
for (let i = 0; i < len; ++i) {
output += hex.charAt(Math.floor(Math.random() * hex.length));
}
return output;
}

View File

@@ -0,0 +1,5 @@
export default function validOrderId(orderId = '') {
const re = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const isValidOrderId = String(orderId).match(re);
return isValidOrderId ? true : false;
}

110
src/lib/websocketCart.ts Normal file
View File

@@ -0,0 +1,110 @@
import { dev } from '$app/environment';
import { cart as cartStore } from './cartStore';
const WS_HOST = '127.0.0.1';
const WS_PORT = 30010;
let ws: WebSocket;
let wsReconnectTimeout: ReturnType<typeof setTimeout> | undefined;
function getCookie(key: string) {
const name = key + '=';
const decodedCookie = decodeURIComponent(document?.cookie || '');
const ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return null;
}
export function getCart() {
sendPayload({ command: 'cart' });
}
export function addProductToCart(product_no: number, product_sku_no: number, quantity: number) {
sendPayload({ command: 'add', product_no, product_sku_no, quantity });
}
export function removeProductFromCart(lineitem_id: number) {
sendPayload({ command: 'rm', lineitem_id });
}
export function decrementProductInCart(lineitem_id: number) {
sendPayload({ command: 'decrement', lineitem_id });
}
export function incrementProductInCart(lineitem_id: number) {
sendPayload({ command: 'increment', lineitem_id });
}
function sendPayload(payload: object) {
ws.send(JSON.stringify(payload));
}
export function reconnectIfCartWSClosed() {
const closed = ws?.readyState === 3;
if (!closed) return;
connectToCart();
}
function heartbeat() {
if (!ws) return;
if (ws.readyState !== 1) return;
ws.send('heartbeat');
setTimeout(heartbeat, 40000);
}
export function connectToCart(attempts = 0, maxAttempts = 6) {
attempts = attempts + 1;
clearTimeout(wsReconnectTimeout);
wsReconnectTimeout = undefined;
if (attempts >= maxAttempts) {
console.debug('Max retries, waiting for explicit reconnect function call.');
return;
}
// TODO user feedback when max retries to reconnect, should refresh
// increasing timeout by a factor
const planetId = getCookie('planetId');
if (!planetId) return console.log('no cookie');
let url = `wss://${window.location.hostname}/ws/cart`;
if (dev) {
url = `ws://${WS_HOST}:${WS_PORT}/ws/cart?planetId=${planetId}`;
}
ws = new WebSocket(url);
ws.onopen = () => {
console.debug('Websocket connection for cart established');
getCart();
heartbeat();
};
ws.onmessage = (event: MessageEvent) => {
try {
const json = JSON.parse(event?.data || {});
const { success, cart } = json;
if (success && cart) cartStore.set(cart);
} catch {
console.debug('Non parsable message from server: ', event?.data);
}
};
ws.onclose = () => {
const seconds = attempts ** 2;
console.debug(`Socket is closed. Reconnect will be attempted in ${seconds} seconds.`);
wsReconnectTimeout = setTimeout(() => connectToCart(attempts, maxAttempts), seconds * 1000);
};
ws.onerror = (err: Event) => {
console.error('Unexpected websocket error:', err);
ws.close();
};
}