aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/invoice
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2022-11-02 19:47:29 -0700
committerAdam Soltys <[email protected]>2022-11-02 19:48:18 -0700
commitf0d88333750a2865d3016e92620a87269160effa (patch)
tree1cc276efeb5287e1fc176ec60d46aed091ab08ca /src/routes/(app)/invoice
parentb66477f0912349ef91adaff8c57ac787b5a56447 (diff)
downloadcoinos-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.js14
-rw-r--r--src/routes/(app)/invoice/[id]/+layout.svelte1
-rw-r--r--src/routes/(app)/invoice/[id]/+page.server.js5
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte134
-rw-r--r--src/routes/(app)/invoice/[id]/paid/+page.svelte40
-rw-r--r--src/routes/(app)/invoice/[id]/tip/+page.server.js5
-rw-r--r--src/routes/(app)/invoice/[id]/tip/+page.svelte251
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>