diff options
Diffstat (limited to 'src/components/Items.svelte')
| -rw-r--r-- | src/components/Items.svelte | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/src/components/Items.svelte b/src/components/Items.svelte new file mode 100644 index 00000000..f449215c --- /dev/null +++ b/src/components/Items.svelte @@ -0,0 +1,209 @@ +<script> + import { btc, f, sat, post } from "$lib/utils"; + import { browser } from "$app/environment"; + import { goto, invalidate } from "$app/navigation"; + import { fade } from "svelte/transition"; + import { cubicIn } from "svelte/easing"; + import { flip } from "svelte/animate"; + import { + dndzone, + SHADOW_ITEM_MARKER_PROPERTY_NAME, + SOURCES, + TRIGGERS, + } from "svelte-dnd-action"; + import DeleteItem from "$comp/DeleteItem.svelte"; + import Icon from "$comp/Icon.svelte"; + + export let subject, user, items, total, rate, currency; + + let flipDurationMs = 200; + let dropTargetStyle = {}; + + let dragDisabled = + subject?.id !== user?.id || (browser && window.innerWidth < 768); + + let deleting; + let del = (item) => { + deleting = item; + }; + + function handleConsider(e) { + const { + items: newItems, + info: { source, trigger }, + } = e.detail; + items = newItems; + if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED) + reset(); + } + + async function handleFinalize(e) { + const { + items: newItems, + info: { source }, + } = e.detail; + items = newItems; + if (source === SOURCES.POINTER) reset(); + + if (subject?.id === user?.id) { + await post("/api/items/sort", { items }); + invalidate("app:items"); + } + } + + function startDrag(e) { + e.preventDefault(); + dragDisabled = false; + } + + function handleKeyDown(e) { + if ((e.key === "Enter" || e.key === " ") && dragDisabled) + dragDisabled = false; + } + + $: if (total > 0 && browser) { + let payButton = document.querySelector("#payButton"); + if (payButton) payButton.addEventListener("click", checkout); + } + + let checkout = async () => { + let invoice = { + type: "lightning", + amount: btc(total, rate), + currency, + items: items.filter((i) => i.quantity > 0), + prompt: subject.prompt, + memoPrompt: subject.memoPrompt, + rate, + }; + + let { id } = await post(`/${subject.username}/invoice`, { + invoice, + user: { username: subject.username }, + }); + + if (invoice.memoPrompt) { + return goto(`/${subject.username}/invoice/${id}/memo`); + } + goto(`/${subject.username}/invoice/${id}`); + }; + + let reset = () => + (dragDisabled = + user?.username !== subject?.username || window.innerWidth < 768); +</script> + +<svelte:window on:resize={reset} /> + +{#if deleting} + <DeleteItem bind:item={deleting} /> +{/if} + +{#if items.length} + <h2 class="text-2xl font-bold">Select items</h2> +{/if} +<div + class="grid sm:grid-cols-2 gap-4 pb-20" + use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }} + on:consider={handleConsider} + on:finalize={handleFinalize} +> + {#each items as i (i.id)} + <button + type="button" + class="w-full text-center cursor-pointer hover:opacity-80 mb-auto" + on:click={() => i.quantity++} + > + <div class="relative w-full h-64 overflow-hidden rounded-xl"> + {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]} + <div + in:fade={{ duration: 200, easing: cubicIn }} + class="custom-shadow-item" + /> + {:else if i.image} + <img + src={`/api/public/${i.image}.webp`} + alt={i.name} + class="mx-auto object-cover h-full w-full" + /> + {:else} + <div + class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full" + alt="Banner" + /> + {/if} + {#if user?.username === subject.username} + <div class="flex gap-2 justify-center absolute top-2 right-2"> + <button type="button" on:click|stopPropagation={() => {}}> + <a href={`/${user.username}/items/${i.id}`}> + <button + class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" + > + <Icon icon="edit" style="w-8 mx-auto invert" /> + </button> + </a> + </button> + <button + class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" + on:click|stopPropagation={() => del(i)} + > + <Icon icon="trash" style="w-8 mx-auto invert" /> + </button> + <button + type="button" + on:click|stopPropagation={() => {}} + class="sm:hidden" + on:mousedown={startDrag} + on:touchstart={startDrag} + on:keydown={handleKeyDown} + > + <a href={`/${user.username}/items/${i.id}`}> + <button + class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" + > + <Icon icon="move" style="w-8 mx-auto invert" /> + </button> + </a> + </button> + </div> + {/if} + </div> + <div class="bg-white rounded-xl py-2"> + <div class="flex text-left gap-2"> + <div class="overflow-hidden space-y-1"> + <div class="text-2xl break-words">{i.name}</div> + <div class="flex gap-2"> + <div class="font-semibold text-lg"> + {f(i.price, currency)} + </div> + <div class="text-secondary my-auto"> + {sat(btc(i.price, rate))} + </div> + </div> + </div> + <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums"> + {#if i.quantity} + <button + type="button" + on:click|stopPropagation={() => i.quantity > 0 && i.quantity--} + > + <Icon icon="minus" style="w-10 min-w-10" /> + </button> + {/if} + <div class="text-2xl my-auto text-center"> + {#if i.quantity}{i.quantity}{:else}—{/if} + </div> + {#if i.quantity} + <button + type="button" + on:click|stopPropagation={() => i.quantity++} + > + <Icon icon="plus" style="w-10 min-w-10" /> + </button> + {/if} + </div> + </div> + </div> + </button> + {/each} +</div> |
