diff options
| author | Adam Soltys <[email protected]> | 2023-04-07 14:46:09 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-04-07 14:46:09 -0700 |
| commit | d145b9de59cb20181f6fe48f6aa4677d406cf16d (patch) | |
| tree | 16439dbce4ff7e48c60f57875489c16ea5532a13 /src/routes/(app) | |
| parent | d4de8dce613f5ee49068ce90b868ffda5d4771d7 (diff) | |
| parent | 648d9fb41c5302672822fd76fb1b1a90f654b05b (diff) | |
| download | coinos-ui-d145b9de59cb20181f6fe48f6aa4677d406cf16d.tar.gz coinos-ui-d145b9de59cb20181f6fe48f6aa4677d406cf16d.zip | |
Merge branch 'master' into hashme
Diffstat (limited to 'src/routes/(app)')
34 files changed, 220 insertions, 345 deletions
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 0dcdc1b2..9eb8f18c 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -2,7 +2,7 @@ import { SvelteToast } from '@zerodevx/svelte-toast'; import { onDestroy, onMount } from 'svelte'; import { close, connect, send } from '$lib/socket'; - import { last, selectedRate, invoice, rate, user, request, passwordPrompt } from '$lib/store'; + import { last, invoice, request, passwordPrompt } from '$lib/store'; import { page } from '$app/stores'; import { browser } from '$app/environment'; import { goto } from '$app/navigation'; @@ -15,16 +15,7 @@ export let data; let { subject } = $page.data; - let r, u, token, rates; - - $: update(data); - let update = () => { - ({ user: u, rate: r, token, rates } = data); - $rate = r; - $user = u; - }; - - $: $selectedRate = r * (rates[u?.currency || 'USD'] / rates.USD); + let { rate, user, token, rates } = data; onMount(() => { let localStorageLocale = localStorage.getItem(localeLocalStorageKey); @@ -89,9 +80,9 @@ </main> {#if $invoice} - <Invoice user={u} /> + <Invoice {user} /> {/if} {#if $request} - <Request user={u} /> + <Request {user} /> {/if} diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index bbb2379e..df9b9ad8 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -1,13 +1,13 @@ <script> import { f, s, sats } from '$lib/utils'; - import { animatedRate, selectedRate } from '$lib/store'; + import { animatedRate } from '$lib/store'; import { Icon } from '$comp'; import { t } from '$lib/translations'; import { sign, send } from '$lib/nostr'; export let data; - let events, user, subject, src, text; - $: ({ events, user, subject, src, text } = data); + let events, user, subject, rates, src, text; + $: ({ events, user, rates, subject, src, text } = data); $: ({ currency, username: n, display } = subject); $: username = n.length > 60 ? n.substr(0, 6) : display || n; @@ -42,24 +42,6 @@ $: following = !!user?.follows.find((t) => t.includes(subject.pubkey)); - let ease = (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t); - let i; - - let o = $selectedRate; - $: animate($selectedRate); - let animate = (n) => { - clearInterval(i); - - let t = 0; - let d = o - n; - - i = setInterval(() => { - $animatedRate = (o - d * ease(t / 100)).toFixed(2); - if (t > 80) (o = n) && clearInterval(i); - t++; - }, 10); - }; - let hideBio = true; let toggleBio = () => (hideBio = false); </script> @@ -115,39 +97,31 @@ </button> </div> {/if} + {/if} + {#if !subject.anon && subject.username !== user?.username} <div class="w-full flex"> - <a href={`/${subject.username}/messages`} class="mx-auto"> - <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> - <div class="mx-auto flex"> - <Icon icon="support" style="mr-2 my-auto" /> - <div class="mt-1 my-auto">{$t('user.message')}</div> - </div> - </button> - </a> - </div> - - <div class="w-full flex"> - <a href={`/${subject.username}/request`} class="mx-auto"> + <a + href={user ? `/send/${subject.username}` : `/${subject.username}/receive`} + class="mx-auto" + > <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> <div class="mx-auto flex"> - <Icon icon="numpad" style="mr-2 my-auto" /> - <div class="mt-1 my-auto">{$t('payments.requestAnInvoice')}</div> + <Icon icon="send" style="mr-2" /> + <div class="mt-1">{$t('user.pay')}</div> </div> </button> </a> </div> {/if} - {#if !subject.anon && subject.username !== user?.username} + + {#if user && user.username !== subject.username && subject.pubkey} <div class="w-full flex"> - <a - href={user ? `/send/${subject.username}` : `/${subject.username}/receive`} - class="mx-auto" - > + <a href={`/${subject.username}/messages`} class="mx-auto"> <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> <div class="mx-auto flex"> - <Icon icon="send" style="mr-2" /> - <div class="mt-1">{$t('user.pay')}</div> + <Icon icon="support" style="mr-2 my-auto" /> + <div class="mt-1 my-auto">{$t('user.message')}</div> </div> </button> </a> @@ -163,7 +137,7 @@ </div> </div> -{#if currency && $animatedRate} +{#if currency && !isNaN($animatedRate)} <div class="flex fixed w-full px-4 bg-white py-2 bottom-0 bg-opacity-90 tabular-nums"> <div class="text-secondary flex mr-auto"> <div class="flex mr-1"> diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 09335e9c..308289b0 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -2,24 +2,23 @@ import { onMount } from 'svelte'; import { Icon, Avatar, Balance, Feed } from '$comp'; import { t } from '$lib/translations'; - import { f, sat, sats } from '$lib/utils'; + import { ease, f, sat, sats } from '$lib/utils'; import { sign, send, encrypt, decrypt } from '$lib/nostr'; - import { event as e, password } from '$lib/store'; + import { event as e, password, rate, animatedRate } from '$lib/store'; import { browser } from '$app/environment'; export let data; - let { messages, notes, invoices, sent, received, subject, user } = data; + let { messages, notes, invoices, sent, received, subject, user, rates } = data; $: refresh(data); - let refresh = (d) => ({ messages, notes, invoices, sent, received, subject, user } = d); + let refresh = (d) => ({ messages, notes, invoices, sent, received, subject, user, rates } = d); let keys = new Set(); let latest = []; - let i = 0; let ready; e.subscribe(async (event) => { if (!(event && ready)) return; - if (event.recipient.id === user?.id && !~latest.findIndex((m) => m.id === event.id)) { + if (event.recipient?.id === user.id && !~latest.findIndex((m) => m.id === event.id)) { event.content = await decrypt({ event, user }); let i = latest.findIndex((m) => m.pubkey === event.pubkey); @@ -34,22 +33,49 @@ $: initialize($password); let initialize = async (p) => { + let i = 0; ready = false; - if (!p) return; + if (!p || !user) return; while (i < messages.length) { let event = messages[i]; - let k = event.author.id === user.id ? event.recipient.pubkey : event.author.pubkey; + i++; + + if (!(event.author && event.recipient)) continue; + + let k = event.author.id === user.id ? event.recipient?.pubkey : event.author.pubkey; if (!keys.has(k)) { keys.add(k); event.content = await decrypt({ event, user }); if (event.content) latest.push(event); } - i++; } latest = latest; ready = true; }; + + let i; + let o; + + rate.subscribe((n) => { + if (!user?.currency) return; + clearInterval(i); + + let r = ((rates[user.currency] / rates['USD']) * n).toFixed(2); + + if (!r || isNaN(r)) return; + if (!o) o = r; + + let t = 0; + let d = o - r; + + i = setInterval(() => { + if (!r) return; + $animatedRate = (o - d * ease(t / 100)).toFixed(2); + if (t > 80) (o = r) && clearInterval(i); + t++; + }, 10); + }); </script> <div class="space-y-8"> diff --git a/src/routes/(app)/[username]/dashboard/+page.server.js b/src/routes/(app)/[username]/dashboard/+page.server.js deleted file mode 100644 index ae66a20d..00000000 --- a/src/routes/(app)/[username]/dashboard/+page.server.js +++ /dev/null @@ -1,4 +0,0 @@ -import { redirect } from '@sveltejs/kit'; -export async function load({ params: { username } }) { - throw redirect(301, `/${username}`); -} diff --git a/src/routes/(app)/[username]/dashboard/+page.svelte b/src/routes/(app)/[username]/dashboard/+page.svelte deleted file mode 100644 index d8761ea9..00000000 --- a/src/routes/(app)/[username]/dashboard/+page.svelte +++ /dev/null @@ -1,163 +0,0 @@ -<script> - import { enhance } from '$app/forms'; - import { f, s, post, fail, sat, sats } from '$lib/utils'; - import { tick } from 'svelte'; - import { Avatar, Icon } from '$comp'; - import { rate, password, selectedRate } from '$lib/store'; - import { goto } from '$app/navigation'; - import { t } from '$lib/translations'; - import { events } from '$lib/store'; - - export let data; - $: ({ invoices, requests } = data); - - let ease = (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t); - let interval, rateInterval; - - let oldRate = $selectedRate; - let animatedRate = oldRate; - $: animateRate($selectedRate); - let animateRate = (newRate) => { - if (!accountBalanceFiat) return; - let t = 0; - let newBalance = (user.balance * newRate) / sats; - let oldBalance = parseFloat(accountBalanceFiat); - let diff = oldBalance - newBalance; - let rateDiff = oldRate - newRate; - - clearInterval(rateInterval); - rateInterval = setInterval(() => { - let delta = rateDiff * ease(t / 100); - animatedRate = (oldRate - delta).toFixed(2); - if (t > 80) clearInterval(interval); - t++; - }, 10); - - oldRate = newRate; - - clearInterval(interval); - interval = setInterval(() => { - let delta = diff * ease(t / 100); - accountBalanceFiat = (oldBalance - delta).toFixed(2); - if (t > 80) clearInterval(interval); - t++; - }, 10); - }; - - $: user = data.user; - $: accountBalanceFiat = - accountBalanceFiat || - (user && $selectedRate && ((user.balance * $selectedRate) / sats).toFixed(2)); - - let payreq = '', - payreqField, - loading; - - $: btcPrice = f($selectedRate, user.currency); - - $: accountBalanceBtc = user.balance / sats; -</script> - -<div class="container px-4 max-w-md mx-auto mt-20 space-y-12 mb-8"> - <div class="mt-24 space-y-2"> - <div class="text-5xl font-bold"> - {$selectedRate ? f(accountBalanceFiat, user.currency) : $t('user.dashboard.fetchingRate')} - </div> - - <div class="text-secondary text-2xl"> - ⚡️{s(user.balance)} - </div> - </div> - - <div class="grid sm:grid-cols-2 gap-2 text-lg"> - <a href="/send"> - <button class="rounded-full border py-4 px-5 font-bold hover:opacity-80 w-full" - >{$t('user.dashboard.send')}</button - > - </a> - <a href={`/${user.username}/receive`}> - <button class="rounded-full border py-4 px-5 font-bold hover:opacity-80 w-full" - >{$t('user.dashboard.receive')}</button - > - </a> - </div> - - <!-- <div> --> - <!-- {#each invoices as { request_id, amount, currency, rate, user: recipient, requester, id }} --> - <!-- {@const requested = user.username === requester?.username} --> - <!-- <a href={`/invoice/${id}` + (requested ? '/tip' : '')}> --> - <!-- <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> --> - <!-- <div class="flex gap-2"> --> - <!-- <div> --> - <!-- {#if requested} --> - <!-- <div class="flex"> --> - <!-- <Avatar user={recipient} size={20} /> --> - <!-- <div class="my-auto text-left"> --> - <!-- <p class="text-secondary">{$t('payments.invoiceFrom')}</p> --> - <!-- <p class="ml-1 text-lg break-words">{recipient.username}</p> --> - <!-- </div> --> - <!-- </div> --> - <!-- {:else} --> - <!-- <div class="flex"> --> - <!-- <Avatar user={requester} size={20} /> --> - <!-- <div class="my-auto text-left"> --> - <!-- <p class="text-secondary">{$t('payments.awaiting')}</p> --> - <!-- <p class="ml-1 text-lg break-words">{requester.username}</p> --> - <!-- </div> --> - <!-- </div> --> - <!-- {/if} --> - <!-- </div> --> - <!-- <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> --> - <!-- <div class="font-bold"> --> - <!-- {f(amount * (rate / sats), currency)} --> - <!-- </div> --> - <!-- --> - <!-- <div class="text-secondary">{sat(amount)}</div> --> - <!-- </div> --> - <!-- </div> --> - <!-- </div> --> - <!-- </a> --> - <!-- {/each} --> - <!-- </div> --> - - <!-- <div> --> - <!-- {#each requests as { id, invoice, memo, requester: r }} --> - <!-- <a href={`/${user.username}/receive/${id}`}> --> - <!-- <div class="border-b p-2 last:border-b-0 hover:bg-gray-100"> --> - <!-- <div class="flex"> --> - <!-- <div> --> - <!-- <div class="flex"> --> - <!-- <Avatar user={r} size={20} /> --> - <!-- <div class="my-auto text-left"> --> - <!-- <p class="ml-1 text-lg break-words">{r.username}</p> --> - <!-- <p class="ml-1 text-secondary">{memo}</p> --> - <!-- </div> --> - <!-- </div> --> - <!-- </div> --> - <!-- <div class="whitespace-nowrap my-auto ml-auto flex gap-2"> --> - <!-- <button class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full" --> - <!-- >Invoice</button --> - <!-- > --> - <!-- </div> --> - <!-- </div> --> - <!-- </div> --> - <!-- </a> --> - <!-- {/each} --> - <!-- </div> --> -</div> - -<div class="flex fixed w-full px-4 bg-white py-2 bottom-0 bg-opacity-90"> - <div class="text-secondary flex mr-auto"> - <div class="flex mr-1"> - <div class="my-auto mr-1">1</div> - <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> - </div> - <div>= {f(animatedRate, user.currency)}</div> - </div> - <div class="text-secondary flex ml-auto"> - <div class="flex"> - <div class="mr-1">⚡️{s((1 * sats) / animatedRate)} =</div> - <div>{f(1, user.currency)}</div> - </div> - </div> -</div> diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index 32a38592..3548ccd4 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -1,9 +1,6 @@ <script> import { fail, post, wait } from '$lib/utils'; - import { Buffer } from 'buffer'; import { onMount } from 'svelte'; - import { bech32m } from 'bech32'; - import { generateMnemonic, mnemonicToEntropy, mnemonicToSeedSync } from 'bip39'; import { goto, invalidate } from '$app/navigation'; import { Pin } from '$comp'; import { loginRedirect, pin, password, passwordPrompt } from '$lib/store'; diff --git a/src/routes/(app)/[username]/generate/+server.js b/src/routes/(app)/[username]/generate/+server.js index 1335c3e2..e1df08e3 100644 --- a/src/routes/(app)/[username]/generate/+server.js +++ b/src/routes/(app)/[username]/generate/+server.js @@ -3,7 +3,8 @@ import { error, json } from '@sveltejs/kit'; export async function POST({ cookies, request }) { try { - return json(await post(`/user`, await request.json(), auth(cookies))); + let body = await request.json(); + return json(await post(`/user`, body, auth(cookies))); } catch (e) { throw error(500, e.message); } diff --git a/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js b/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js index 7cf87e14..875c4e11 100644 --- a/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js +++ b/src/routes/(app)/[username]/invoice/[hash]/+layout.server.js @@ -12,10 +12,10 @@ export async function load({ depends, params, url, parent }) { if (user && invoice.uid !== user.id && !url.pathname.includes('tip')) throw redirect(307, `/send/invoice/${hash}`); - let { amount, received } = invoice; + let { amount, pending, received } = invoice; amount = parseInt(amount); - let paid = (!amount && received) || (amount > 0 && received >= amount); + let paid = (!amount && received) || (amount > 0 && (pending >= amount || received >= amount)); if (paid && !url.pathname.endsWith('paid')) throw redirect(307, `/${params.username}/invoice/${hash}/paid`); diff --git a/src/routes/(app)/[username]/invoice/[hash]/paid/+page.svelte b/src/routes/(app)/[username]/invoice/[hash]/paid/+page.svelte index 4d7aae51..c9619cee 100644 --- a/src/routes/(app)/[username]/invoice/[hash]/paid/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[hash]/paid/+page.svelte @@ -21,7 +21,7 @@ {#if received} <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, rate), currency)} + {f(fiat(received - tip, rate), currency)} {#if tip} <span class="text-lg"> + {f(fiat(tip, rate), currency)} @@ -29,7 +29,7 @@ {/if} </h2> <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(received)} + ⚡️{s(received - tip)} {#if tip} <span class="text-lg"> @@ -42,13 +42,13 @@ {#if pending} <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, rate), currency)} + {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)} + ⚡️{s(pending - tip)} {#if tip} <span class="text-lg"> + ⚡️{s(tip)} diff --git a/src/routes/(app)/[username]/invoice/[hash]/tip/+page.svelte b/src/routes/(app)/[username]/invoice/[hash]/tip/+page.svelte index 9d9021a6..fe6c67be 100644 --- a/src/routes/(app)/[username]/invoice/[hash]/tip/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[hash]/tip/+page.svelte @@ -56,7 +56,7 @@ export let data; $: refresh(data); - let { invoice, id, user } = data; + let { invoice, id, user, rates } = data; let { amount, hash, @@ -82,17 +82,19 @@ amount, hash, type, - rate, received, prompt, text, tip, - user: { username, currency } + 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)); @@ -109,7 +111,7 @@ <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="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} /> diff --git a/src/routes/(app)/[username]/messages/+page.server.js b/src/routes/(app)/[username]/messages/+page.server.js index 8347c184..ec5c5eee 100644 --- a/src/routes/(app)/[username]/messages/+page.server.js +++ b/src/routes/(app)/[username]/messages/+page.server.js @@ -8,8 +8,9 @@ export async function load({ params, parent, url }) { try { messages = await get(`/${user.pubkey}/${since}/messages`); + messages = messages - .filter((m) => m.recipient.id === subject.id || m.author.id === subject.id) + .filter((m) => m.recipient?.id === subject.id || m.author?.id === subject.id) .sort((a, b) => a.created_at - b.created_at); if (user.id === subject.id) messages = messages.filter((m) => m.recipient.id === m.author.id); diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte index 7a10d605..cfa7f741 100644 --- a/src/routes/(app)/[username]/messages/+page.svelte +++ b/src/routes/(app)/[username]/messages/+page.svelte @@ -10,7 +10,7 @@ import { sign, send, encrypt, decrypt } from '$lib/nostr'; import { event as e, password } from '$lib/store'; import { tick, onMount } from 'svelte'; - import { calculateId } from 'nostr'; + import { getEventHash } from 'nostr-tools'; export let data; @@ -77,8 +77,8 @@ tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); try { - event.id = await calculateId(event); event.content = await encrypt({ message: event.message, recipient: subject.pubkey, user }); + event.id = getEventHash(event); await sign({ event, user }); await send(event); diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte index c9cc5c40..744b35ec 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte +++ b/src/routes/(app)/[username]/payments/[...page]/+page.svelte @@ -148,19 +148,16 @@ <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> <div class="whitespace-nowrap my-auto"> <div class="font-bold" class:text-red-800={p.amount < 0}> - {f((Math.abs(p.amount) + (p.tip || 0)) * (p.rate / sats), p.currency)} + {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} {#if p.tip} - <span class="text-xs text-secondary"> - {f(Math.abs(p.amount) * (p.rate / sats), p.currency)} + {f( - p.tip * (p.rate / sats), - p.currency - )} + <span class="text-sm text-secondary"> + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% </span> {/if} </div> <div class="text-secondary"> - {sat(p.amount + (p.tip || 0))} + {sat(Math.abs(p.amount) + (p.tip || 0))} </div> </div> diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte index 95495368..27b339aa 100644 --- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte +++ b/src/routes/(app)/[username]/receive/[...param]/+page.svelte @@ -18,9 +18,10 @@ let lightning = true; let { currency, username, prompt } = data.subject; - let { user } = data; + let { rates, user } = data; let submit; + $: rate = rates[currency]; $: type = lightning ? 'lightning' : 'bitcoin'; </script> @@ -37,7 +38,7 @@ <input type="hidden" name="prompt" value={prompt} /> <div class="space-y-3 w-[300px] mx-auto"> - <Numpad bind:amount bind:currency {submit} /> + <Numpad bind:amount bind:currency bind:rate {submit} /> <button bind:this={submit} type="submit" diff --git a/src/routes/(app)/[username]/reset/+server.js b/src/routes/(app)/[username]/reset/+server.js new file mode 100644 index 00000000..52fff367 --- /dev/null +++ b/src/routes/(app)/[username]/reset/+server.js @@ -0,0 +1,10 @@ +import { json } from '@sveltejs/kit'; +import { auth, post } from '$lib/utils'; + +export async function POST({ cookies, request, params }) { + let { username } = params; + let r = await request.json(); + let { password } = r; + let result = await post('/reset', { password, username }, auth(cookies)); + return json(result); +} diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 997d8b4a..1b212abe 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -112,13 +112,10 @@ loading = false; } - - let loaded; - onMount(() => setTimeout(() => (loaded = true), 50)); </script> -{#if loaded && user.haspin && $pin?.length !== 6} - <Pin bind:value={$pin} /> +{#if user.haspin && $pin?.length !== 6} + <Pin /> {/if} <form diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte index eb3b405e..40947bda 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte @@ -148,9 +148,9 @@ {$t('user.settings.securityPINDescription')} </p> {#if verifying} - <Pin bind:value={verify} {cancel} /> + <Pin bind:value={verify} {cancel} notify={false} /> {:else if settingPin} - <Pin bind:value={pin} title={$t('user.settings.setPIN')} {cancel} /> + <Pin bind:value={pin} title={$t('user.settings.setPIN')} {cancel} notify={false} /> {:else} <button type="button" class="primary" on:click={togglePin} ><Icon icon="lock" style="mr-1" /> @@ -192,7 +192,7 @@ {/if} {#if confirming2fa || disabling2fa} - <Pin bind:value={token} title="Enter 2FA Code" {cancel} /> + <Pin bind:value={token} title="Enter 2FA Code" {cancel} persist={false} notify={false} /> {/if} </div> diff --git a/src/routes/(app)/admin/+page.server.js b/src/routes/(app)/admin/+page.server.js index c4079a2f..e19f894c 100644 --- a/src/routes/(app)/admin/+page.server.js +++ b/src/routes/(app)/admin/+page.server.js @@ -1,10 +1,10 @@ -import { get } from '$lib/utils'; +import { auth, get } from '$lib/utils'; import { redirect } from '@sveltejs/kit'; -export async function load({ parent }) { +export async function load({ cookies, parent }) { let { user } = await parent(); if (!user) throw redirect(307, '/login'); - let users = (await get('/users')).sort((a, b) => b.balance - a.balance); + let users = (await get('/users', auth(cookies))).sort((a, b) => b.balance - a.balance); return { users }; } diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte index 1a747f1f..529d2f77 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -1,11 +1,22 @@ <script> + import { post } from '$lib/utils'; export let data; + let reset = (username) => post(`/${username}/reset`, { password }); let { users } = data; + let password; </script> -{#each users as { balance, username }} - <div class="grid grid-cols-2"> +{#each users as { balance, username, expected }} + <div class="grid grid-cols-5"> <div>{username}</div> <div>{balance}</div> + <div>{expected}</div> + <div><input bind:value={password} /></div> + <div> + <button + class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" + on:click={() => reset(username)}>Reset</button + > + </div> </div> {/each} diff --git a/src/routes/(app)/buy/+page.svelte b/src/routes/(app)/buy/+page.svelte index 2f556ada..22e7e9ec 100644 --- a/src/routes/(app)/buy/+page.svelte +++ b/src/routes/(app)/buy/+page.svelte @@ -1,7 +1,7 @@ <script> import { enhance } from '$app/forms'; import { Icon, Lock, Card, Calendar, Spinner } from '$comp'; - import { animatedRate, selectedRate } from '$lib/store'; + import { animatedRate } from '$lib/store'; import { f } from '$lib/utils'; export let data, form; @@ -10,24 +10,6 @@ let loading; let amount, n, d, cvc; - - let ease = (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t); - let i; - - let o = $selectedRate; - $: animate($selectedRate); - let animate = (n) => { - clearInterval(i); - - let t = 0; - let d = o - n; - - i = setInterval(() => { - $animatedRate = (o - d * ease(t / 100)).toFixed(2); - if (t > 80) (o = n) && clearInterval(i); - t++; - }, 10); - }; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> @@ -141,7 +123,6 @@ <div class="my-auto mr-1">1</div> <img src="/images/bitcoin.svg" class="w-5 my-auto" alt="Bitcoin" /> </div> - <div>= {f($animatedRate, currency)}</div> </div> <p class="text-secondary">3% credit card fee applies</p> </div> diff --git a/src/routes/(app)/launch/purchase/+page.svelte b/src/routes/(app)/launch/purchase/+page.svelte index 3273752a..fbaa58c0 100644 --- a/src/routes/(app)/launch/purchase/+page.svelte +++ b/src/routes/(app)/launch/purchase/+page.svelte @@ -1,7 +1,6 @@ <script> import { onMount } from 'svelte'; import { enhance } from '$app/forms'; - import { selectedRate } from '$lib/store'; import { post } from '$lib/utils'; import { goto } from '$app/navigation'; diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index 75998cd0..eb3ae26b 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -1,5 +1,13 @@ <script> import { RegisterOrLogin } from '$comp'; + import { onMount } from 'svelte'; + import { pin } from '$lib/store'; + + onMount(() => { + $pin = undefined; + localStorage.clear(); + sessionStorage.clear(); + }); export let form; </script> diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index cf30db35..9e61cbd6 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -1,6 +1,6 @@ <script> import { t } from '$lib/translations'; - import { back, copy, f, s, fiat, sats } from '$lib/utils'; + import { back, copy, f, s, fiat, post, sats, success } from '$lib/utils'; import { Avatar, Icon } from '$comp'; import { format } from 'date-fns'; import { PUBLIC_EXPLORER as expl } from '$env/static/public'; @@ -12,6 +12,11 @@ let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; let a = Math.abs(amount); + let print = async () => { + await post(`/payment/${id}/print`, { id }); + success('Printing!'); + }; + fee = fee || 0; </script> @@ -52,6 +57,7 @@ <span class="text-lg text-secondary">Tip</span> <div> {f(fiat(tip, rate), currency)} + ({Math.round((tip * 100) / Math.abs(a))}%) <span class="text-secondary text-lg"> ⚡️{s(tip)} </span> @@ -106,4 +112,11 @@ </div> </div> {/if} + + {#if user.username.toLowerCase() === 'laughingbean'} + <button + class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px] bg-black text-white" + on:click={print}>Print Receipt</button + > + {/if} </div> diff --git a/src/routes/(app)/payment/[id]/print/+server.js b/src/routes/(app)/payment/[id]/print/+server.js new file mode 100644 index 00000000..fde834d1 --- /dev/null +++ b/src/routes/(app)/payment/[id]/print/+server.js @@ -0,0 +1,7 @@ +import { auth, post } from '$lib/utils'; +import { json } from '@sveltejs/kit'; + +export async function POST({ cookies, request }) { + let body = await request.json(); + return json(post('/print', body, auth(cookies))); +} diff --git a/src/routes/(app)/pot/[name]/+page.svelte b/src/routes/(app)/pot/[name]/+page.svelte index 2d93abfc..7b3d890b 100644 --- a/src/routes/(app)/pot/[name]/+page.svelte +++ b/src/routes/(app)/pot/[name]/+page.svelte @@ -6,7 +6,7 @@ import { toast } from '@zerodevx/svelte-toast'; import { t } from '$lib/translations'; import { copy, f, s, sat, sats } from '$lib/utils'; - import { loginRedirect, selectedRate } from '$lib/store'; + import { loginRedirect } from '$lib/store'; export let data; diff --git a/src/routes/(app)/send/+layout.svelte b/src/routes/(app)/send/+layout.svelte index e5c9c051..6192cd40 100644 --- a/src/routes/(app)/send/+layout.svelte +++ b/src/routes/(app)/send/+layout.svelte @@ -8,30 +8,11 @@ export let data; - let loaded; let { user } = data; - - onMount(() => setTimeout(() => (loaded = true), 500)); let cancel = () => goto(`/${user.username}`); - - $: browser && loaded && checkPin($pin); - let checkPin = async (p) => { - if (p?.length !== 6 || p === JSON.parse(sessionStorage.getItem('pin'))) return; - let result; - try { - result = await post('/pin', { pin: p }); - } catch (e) {} - - if (result) { - success('Pin confirmed'); - } else { - fail('Invalid pin'); - $pin = ''; - } - }; </script> -{#if loaded && user.haspin && (!$pin || $pin?.length !== 6)} +{#if user.haspin && (!$pin || $pin?.length !== 6)} <Pin bind:value={$pin} {cancel} /> {/if} diff --git a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte index 77d5fde6..342eeebe 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte @@ -5,15 +5,18 @@ import { Toggle, Slider, Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; import { fiat as toFiat, f, back, s, sat } from '$lib/utils'; - import { pin, selectedRate } from '$lib/store'; + import { pin } from '$lib/store'; export let data; export let form; + let { rates } = data; let { address, amount } = $page.params; let { balance, currency } = data.user; let loading, submit, fiat, confirmed, feeRate, min, max, fee, ourfee, stale, subtract, adjusting; + $: rate = rates[currency]; + let toggle = () => (loading = true); let toggleAdjusting = () => (adjusting = !adjusting); @@ -64,7 +67,7 @@ {#if confirmed} <div class="text-center"> <h2 class="text-2xl md:text-3xl font-semibold"> - {f(toFiat(amount, $selectedRate), currency)} + {f(toFiat(amount, rate), currency)} </h2> <h3 class="text-secondary md:text-lg mb-6 mt-1">⚡️{s(amount)}</h3> </div> @@ -74,7 +77,7 @@ <div class="relative"> <h2 class="text-xl font-semibold"> - {f(toFiat(fee + ourfee, $selectedRate), currency)} + {f(toFiat(fee + ourfee, rate), currency)} </h2> <h3 class="text-secondary mb-6 mt-1">⚡️{s(fee + ourfee)}</h3> <button class="absolute right-1/4 top-0 text-secondary p-4" on:click={toggleAdjusting} @@ -83,7 +86,7 @@ </div> </div> {:else} - <Numpad bind:amount bind:fiat {currency} {submit} /> + <Numpad bind:amount bind:fiat {currency} {submit} bind:rate /> {/if} <form method="POST" use:enhance on:submit={toggle}> diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.js b/src/routes/(app)/send/invoice/[id]/+page.server.js index c9c2b222..4cbbb03f 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.js +++ b/src/routes/(app)/send/invoice/[id]/+page.server.js @@ -7,6 +7,7 @@ export async function load({ params: { id }, parent, url }) { let { amount, address, + currency, hash, rate, prompt, @@ -17,7 +18,7 @@ export async function load({ params: { id }, parent, url }) { if (prompt && tip === null) throw redirect(307, `/${recipient.username}/invoice/${id}/tip`); if (recipient.username === user.username) throw error(500, { message: 'Cannot send to self' }); - return { amount, address, hash, tip, rate, payreq: text, recipient }; + return { amount, address, currency, hash, tip, rate, payreq: text, recipient }; } export const actions = { diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index c9cb9392..d5c548a7 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -1,7 +1,7 @@ <script> import { t } from '$lib/translations'; import { goto } from '$app/navigation'; - import { pin, selectedRate } from '$lib/store'; + import { pin } from '$lib/store'; import { enhance } from '$app/forms'; import { Avatar, Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; @@ -9,13 +9,13 @@ export let data; export let form; - let { address, payreq, recipient, rate, tip, user } = data; + let { address, rate, payreq, recipient, tip, rates, user } = data; let { currency } = user; let amount = form?.amount || data.amount; let a, af, - amountFiat = amount * ($selectedRate / sats), + amountFiat = amount * (rate / sats), fiat = !amount; let setAmount = () => { @@ -26,6 +26,8 @@ let loading; let submit = () => (loading = true); + $: rate = data.rate * (rates[user.currency] / rates[data.currency]); + $: update(form); let update = () => { if (form?.message.includes('pin')) $pin = undefined; @@ -75,7 +77,7 @@ </div> </div> {:else} - <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat /> + <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat bind:rate /> {/if} <form method="POST" use:enhance on:submit={submit}> diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte index af07a565..cb1b539e 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte +++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte @@ -10,13 +10,14 @@ export let form; let { payreq } = $page.params; - let { alias } = data; + let { alias, rates } = data; let { currency } = data.user; let a; let maxfee = 100; $: amount = form?.amount || data.amount; + $: rate = rates[currency]; let loading; let submit = () => (loading = true); @@ -91,7 +92,7 @@ {:else} <form method="POST" action="?/setAmount" class="w-[300px] mx-auto" use:enhance> <input type="hidden" value={a} name="amount" /> - <Numpad bind:amount={a} {currency} /> + <Numpad bind:amount={a} {currency} {rate} /> <button type="submit" class="bg-black text-white rounded-xl h-[48px] flex w-full justify-center items-center font-semibold diff --git a/src/routes/(app)/send/pot/[name]/[...amount]/+page.svelte b/src/routes/(app)/send/pot/[name]/[...amount]/+page.svelte index da761cc2..6aecf8f3 100644 --- a/src/routes/(app)/send/pot/[name]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/pot/[name]/[...amount]/+page.svelte @@ -10,9 +10,12 @@ export let form; let { amount, name } = $page.params; + let { rates } = data; let { currency } = data.user; let loading; + $: rate = rates[currency]; + let submit = () => (loading = true); $: update(form); let update = () => { @@ -32,7 +35,7 @@ {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount {currency} /> + <Numpad bind:amount {currency} bind:rate /> <form method="POST" use:enhance on:submit={submit}> <input name="name" value={name} type="hidden" /> diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js index c63328e2..a01cdcfc 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js @@ -1,12 +1,34 @@ import { error, fail, redirect } from '@sveltejs/kit'; -import { fd, auth, get, post } from '$lib/utils'; +import { types, sats, fd, auth, get, post } from '$lib/utils'; -export async function load({ params, parent }) { - let { user } = await parent(); - let { amount } = params; +export async function load({ cookies, params, parent }) { let subject = await get(`/users/${params.username}`); + + let { rates, user } = await parent(); + let { username } = params; + let [amount, currency] = params.amount.split('/'); + if (subject.username === user?.username) throw error(500, { message: 'Cannot send to self' }); - return { amount, subject }; + + let rate; + if (amount) { + rate = rates[currency ? currency.toUpperCase() : subject.currency]; + if (!rate) throw error(500, 'Invalid currency symbol'); + if (currency) amount = (amount * sats) / rate; + + let { hash } = await post( + '/invoice', + { + invoice: { amount, prompt: false, type: types.lightning }, + user: { username } + }, + auth(cookies) + ); + + throw redirect(307, `/${username}/invoice/${hash}`); + } + + return { amount, rate, subject }; } export const actions = { diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte index af6a6368..9ce82d2e 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte @@ -1,7 +1,7 @@ <script> import { t } from '$lib/translations'; import { goto } from '$app/navigation'; - import { pin, selectedRate } from '$lib/store'; + import { pin } from '$lib/store'; import { enhance } from '$app/forms'; import { AppHeader, Avatar, Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; @@ -10,23 +10,22 @@ export let data; export let form; - let { subject, rates, user } = data; - let currency = user?.currency || 'USD'; + let { subject, rate, rates, user } = data; + let currency = user.currency || subject.currency || 'USD'; let amount = form?.amount || data.amount; let a, af, fiat = !amount, hash, - rate, amountFiat = 0; - selectedRate.subscribe((r) => rate || (amountFiat = amount * (r / sats))); + $: r = rate || rates[user.currency || subject.currency]; let setAmount = async () => { amount = a; amountFiat = parseFloat(af).toFixed(2); - rate = fiat ? (sats * amountFiat) / amount : $selectedRate; + rate = fiat ? (sats * amountFiat) / amount : r; ({ hash } = await post(`/${subject.username}/invoice`, { invoice: { amount, @@ -60,7 +59,7 @@ {/if} <div class="container px-4 mt-20 max-w-xl mx-auto space-y-8"> - <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat /> + <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat bind:rate={r} /> <form method="POST" use:enhance on:submit={submit}> <input name="amount" value={amount} type="hidden" /> diff --git a/src/routes/(app)/support/+page.svelte b/src/routes/(app)/support/+page.svelte index 8dbfc1af..b2f82e89 100644 --- a/src/routes/(app)/support/+page.svelte +++ b/src/routes/(app)/support/+page.svelte @@ -1,4 +1,5 @@ <script> + import { onDestroy } from 'svelte'; import { fail, success, post } from '$lib/utils'; import { scale } from 'svelte/transition'; import { AppHeader, Icon } from '$comp'; @@ -14,19 +15,32 @@ let email; let message; let sent; + let recaptchaSiteKey = '6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM'; let submit = (e) => { e.preventDefault(); grecaptcha.ready(() => { - grecaptcha - .execute('6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM', { action: 'submit' }) - .then((token) => - post('/support', { email, message, token }) - .then(() => (sent = true)) - .catch(() => fail('problem submitting')) - ); + grecaptcha.execute(recaptchaSiteKey, { action: 'submit' }).then((token) => + post('/support', { username: user?.username, email, message, token }) + .then(() => (sent = true)) + .catch(() => fail('problem submitting')) + ); }); }; + + onDestroy(() => { + const nodeBadge = document.querySelector('.grecaptcha-badge'); + if (nodeBadge) { + document.body.removeChild(nodeBadge.parentNode); + } + + const scriptSelector = + "script[src='https://www.google.com/recaptcha/api.js?render=" + recaptchaSiteKey + "']"; + const script = document.querySelector(scriptSelector); + if (script) { + script.remove(); + } + }); </script> <svelte:head> |
