diff options
| -rw-r--r-- | src/lib/store.js | 41 | ||||
| -rw-r--r-- | src/lib/utils.js | 1 | ||||
| -rw-r--r-- | src/locales/en.json | 1 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.svelte | 96 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/invoice/[id]/+page.svelte | 126 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/items/create/+page.server.js | 3 | ||||
| -rw-r--r-- | static/icons/copy.svg | 4 | ||||
| -rw-r--r-- | static/icons/mobile.svg | 8 |
8 files changed, 173 insertions, 107 deletions
diff --git a/src/lib/store.js b/src/lib/store.js index c27e7f6c..3bec45b9 100644 --- a/src/lib/store.js +++ b/src/lib/store.js @@ -1,7 +1,7 @@ import { get, writable } from "svelte/store"; import { browser } from "$app/environment"; -const persistSession = (key, defaultValue) => { +let persistSession = (key, defaultValue) => { let s = writable( browser && sessionStorage.getItem(key) && @@ -17,7 +17,7 @@ const persistSession = (key, defaultValue) => { return s; }; -const persistLocal = (key, defaultValue) => { +let persistLocal = (key, defaultValue) => { let s = writable( browser && localStorage.getItem(key) && @@ -38,21 +38,22 @@ const persistLocal = (key, defaultValue) => { return s; }; -export const avatar = writable(); -export const banner = writable(); -export const event = writable(); -export const events = writable({}); -export const invoice = writable(); -export const last = writable(); -export const loginRedirect = writable(); -export const decrypted = persistLocal("decrypted", {}); -export const newPayment = persistLocal("newPayment"); -export const password = writable(); -export const passwordPrompt = writable(); -export const pin = persistSession("pin"); -export const rate = writable(); -export const request = writable(); -export const requestRedirect = writable(); -export const token = persistSession("token"); -export const user = writable(); -export const ndef = writable(); +export let avatar = writable(); +export let banner = writable(); +export let event = writable(); +export let events = writable({}); +export let invoice = writable(); +export let last = writable(); +export let loginRedirect = writable(); +export let decrypted = persistLocal("decrypted", {}); +export let newPayment = persistLocal("newPayment"); +export let password = writable(); +export let passwordPrompt = writable(); +export let pin = persistSession("pin"); +export let rate = writable(); +export let request = writable(); +export let requestRedirect = writable(); +export let token = persistSession("token"); +export let user = writable(); +export let ndef = writable(); + export let showQr = persistLocal("showQr"); diff --git a/src/lib/utils.js b/src/lib/utils.js index d3ad0da3..9f9fc042 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -49,7 +49,6 @@ export let post = async (url, body, headers, timeout = 8000) => { }; let controller = new AbortController(); - console.log(controller, timeout); let id = setTimeout(() => { controller.abort(new Error("Operation timed out")); }, timeout); diff --git a/src/locales/en.json b/src/locales/en.json index 1ad0c931..0915d98c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -285,6 +285,7 @@ } }, "payments": { + "options": "options", "bump": "Bump fee", "networkFee": "Network fee", "platformFee": "Platform fee", diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 95dd41a4..2a0f94bb 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -1,5 +1,5 @@ <script> - import { invalidate } from "$app/navigation"; + import { goto, invalidate } from "$app/navigation"; import { btc, f, sat, post } from "$lib/utils"; import { onMount } from "svelte"; import Icon from "$comp/Icon.svelte"; @@ -11,14 +11,18 @@ import { fade } from "svelte/transition"; import { cubicIn } from "svelte/easing"; import { flip } from "svelte/animate"; - const flipDurationMs = 200; - const dropTargetStyle = {}; export let data; + + let flipDurationMs = 200; + let dropTargetStyle = {}; + let { subject, user, items, rates } = data; + $: ({ currency } = subject); + $: rate = rates[currency]; $: refresh(data); let refresh = (d) => ({ items, subject, user, rates } = d); - $: dragDisabled = subject?.id !== user?.id + $: dragDisabled = subject?.id !== user?.id; let deleting; $: total = items.reduce((a, b) => a + b.price * b.quantity, 0); @@ -26,9 +30,7 @@ deleting = item; }; - let consider = (e) => { - items = e.detail.items; - }; + let consider = (e) => (items = e.detail.items); let finalize = async (e) => { items = e.detail.items; @@ -37,6 +39,25 @@ invalidate("app:items"); } }; + + let checkout = async () => { + let invoice = { + type: "lightning", + amount: btc(total, rate), + currency, + items: items.filter((i) => i.quantity > 0), + rate, + }; + + let { id } = await post(`/${subject.username}/invoice`, { + invoice, + user: { username: subject.username }, + }); + + goto(`/${subject.username}/invoice/${id}?options=true`, { + invalidateAll: true, + }); + }; </script> {#if deleting} @@ -46,7 +67,7 @@ <div class="space-y-5"> {#if user?.id === subject.id} <div class="flex justify-center lg:justify-start"> - <Balance {user} rate={rates[user.currency]} /> + <Balance {user} {rate} /> </div> {#if !user.balance} @@ -62,6 +83,7 @@ > <a href={`/send`} class="w-full"> <button + type="button" class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-primary" > <div class="mx-auto flex"> @@ -103,12 +125,14 @@ <div class="grid grid-cols-1 md:grid-cols-2 gap-4" + class:pb-20={total > 0} use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }} on:consider={consider} on:finalize={finalize} > {#each items as i (i.id)} - <div + <button + type="button" class="w-full text-center cursor-pointer hover:opacity-80" on:click={() => i.quantity++} > @@ -132,7 +156,7 @@ {/if} {#if user?.username === subject.username} <div class="flex gap-2 justify-center absolute top-2 right-2"> - <div on:click|stopPropagation={() => {}}> + <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" @@ -140,7 +164,7 @@ <Icon icon="edit" style="w-8 mx-auto invert" /> </button> </a> - </div> + </button> <button class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100" on:click|stopPropagation={() => del(i)} @@ -156,20 +180,22 @@ <div class="text-2xl break-words">{i.name}</div> <div class="flex gap-2"> <div class="font-semibold"> - {f(i.price, subject.currency)} + {f(i.price, currency)} </div> <div class="text-secondary"> - {sat(btc(i.price, rates[subject.currency]))} + {sat(btc(i.price, rate))} </div> </div> </div> <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums"> {#if i.quantity} - <div - on:click|stopPropagation={() => i.quantity > 0 && i.quantity--} - > - <Icon icon="minus" style="w-10 min-w-10" /> - </div> + <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} @@ -177,30 +203,24 @@ </div> </div> </div> - </div> + </button> {/each} </div> {#if total > 0} - <div> - <a - href={`/send/${subject.username}/${total}/${subject.currency}`} - class="w-full" + <button + class="rounded-2xl py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white fixed bottom-16 w-96 right-0" + on:click={checkout} > - <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full bg-black text-white" - > - <div class="mx-auto flex"> - <div class="my-auto text-2xl"> - {$t("user.dashboard.checkout")} - {f(total, subject.currency)} - <span class="text-base"> - {sat(btc(total, rates[subject.currency]), subject.currency)} - </span> - </div> - </div> - </button> - </a> - </div> + <div class="mx-auto flex"> + <div class="my-auto text-2xl"> + {$t("user.dashboard.checkout")} + {f(total, currency)} + <span class="text-base"> + {sat(btc(total, rate), currency)} + </span> + </div> + </div> + </button> {/if} </div> diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/+page.svelte index cd67744f..3451cf58 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/+page.svelte @@ -2,6 +2,7 @@ import { enhance } from "$app/forms"; import { send } from "$lib/socket"; import { + btc, post, back, copy, @@ -15,7 +16,7 @@ } from "$lib/utils"; import { tick, onMount, onDestroy } from "svelte"; import { browser } from "$app/environment"; - import { last } from "$lib/store"; + import { last, showQr } from "$lib/store"; import Avatar from "$comp/Avatar.svelte"; import Icon from "$comp/Icon.svelte"; import Heart from "$comp/Heart.svelte"; @@ -26,6 +27,7 @@ export let data; let ndef; + let showOptions; let reading = async ({ message, serialNumber }) => { let name = serialNumber.replace(/:/g, "-"); @@ -49,7 +51,6 @@ user: { username, currency }, } = invoice; - let showQr = true; let qr; let tipPercent = 0; @@ -92,6 +93,7 @@ let subbed; onMount(async () => { + $showQr = $showQr && 1 if (browser) { last.subscribe((v) => { if (!v || subbed) return; @@ -125,7 +127,7 @@ </script> <div class="invoice container mx-auto max-w-xl px-4 space-y-2"> - {#if showQr} + {#if $showQr === 1} <div> <a href={[types.bitcoin, types.liquid].includes(invoice.type) @@ -142,6 +144,20 @@ </div> {/if} + {#each invoice.items as i} + <div class="flex tabular-nums"> + <div class="mr-auto"><span class="mr-2">{i.quantity}</span> {i.name}</div> + <div class="flex gap-2"> + <div class="font-semibold"> + {f(i.price * i.quantity, currency)} + </div> + <div class="text-secondary"> + {sat(btc(i.price * i.quantity, rate))} + </div> + </div> + </div> + {/each} + {#if amount > 0} <div class="text-center font-bold text-2xl"> <div> @@ -167,9 +183,55 @@ </div> {/if} - <div class="flex justify-center text-secondary"> + + <button + type="button" + class="flex gap-2 text-center break-all text-lg rounded-2xl hover:opacity-80 p-4 w-full mx-auto justify-center border bg-black text-white text-xl" + on:click={() => copy(txt)} + > + <Icon icon="copy" style="w-8 my-auto invert" /> + <div class="my-auto font-bold"> + {txt.substr(0, 11)}..{txt.substr(-11)} + </div> + </button> + + <div class="w-full flex justify-center gap-2 flex-wrap text-secondary"> + <a href={link} class="w-full"> + <button + class="w-full flex justify-center rounded-2xl border py-3 px-5 hover:opacity-80 text-lg" + > + <Icon icon="mobile" style="mr-1 w-8" /> + <div class="my-auto">{$t("payments.openLink")}</div> + </button> + </a> + + <button + class="w-full flex justify-center rounded-2xl border py-3 px-5 hover:opacity-80 text-lg" + on:click={() => ($showQr = $showQr ? false : 2)} + > + <Icon icon="qr" style="w-8 mr-1 invert my-auto" /> + <div class="my-auto"> + {$showQr ? $t("payments.hide") : $t("payments.show")} + {$t("payments.qr")} + </div></button + > + + <!-- <button --> + <!-- class="w-full flex justify-center rounded-2xl border py-3 px-5 hover:opacity-80 text-lg" --> + <!-- on:click={() => (showOptions = !showOptions)} --> + <!-- > --> + <!-- <Icon icon="settings" style="w-8 mr-1 my-auto" /> --> + <!-- <div class="my-auto"> --> + <!-- {showOptions ? $t("payments.hide") : $t("payments.show")} --> + <!-- {$t("payments.options")} --> + <!-- </div></button --> + <!-- > --> + </div> + + + <div class="flex justify-around text-secondary"> <button - class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 rounded-full" + class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center" class:bg-primary={type === types.bitcoin} class:text-black={type === types.bitcoin} on:click={() => setType(types.bitcoin)} @@ -179,7 +241,7 @@ </button> <button - class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 rounded-full" + class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center" class:bg-primary={type === types.liquid} class:text-black={type === types.liquid} on:click={() => setType(types.liquid)} @@ -189,7 +251,7 @@ </button> <button - class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 rounded-full" + class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center" class:bg-primary={type === types.lightning} class:text-black={type === types.lightning} on:click={() => setType(types.lightning)} @@ -204,45 +266,29 @@ {#if type === types.liquid} <div class="flex justify-center"> <div class="my-auto text-xl text-center text-secondary"> - {$t("payments.onlyLbtc")} <span class="text-teal-500 font-bold">L-BTC</span>, + {$t("payments.onlyLbtc")} + <span class="text-teal-500 font-bold">L-BTC</span>, {$t("payments.dontDeposit")} </div> </div> {/if} - <button - type="button" - class="flex gap-2 text-center break-all text-lg rounded-full hover:opacity-80 p-4 w-full mx-auto justify-center border text-black text-xl" - on:click={() => copy(txt)} - > - <Icon icon="copy" style="w-8 my-auto font-semibold" /> - <div class="my-auto"> - {txt.substr(0, 11)}..{txt.substr(-11)} - </div> - </button> - - - <div class="w-full flex justify-center gap-2 flex-wrap text-secondary"> - <a href={link} class="w-full"> - <button - class="w-full flex justify-center rounded-full border py-3 px-5 hover:opacity-80 text-lg" + {#if $showQr === 2} + <div> + <a + href={[types.bitcoin, types.liquid].includes(invoice.type) + ? invoice.text + : "lightning:" + invoice.text} > - <Icon icon="mobile" style="mr-1 w-6" /> - <div>{$t("payments.openLink")}</div> - </button> - </a> - - <button - class="w-full flex justify-center rounded-full border py-3 px-5 hover:opacity-80 text-lg" - on:click={() => (showQr = !showQr)} - > - <Icon icon="qr" style="mr-1 invert" /> - <div> - {showQr ? $t("payments.hide") : $t("payments.show")} - {$t("payments.qr")} - </div></button - > - </div> + <img + {src} + class="mx-auto z-10 shadow-inner rounded-2xl" + bind:this={qr} + alt={txt} + /> + </a> + </div> + {/if} </div> <style> diff --git a/src/routes/(app)/[username]/items/create/+page.server.js b/src/routes/(app)/[username]/items/create/+page.server.js index 6740f5f9..8cb48399 100644 --- a/src/routes/(app)/[username]/items/create/+page.server.js +++ b/src/routes/(app)/[username]/items/create/+page.server.js @@ -6,10 +6,7 @@ export const actions = { let form = await fd(request); try { await post("/items", form, auth(cookies)); - - console.log("YEAH"); } catch (e) { - console.log("E", e) return fail(400, { message: e.message }); } diff --git a/static/icons/copy.svg b/static/icons/copy.svg index 90375679..6d67a021 100644 --- a/static/icons/copy.svg +++ b/static/icons/copy.svg @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M16 12.9V17.1C16 20.6 14.6 22 11.1 22H6.9C3.4 22 2 20.6 2 17.1V12.9C2 9.4 3.4 8 6.9 8H11.1C14.6 8 16 9.4 16 12.9Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> -<path opacity="0.4" d="M22 6.9V11.1C22 14.6 20.6 16 17.1 16H16V12.9C16 9.4 14.6 8 11.1 8H8V6.9C8 3.4 9.4 2 12.9 2H17.1C20.6 2 22 3.4 22 6.9Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M21 8C21 6.34315 19.6569 5 18 5H10C8.34315 5 7 6.34315 7 8V20C7 21.6569 8.34315 23 10 23H18C19.6569 23 21 21.6569 21 20V8ZM19 8C19 7.44772 18.5523 7 18 7H10C9.44772 7 9 7.44772 9 8V20C9 20.5523 9.44772 21 10 21H18C18.5523 21 19 20.5523 19 20V8Z" fill="#0F0F0F"/> +<path d="M6 3H16C16.5523 3 17 2.55228 17 2C17 1.44772 16.5523 1 16 1H6C4.34315 1 3 2.34315 3 4V18C3 18.5523 3.44772 19 4 19C4.55228 19 5 18.5523 5 18V4C5 3.44772 5.44772 3 6 3Z" fill="#0F0F0F"/> </svg>
\ No newline at end of file diff --git a/static/icons/mobile.svg b/static/icons/mobile.svg index e23e8233..71c82e4c 100644 --- a/static/icons/mobile.svg +++ b/static/icons/mobile.svg @@ -1,3 +1,5 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M12 18H12.01M8 21H16C17.1046 21 18 20.1046 18 19V5C18 3.89543 17.1046 3 16 3H8C6.89543 3 6 3.89543 6 5V19C6 20.1046 6.89543 21 8 21Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> -</svg> +<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> +<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M12 18C11.4477 18 11 18.4477 11 19C11 19.5523 11.4477 20 12 20C12.5523 20 13 19.5523 13 19C13 18.4477 12.5523 18 12 18Z" fill="#0F0F0F"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M19 4C19 2.34315 17.6569 1 16 1H8C6.34315 1 5 2.34315 5 4V20C5 21.6569 6.34315 23 8 23H16C17.6569 23 19 21.6569 19 20V4ZM17 4C17 3.44772 16.5523 3 16 3H8C7.44772 3 7 3.44772 7 4V15H17V4ZM7 20V17H17V20C17 20.5523 16.5523 21 16 21H8C7.44772 21 7 20.5523 7 20Z" fill="#0F0F0F"/> +</svg>
\ No newline at end of file |
