diff options
| author | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
| commit | d49d4119811dee9270f0858f3f03e5d8cd0d95ba (patch) | |
| tree | 42101a3ccf47f7b7290637700b90a843476e56f7 /src/routes/(app)/invoice | |
| parent | 20c0c52f8b850bfbab490735b0b5cd5d08851488 (diff) | |
| parent | 9531ccbdf01b07c392b1b5cb6d8ad4680ee29d2c (diff) | |
| download | coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.tar.gz coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.zip | |
Merge branch 'self-custody'
Diffstat (limited to 'src/routes/(app)/invoice')
| -rw-r--r-- | src/routes/(app)/invoice/+page.server.ts | 28 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/+page.svelte | 0 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/+server.ts | 7 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+layout.server.ts | 38 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+layout.svelte | 1 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+page.server.ts | 5 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+page.svelte | 344 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/memo/+page.server.ts | 13 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/memo/+page.svelte | 95 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/paid/+page.svelte | 85 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/tip/+page.server.ts | 12 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/tip/+page.svelte | 212 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/sent/+page.svelte | 11 |
13 files changed, 851 insertions, 0 deletions
diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts new file mode 100644 index 00000000..b8a2fe06 --- /dev/null +++ b/src/routes/(app)/invoice/+page.server.ts @@ -0,0 +1,28 @@ +import { auth, post } from "$lib/utils"; +import { error, redirect } from "@sveltejs/kit"; +import type { Invoice } from "$lib/types"; + +export let load = async ({ cookies, parent }) => { + let aid = cookies.get("aid"); + let { subject, user, rates } = await parent(); + + let invoice: Invoice = { + aid, + rate: rates[user?.currency || subject?.currency], + }; + + if (!user) user = subject; + + try { + invoice = await post("/invoice", { invoice, aid, user }, auth(cookies)); + } catch (e) { + console.log(e); + error(500, "Failed to generate invoice"); + } + + let { id } = invoice; + + if (invoice.memoPrompt && !invoice.memo) { + redirect(307, `/invoice/${id}/memo`); + } else redirect(307, `/invoice/${id}`); +}; diff --git a/src/routes/(app)/invoice/+page.svelte b/src/routes/(app)/invoice/+page.svelte new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/routes/(app)/invoice/+page.svelte diff --git a/src/routes/(app)/invoice/+server.ts b/src/routes/(app)/invoice/+server.ts new file mode 100644 index 00000000..5e3d0300 --- /dev/null +++ b/src/routes/(app)/invoice/+server.ts @@ -0,0 +1,7 @@ +import { auth, post } from "$lib/utils"; +import { json } from "@sveltejs/kit"; + +export let POST = async ({ cookies: c, request: r }) => { + let j = await r.json(); + return json(await post("/invoice", j, auth(c))); +}; diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts new file mode 100644 index 00000000..55c44f36 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+layout.server.ts @@ -0,0 +1,38 @@ +import * as Qr from "qrcode-base64"; +import { get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ depends, params, url, parent }) { + depends("app:invoice"); + + let { subject, user } = await parent(); + let { id } = params; + let invoice; + + if (id) { + invoice = await get(`/invoice/${id}`); + let options = !!url.searchParams.get("options"); + + if ( + user && + invoice.uid !== user.id && + !(url.pathname.includes("tip") || url.pathname.includes("memo")) && + !options + ) { + redirect(307, `/send/invoice/${id}`); + } + + let { amount, pending, received } = invoice; + amount = parseInt(amount); + + let paid = + (!amount && (pending || received)) || + (amount > 0 && (pending >= amount || received >= amount)); + if (paid && !url.pathname.endsWith("paid")) { + redirect(307, `/invoice/${id}/paid` + (options ? "?options=true" : "")); + } + } + + let src = Qr.drawImg(invoice?.text || "", { size: 500 }); + return { id, invoice, subject, src }; +} diff --git a/src/routes/(app)/invoice/[id]/+layout.svelte b/src/routes/(app)/invoice/[id]/+layout.svelte new file mode 100644 index 00000000..4fa864ce --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+layout.svelte @@ -0,0 +1 @@ +<slot /> diff --git a/src/routes/(app)/invoice/[id]/+page.server.ts b/src/routes/(app)/invoice/[id]/+page.server.ts new file mode 100644 index 00000000..a57efa64 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+page.server.ts @@ -0,0 +1,5 @@ +import invoice from "$lib/invoice"; + +export const actions = { + default: invoice, +}; diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte new file mode 100644 index 00000000..5ef9e5e5 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+page.svelte @@ -0,0 +1,344 @@ +<script> + import { enhance } from "$app/forms"; + import { send } from "$lib/socket"; + import { + btc, + post, + back, + copy, + f, + get, + types, + sat, + reverseFormat, + s, + sats, + } from "$lib/utils"; + import { tick, onMount, onDestroy } from "svelte"; + import { browser } from "$app/environment"; + import { last, showQr, amountPrompt } from "$lib/store"; + import Avatar from "$comp/Avatar.svelte"; + import Icon from "$comp/Icon.svelte"; + import Heart from "$comp/Heart.svelte"; + import Image from "$comp/Image.svelte"; + import Numpad from "$comp/Numpad.svelte"; + import { t } from "$lib/translations"; + import { goto, invalidate } from "$app/navigation"; + import Toggle from "$comp/Toggle.svelte"; + + export let data; + + let showOptions; + + let reading = async ({ message, serialNumber }) => { + let name = serialNumber.replace(/:/g, "-"); + let result = await post(`/fund/${name}/withdraw`, { amount, hash, name }); + }; + + let readingerror = (e) => console.log("nfc error", e); + + $: refresh(data); + let { invoice, id, subject, user, src } = data; + let { + aid, + amount, + hash, + type, + rate, + text, + tip, + user: { username, currency }, + } = invoice; + + let qr; + let tipPercent = 0; + + let refresh = async (data) => { + ({ invoice, id, user, src } = data); + ({ + aid, + amount, + hash, + type, + rate, + text, + tip, + user: { username, currency }, + } = invoice); + + tipPercent = (tip / amount) * 100; + }; + + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $: tipAmount = ((tip * rate) / sats).toFixed(2); + + let subbed; + + onMount(async () => { + if (browser) { + last.subscribe((v) => { + if (!v || subbed) return; + subbed = true; + send("subscribe", invoice); + }); + + if ($amountPrompt && tip === null) toggleAmount(); + } + }); + + $: link = [types.bitcoin, types.liquid].includes(type) + ? text + : `lightning:${text}`; + $: txt = [types.bitcoin, types.liquid].includes(type) ? hash : text; + + let setType = async (type) => { + invoice.type = type; + ({ id } = await post(`/invoice`, { + invoice, + user: { username, currency }, + })); + + goto(`./${id}?options=true`, { invalidateAll: true, noScroll: true }); + }; + + let setAmount = async () => { + if (typeof $amountPrompt === "undefined") $amountPrompt = true; + settingAmount = false; + amount = newAmount; + invoice.amount = newAmount; + invoice.tip = 0; + ({ id } = await post(`/invoice`, { + invoice, + user: { username, currency }, + })); + + let url = `./${id}`; + if (subject?.prompt) url += "/tip"; + else url += "?options=true"; + + goto(url, { invalidateAll: true, noScroll: true }); + }; + + let newAmount; + let settingAmount; + let toggleAmount = () => (settingAmount = !settingAmount); + let fiat; + let submit; +</script> + +<div class="invoice container mx-auto max-w-xl px-4 space-y-2"> + {#if $showQr} + <div> + <a + href={[types.bitcoin, types.liquid].includes(invoice.type) + ? invoice.text + : "lightning:" + invoice.text} + > + <img + {src} + class="mx-auto z-10 max-w-[360px]" + bind:this={qr} + alt={txt} + /> + </a> + </div> + {:else} + <div class="break-all pb-5 text-center text-secondary text-xl"> + {txt} + </div> + {/if} + + {#each invoice.items as i} + <div class="grid grid-cols-12 text-xl"> + <div class="col-span-1 my-auto">{i.quantity}</div> + <div class="mr-auto grow col-span-7 my-auto"> + {i.name} + </div> + <div class="col-span-2 font-semibold text-right my-auto"> + {f(i.price * i.quantity, currency)} + </div> + <div class="col-span-2 text-secondary text-right text-lg my-auto"> + {sat(btc(i.price * i.quantity, rate))} + </div> + </div> + {/each} + + {#if amount > 0} + <div class="text-center font-bold text-2xl"> + <div> + {f(amountFiat, currency)} + + {#if tip} + <span class="text-sm"> + +{f(tip * (rate / sats), currency)} + </span> + {/if} + </div> + <div> + <span class="text-secondary font-normal text-xl" + >⚡️{`${s(amount)}`}</span + > + + {#if tip} + <span class="text-sm text-secondary font-normal"> + +⚡️{s(tip)} + </span> + {/if} + </div> + </div> + {/if} + + <div class="text-secondary space-y-2 text-xl pt-2"> + <button + type="button" + class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2" + on:click={() => copy(txt)} + > + <Icon icon="copy" style="w-8 my-auto" /> + <div class="my-auto"> + {$t("payments.copy")} + </div> + </button> + + {#if user?.id === invoice?.user?.id} + <button + class="w-full flex justify-center rounded-2xl border py-5 px-6 hover:opacity-80" + on:click={toggleAmount} + > + <Icon icon="edit" style="mr-1 w-8" /> + <div class="my-auto">{$t("payments.setAmount")}</div> + </button> + {:else} + <a href={link} class="w-full"> + <button + class="w-full flex justify-center rounded-2xl border py-5 px-6 hover:opacity-80" + > + <Icon icon="mobile" style="mr-1 w-8" /> + <div class="my-auto">{$t("payments.openLink")}</div> + </button> + </a> + {/if} + + <div class="w-full flex justify-center gap-2 flex-wrap"> + <button + class="w-full flex justify-center rounded-2xl border py-5 px-6 hover:opacity-80" + on:click={() => ($showQr = !$showQr)} + > + {#if $showQr} + <Icon icon="text" style="w-6 mr-2 my-auto" /> + {:else} + <Icon icon="qr" style="w-8 mr-1 invert my-auto" /> + {/if} + <div class="my-auto"> + {$showQr ? $t("payments.showText") : $t("payments.showQr")} + </div></button + > + </div> + </div> + + {#if aid === user.id} + <div class="grid grid-cols-2 justify-around text-secondary"> + <button + class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + class:bg-primary={type === types.bitcoin} + class:text-black={type === types.bitcoin} + on:click={() => setType(types.bitcoin)} + > + <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> + <div class="my-auto text-lg">Bitcoin</div> + </button> + + <button + class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + class:bg-primary={type === types.liquid} + class:text-black={type === types.liquid} + on:click={() => setType(types.liquid)} + > + <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> + <div class="my-auto text-lg">Liquid</div> + </button> + + <button + class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + class:bg-primary={type === types.lightning} + class:text-black={type === types.lightning} + on:click={() => setType(types.lightning)} + > + <div class="bg-white rounded-full w-8 h-8 text-center flex"> + <div class="m-auto">⚡️</div> + </div> + <div class="my-auto text-lg">Lightning</div> + </button> + + {#if user?.id === invoice?.user?.id} + <a href={`/receive`} class="block contents"> + <button + class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center" + > + <img src="/images/cash.png" class="w-8 my-auto" /> + <div class="my-auto text-lg">Ecash</div> + </button> + </a> + {/if} + </div> + + {#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.dontDeposit")} + </div> + </div> + {/if} + {/if} +</div> + +{#if settingAmount} + <div + class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50" + > + <div + class="relative mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5 top-10" + > + <form on:submit|preventDefault={setAmount}> + <Numpad + bind:amount={newAmount} + bind:currency + bind:rate + bind:fiat + bind:submit + /> + <div class="w-full flex flex-wrap gap-2"> + <button + bind:this={submit} + type="submit" + on:click={setAmount} + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto bg-black text-white w-full" + > + <div class="my-auto">Ok</div> + </button> + <button + type="button" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto w-full" + on:click={toggleAmount} + on:keydown={toggleAmount} + > + <div class="my-auto">Cancel</div> + </button> + </div> + </form> + + <div class="flex justify-center gap-3"> + <div class="text-secondary">{$t("payments.amountPrompt")}</div> + <Toggle id="notify" bind:value={$amountPrompt} /> + </div> + </div> + </div> +{/if} + +<style> + .invoice { + view-transition-name: invoice; + } +</style> diff --git a/src/routes/(app)/invoice/[id]/memo/+page.server.ts b/src/routes/(app)/invoice/[id]/memo/+page.server.ts new file mode 100644 index 00000000..24dec1af --- /dev/null +++ b/src/routes/(app)/invoice/[id]/memo/+page.server.ts @@ -0,0 +1,13 @@ +import invoice from "$lib/invoice"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ parent, params }) { + let { invoice } = await parent(); + if (!invoice.amount) { + redirect(307, `/invoice/${invoice.id}`); + } +} + +export const actions = { + default: invoice, +}; diff --git a/src/routes/(app)/invoice/[id]/memo/+page.svelte b/src/routes/(app)/invoice/[id]/memo/+page.svelte new file mode 100644 index 00000000..4d23ff19 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/memo/+page.svelte @@ -0,0 +1,95 @@ +<script> + import { onMount } from "svelte"; + import { browser } from "$app/environment"; + import { invoice as inv, request } from "$lib/store"; + import { + copy, + select, + f, + sat, + get, + reverseFormat, + s, + sats, + } from "$lib/utils"; + import Icon from "$comp/Icon.svelte"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + + let submitting; + let submit; + + export let data; + $: refresh(data); + let { invoice, id, user, rates } = data; + let { + amount, + hash, + items, + memoPrompt, + type, + rate, + received, + prompt, + text, + tip, + user: { username, currency }, + } = invoice; + + invoice.memo = user?.username; + + let refresh = (data) => { + $inv = null; + + ({ invoice, id } = data); + ({ + amount, + hash, + items, + memoPrompt, + type, + received, + prompt, + text, + tip, + user: { username }, + } = invoice); + }; +</script> + +<form + method="POST" + use:enhance + class:invisible={submitting} + class="container px-4 max-w-lg mx-auto space-y-5" +> + <input type="hidden" name="amount" value={amount} /> + <input type="hidden" name="tip" value={tip} /> + <input type="hidden" name="username" value={username} /> + <input type="hidden" name="rate" value={invoice.rate} /> + <input type="hidden" name="memoPrompt" value={invoice.memoPrompt} /> + <input type="hidden" name="prompt" value={invoice.prompt} /> + <input type="hidden" name="type" value={type} /> + <input type="hidden" name="hash" value={hash} /> + <input type="hidden" name="items" value={JSON.stringify(items)} /> + + <h1 class="text-4xl font-semibold text-center">{$t("invoice.addMemo")}</h1> + <div class="space-y-2"> + <input + use:select + name="memo" + class="w-full p-4 border rounded-xl text-xl" + bind:value={invoice.memo} + autocapitalize="none" + /> + <div class="text-secondary">{invoice.memoPrompt}</div> + </div> + + <button + type="submit" + bind:this={submit} + class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold" + > + {$t("payments.next")} + </button> +</form> diff --git a/src/routes/(app)/invoice/[id]/paid/+page.svelte b/src/routes/(app)/invoice/[id]/paid/+page.svelte new file mode 100644 index 00000000..e4279e48 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/paid/+page.svelte @@ -0,0 +1,85 @@ +<script> + import { scale } from "svelte/transition"; + import Icon from "$comp/Icon.svelte"; + import { toast } from "@zerodevx/svelte-toast"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { fiat, f, s, sat, sats } from "$lib/utils"; + + export let data; + let amount, currency, rate, received, pending, tip, user, id; + $: update(data); + let update = (data) => { + ({ amount, currency, rate, received, pending, tip, user, id } = + data.invoice); + }; + + toast.pop(0); +</script> + +<div class="container px-4 text-center mx-auto"> + {#if received} + <div + class="flex w-full py-5 max-w-[200px] mx-auto" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + + <h1 class="text-3xl md:text-4xl font-bold mb-6"> + {$t("invoice.paymentSuccessful")} + </h1> + + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(received - tip, rate), currency)} + {#if tip} + <span class="text-lg"> + + {f(fiat(tip, rate), currency)} + </span> + {/if} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(received - tip)} + + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> + {/if} + + {#if pending} + <div + class="flex w-full py-5 max-w-[200px] mx-auto" + in:scale={{ start: 0.5 }} + > + <Icon icon="orangeclock" style="mx-auto" /> + </div> + + <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment detected</h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(pending - tip, rate), currency)} + {#if tip} + + {f(fiat(tip, rate), currency)} + {/if} + </h2> + + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(pending - tip)} + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> + {/if} +</div> + +<a href={`/${user.username}`}> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> +</a> + +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/invoice/[id]/tip/+page.server.ts new file mode 100644 index 00000000..48ebf970 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/tip/+page.server.ts @@ -0,0 +1,12 @@ +import invoice from "$lib/invoice"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ parent }) { + let { invoice } = await parent(); + if (!invoice.amount) + redirect(307, `/invoice/${invoice.id}`); +} + +export const actions = { + default: invoice, +}; diff --git a/src/routes/(app)/invoice/[id]/tip/+page.svelte b/src/routes/(app)/invoice/[id]/tip/+page.svelte new file mode 100644 index 00000000..da2d041e --- /dev/null +++ b/src/routes/(app)/invoice/[id]/tip/+page.svelte @@ -0,0 +1,212 @@ +<script> + import { onMount } from "svelte"; + import { browser } from "$app/environment"; + import { invoice as inv, request } from "$lib/store"; + import { copy, focus, f, sat, get, reverseFormat, s, sats } from "$lib/utils"; + import Icon from "$comp/Icon.svelte"; + import Slider from "$comp/Slider.svelte"; + import { goto } from "$app/navigation"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { tick } from "svelte"; + + const tipAmounts = ["No", "10%", "15%", "20%"]; + let showCustomAmount; + + let customInput; + let customTipAmount; + + let apply = async () => { + if (customTipAmount > 0) { + tipPercent = (customTipAmount / amountFiat) * 100; + showCustomAmount = false; + } + }; + + let active = (amount) => + !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2)); + + const handleCustomTipAmount = (e) => { + tipPercent = 0; + customTipAmount = e.target.value; + }; + + let timeout; + const handleSlide = (e) => { + customTipAmount = ""; + if (customInput) { + customInput.value = ""; + } + tipPercent = e.target.value; + }; + + let submitting; + const handleTipButtonClick = async (amount) => { + submitting = true; + if (amount === "No") { + tipPercent = 0; + } else { + tipPercent = parseInt(amount.slice(0, 2)); + } + + await tick(); + submit.click(); + }; + + let submit; + + export let data; + $: refresh(data); + let { invoice, id, user, rates } = data; + let { + aid, + amount, + hash, + items, + type, + rate, + received, + prompt, + text, + tip, + user: { username, currency }, + } = invoice; + + let qr; + let tipPercent = 0; + + let fullscreen; + + let refresh = (data) => { + $inv = null; + + ({ invoice, id } = data); + ({ + aid, + amount, + hash, + items, + type, + received, + prompt, + text, + tip, + user: { username }, + } = invoice); + + tipPercent = (tip / amount) * 100; + }; + + $: currency = user?.currency || invoice.currency; + $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]); + + $: tip = Math.round((amount / 100) * tipPercent); + + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + + $: tipAmount = ((tip * rate) / sats).toFixed(2); + + $: invoiceAmountFiatFormatted = f(amountFiat, currency); + + onMount(() => ($request = undefined)); +</script> + +<div class="container px-4 max-w-lg text-center mx-auto"> + <form method="POST" use:enhance class:invisible={submitting}> + <input type="hidden" name="aid" value={aid} /> + <input type="hidden" name="amount" value={amount} /> + <input type="hidden" name="tip" value={tip} /> + <input type="hidden" name="username" value={username} /> + <input type="hidden" name="rate" value={invoice.rate} /> + <input type="hidden" name="prompt" value="false" /> + <input type="hidden" name="type" value={type} /> + <input type="hidden" name="hash" value={hash} /> + <input type="hidden" name="items" value={JSON.stringify(items)} /> + + {#if invoice.memoPrompt} + <input type="hidden" name="memoPrompt" value={invoice.memoPrompt} /> + {/if} + + <h1 class="text-4xl font-semibold my-8">{$t("invoice.addTipq")}</h1> + {#if !showCustomAmount} + <div class="my-2 text-xl">{Math.round(tipPercent)}%</div> + + <Slider handle={handleSlide} bind:value={tipPercent} /> + + <div class="flex mb-8 text-lg"> + <div> + <span class="mr-1"> + {f(amountFiat, currency)} + <span class="font-semibold">+{f(tipAmount, currency)}</span> + </span> + </div> + + <div class="ml-auto text-lg"> + {sat(amount)} + <span class="font-semibold">+{sat(tip)}</span> + </div> + </div> + + {#if tip} + <div> + <button type="button" on:click={() => submit.click()}>Next</button> + <button type="button" on:click={() => (tipPercent = 0)}>Reset</button> + </div> + {:else} + {#each tipAmounts as amount} + <button + type="button" + class:active={active(amount, tipPercent)} + on:click={() => handleTipButtonClick(amount)}>{amount}</button + > + {/each} + {/if} + + <div> + <button type="button" on:click={() => (showCustomAmount = true)} + >Custom</button + > + </div> + {/if} + + <button type="submit" bind:this={submit} /> + </form> + + {#if showCustomAmount} + <form on:submit|preventDefault={apply}> + <div class="relative"> + <span + class="absolute top-[18px] left-5 font-semibold {!customTipAmount + ? 'opacity-50' + : 'opacity-100'}">$</span + > + <input + bind:this={customInput} + type="number" + step="0.01" + on:input={(e) => handleCustomTipAmount(e)} + class="pl-10" + placeholder="Custom amount" + use:focus + /> + <button + type="submit" + class="w-full mt-2 bg-black text-white font-semibold py-3 px-7 rounded-2xl {!customTipAmount + ? 'opacity-50' + : 'opacity-100 hover:opacity-80'}" + disabled={!customTipAmount}>Apply</button + > + </div> + </form> + {/if} +</div> + +<style> + .active { + @apply !bg-black !text-white; + } + + .container button[type="button"] { + @apply w-full mt-2 bg-primary text-black hover:bg-black hover:text-white font-semibold py-5 px-7 rounded-2xl; + } +</style> diff --git a/src/routes/(app)/invoice/sent/+page.svelte b/src/routes/(app)/invoice/sent/+page.svelte new file mode 100644 index 00000000..b95bc85e --- /dev/null +++ b/src/routes/(app)/invoice/sent/+page.svelte @@ -0,0 +1,11 @@ +<script> + import Avatar from "$comp/Avatar.svelte"; + import Icon from "$comp/Icon.svelte"; + import { request } from "$lib/store"; +</script> + +Invoice sent to +<div class="flex"> + <Avatar user={$request.requester} size={12} /> + <h1 class="my-auto"><b>{$request.requester.username}</b> is ready to pay</h1> +</div> |
