diff options
| author | Adam Soltys <[email protected]> | 2022-11-02 19:47:29 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2022-11-02 19:48:18 -0700 |
| commit | f0d88333750a2865d3016e92620a87269160effa (patch) | |
| tree | 1cc276efeb5287e1fc176ec60d46aed091ab08ca /src/routes/(app)/invoice | |
| parent | b66477f0912349ef91adaff8c57ac787b5a56447 (diff) | |
| download | coinos-ui-f0d88333750a2865d3016e92620a87269160effa.tar.gz coinos-ui-f0d88333750a2865d3016e92620a87269160effa.zip | |
move to app layout
Diffstat (limited to 'src/routes/(app)/invoice')
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+layout.server.js | 14 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+layout.svelte | 1 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+page.server.js | 5 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+page.svelte | 134 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/paid/+page.svelte | 40 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/tip/+page.server.js | 5 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/tip/+page.svelte | 251 |
7 files changed, 450 insertions, 0 deletions
diff --git a/src/routes/(app)/invoice/[id]/+layout.server.js b/src/routes/(app)/invoice/[id]/+layout.server.js new file mode 100644 index 00000000..690deda0 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+layout.server.js @@ -0,0 +1,14 @@ +import Qr from 'qrcode-svg'; +import { get } from '$lib/utils'; + +export async function load({ params }) { + let { id } = params; + let invoice = await get(`/invoice?uuid=${id}`); + let svg = new Qr({ + content: invoice.text, + join: true, + container: 'svg-viewbox' + }).svg(); + + return { id, invoice, svg }; +} 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.js b/src/routes/(app)/invoice/[id]/+page.server.js new file mode 100644 index 00000000..1068f273 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+page.server.js @@ -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..bb41a116 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/+page.svelte @@ -0,0 +1,134 @@ +<script> + import { send } from '$lib/socket'; + import { back, copy, f, get, reverseFormat, s, sats } from '$lib/utils'; + import { onMount, onDestroy } from 'svelte'; + import { browser } from '$app/environment'; + import { invoices, last } from '$lib/store'; + import { Icon, Heart, Image, Qr } from '$comp'; + import { goto } from '$app/navigation'; + import { t } from '$lib/translations'; + import screenfull from 'screenfull'; + + export let data; + $: refresh(data); + let { invoice, id, user, svg } = data; + let { + amount, + rate, + received, + prompt, + text, + tip, + user: { username, currency } + } = invoice; + + let qr; + let tipPercent = 0; + + let refresh = (data) => { + ({ invoice, id } = data); + ({ + amount, + rate, + received, + prompt, + text, + tip, + user: { username, currency } + } = invoice); + + tipPercent = (tip / amount) * 100; + }; + + $invoices[id] = { amount, id, rate, received, text, tip, username }; + $: browser && + $invoices[id]?.received >= $invoices[id]?.amount && + goto(($invoices[id].memo === 'launch' && '/launch/purchase') || `/invoice/${id}/paid`); + + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $: tipAmount = ((tip * rate) / sats).toFixed(2); + + let full = () => { + screenfull.toggle(qr); + let el = document.getElementById('qr'); + el.style.width = '300px'; + }; + + let subbed; + onMount(() => { + browser && + last.subscribe((v) => { + if (!v || subbed) return; + subbed = true; + send('subscribe', invoice); + }); + }); +</script> + +<button + class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" + on:click={back} +> + <Icon icon="arrow-left" style="w-10" /> +</button> + +<div class="container mx-auto max-w-lg px-4 space-y-10 lg:space-y-0"> + <div class="space-y-10 lg:space-y-0"> + <div class="text-center"> + <span class="text-secondary block text-2xl break-all" + >Scan to pay <a href="/{username}" class="text-black font-semibold hover:opacity-80" + >{username}</a + ></span + > + + <div id="qr" class="w-80 mx-auto" bind:this={qr} on:click={full}> + <div class="max-w-lg mx-auto"> + {@html svg} + </div> + </div> + + <div class="mb-10"> + <button + class="flex rounded-full border py-2 px-5 font-bold hover:opacity-80 mb-2 mx-auto" + on:click={() => copy(text)} + ><Icon icon="copy" style="mr-1" /> + <div>Copy</div></button + > + </div> + + <div class="px-5 space-y-3 w-80 mx-auto"> + {#if invoice.tip} + <div class="flex justify-between"> + <span class="font-semibold text-sm">{$t('invoice.invoice')}</span> + <span class="font-semibold text-sm" + >{f(amountFiat, currency)} + <span class="text-secondary font-normal">{`(${s(amount)} SAT)`}</span></span + > + </div> + + <div class="flex justify-between"> + <span class="font-semibold text-sm">{$t('invoice.tip')}</span> + <span class="font-semibold text-sm" + >{f(tipAmount, currency)} + <span class="text-secondary font-normal">{`(${s(tip)} SAT)`}</span></span + > + </div> + {/if} + + <div class="flex flex-wrap justify-between"> + <span class="font-bold mr-1">{$t('invoice.total')}</span> + <span class="font-bold" + >{f(amountFiat + parseFloat(tipAmount), currency)} + <span class="text-secondary font-normal">{`(${s(tip + amount)} SAT)`}</span></span + > + </div> + </div> + </div> + </div> +</div> + +<style> + .full-shadow { + box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.25); + } +</style> 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..d619b7ff --- /dev/null +++ b/src/routes/(app)/invoice/[id]/paid/+page.svelte @@ -0,0 +1,40 @@ +<script> + import { scale } from 'svelte/transition'; + import { Icon } from '$comp'; + import { toast } from '@zerodevx/svelte-toast'; + import { browser } from '$app/environment'; + import { t } from '$lib/translations'; + import { f, s, sats } from '$lib/utils'; + import { goto } from '$app/navigation'; + import { invoices } from '$lib/store'; + + export let data; + let { invoice } = data; + let { amount, rate } = invoice; + + toast.pop(0); + + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + + const handleDoneClick = () => { + delete $invoices[invoice.uuid]; + goto(`/${invoice.user.username}/receive`); + }; +</script> + +<div class="container px-4 text-center mx-auto"> + <div class="flex w-full py-20 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(amountFiat, invoice.currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">({s(invoice.amount)} SAT)</h3> + <button + class="bg-black text-white rounded-2xl w-20 py-3 font-bold hover:opacity-80" + on:click={handleDoneClick} + > + {$t('invoice.done')} + </button> +</div> diff --git a/src/routes/(app)/invoice/[id]/tip/+page.server.js b/src/routes/(app)/invoice/[id]/tip/+page.server.js new file mode 100644 index 00000000..1068f273 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/tip/+page.server.js @@ -0,0 +1,5 @@ +import invoice from '$lib/invoice'; + +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..6ea3aae5 --- /dev/null +++ b/src/routes/(app)/invoice/[id]/tip/+page.svelte @@ -0,0 +1,251 @@ +<script> + import { browser } from '$app/environment'; + import { invoices } from '$lib/store'; + import { back, copy, f, get, reverseFormat, s, sats } from '$lib/utils'; + import { Icon } from '$comp'; + import { goto } from '$app/navigation'; + import { t } from '$lib/translations'; + import { enhance } from '$app/forms'; + import { tick } from 'svelte'; + + const tipAmounts = ['None', '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; + + clearTimeout(timeout); + timeout = setTimeout(update, 800); + }; + + let submitting; + const handleTipButtonClick = async (amount) => { + submitting = true; + if (amount === 'None') { + tipPercent = 0; + } else { + tipPercent = parseInt(amount.slice(0, 2)); + } + + await tick(); + submit.click(); + }; + + let submit; + let update = async () => { + await tick(); + //submit.click(); + }; + + export let data; + $: refresh(data); + let { invoice, id, user } = data; + let { + amount, + rate, + received, + prompt, + text, + tip, + user: { username, currency } + } = invoice; + + let qr; + let tipPercent = 0; + + let fullscreen; + + let refresh = (data) => { + ({ invoice, id } = data); + ({ + amount, + rate, + received, + prompt, + text, + tip, + user: { username, currency } + } = invoice); + + tipPercent = (tip / amount) * 100; + }; + + $: tip = Math.round((amount / 100) * tipPercent); + + $invoices[id] = { amount, id, rate, received, text, tip, username }; + $: browser && + $invoices[id]?.received >= $invoices[id]?.amount && + goto(($invoices[id].memo === 'launch' && '/launch/purchase') || `/invoice/${id}/paid`); + + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + + $: tipAmount = ((tip * rate) / sats).toFixed(2); + + $: invoiceAmountFiatFormatted = f(amountFiat, currency); +</script> + +<button + class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" + on:click={back} +> + <Icon icon="arrow-left" style="w-10" /> +</button> + +<div class="container px-4 max-w-lg text-center mx-auto"> + <form method="POST" use:enhance class:invisible={submitting}> + <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={rate} /> + <input type="hidden" name="prompt" value="false" /> + + <h1 class="text-4xl font-semibold my-8">{$t('invoice.addTipq')}</h1> + {#if !showCustomAmount} + <div class="my-2 text-xl">{Math.round(tipPercent)}%</div> + + <input + id="slider" + type="range" + min="0" + max="100" + value={tipPercent} + class="px-0 py-0 my-5" + style="--bgPercent: {tipPercent}%;" + on:input={handleSlide} + /> + + <div class="flex mb-8 text-sm"> + <div> + <span class="mr-1"> + {f(amountFiat, currency)} + <span class="font-semibold">+{f(tipAmount, currency)}</span> + </span> + </div> + + <div class="ml-auto"> + {s(amount)} + <span class="font-semibold">+{s(tip)} SAT</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" + autofocus + /> + <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> + #slider { + -webkit-appearance: none; + width: 100%; + height: 6px; + border-radius: 5px; + background: linear-gradient( + to right, + black 0%, + black var(--bgPercent), + #f5f7fa var(--bgPercent), + #f5f7fa 100% + ); + outline: none; + } + + #slider:active { + background-color: black; + } + + #slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 30px; + height: 30px; + border-radius: 50%; + background: black; + cursor: pointer; + } + + #slider::-moz-range-thumb { + width: 30px; + height: 30px; + border-radius: 50%; + background: black; + cursor: pointer; + } + + .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> |
