diff options
| author | Adam Soltys <[email protected]> | 2023-12-01 20:04:13 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2023-12-01 20:04:13 -0800 |
| commit | 216a600224fde22ce76b367557a02094ec693886 (patch) | |
| tree | d1189bbaa39db204633523132a7d4767b952717d /src/routes/(app) | |
| parent | 7d9bc1f54dfd04172e8d6d43ebc2efd6f407e833 (diff) | |
| download | coinos-ui-216a600224fde22ce76b367557a02094ec693886.tar.gz coinos-ui-216a600224fde22ce76b367557a02094ec693886.zip | |
prettier
Diffstat (limited to 'src/routes/(app)')
114 files changed, 5297 insertions, 4859 deletions
diff --git a/src/routes/(app)/+error.svelte b/src/routes/(app)/+error.svelte index 36726e26..ed0bec3e 100644 --- a/src/routes/(app)/+error.svelte +++ b/src/routes/(app)/+error.svelte @@ -1,38 +1,38 @@ <script> - import { back } from '$lib/utils'; - import { Icon } from '$comp'; - import { page } from '$app/stores'; + import { back } from "$lib/utils"; + import { Icon } from "$comp"; + import { page } from "$app/stores"; - export let data; - let { user } = data; + export let data; + let { user } = data; - let link = user ? `/${user.username}` : '/'; + let link = user ? `/${user.username}` : "/"; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> - <div class="relative"> - <h1 class="text-2xl md:text-3xl font-semibold text-center">Error</h1> - </div> - <p class="text-secondary text-center">Something went wrong</p> - <p class="text-secondary text-center">{$page.error.message}</p> + <div class="flex justify-center items-center"> + <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> + <div class="relative"> + <h1 class="text-2xl md:text-3xl font-semibold text-center">Error</h1> + </div> + <p class="text-secondary text-center">Something went wrong</p> + <p class="text-secondary text-center">{$page.error.message}</p> - <div class="flex justify-center py-24"> - <a href={link}> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-xl" - > - Back - </button> - </a> - </div> - </div> - </div> + <div class="flex justify-center py-24"> + <a href={link}> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-xl" + > + Back + </button> + </a> + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/+layout.server.js b/src/routes/(app)/+layout.server.js index e80e1e64..9fcf6411 100644 --- a/src/routes/(app)/+layout.server.js +++ b/src/routes/(app)/+layout.server.js @@ -1,44 +1,48 @@ -import { error, redirect } from '@sveltejs/kit'; -import { get, post, auth, protectedRoutes } from '$lib/utils'; -import { PUBLIC_BTC as btc } from '$env/static/public'; +import { error, redirect } from "@sveltejs/kit"; +import { get, post, auth, protectedRoutes } from "$lib/utils"; +import { PUBLIC_BTC as btc } from "$env/static/public"; export async function load({ cookies, request, url, params }) { - let { pathname } = url; - let token = cookies.get('token'); - let rate, - rates = { USD: 1 }; + let { pathname } = url; + let token = cookies.get("token"); + let rate, + rates = { USD: 1 }; - let user; - if (token) { - try { - user = await get('/me', auth(cookies)); - } catch (e) {} - } + let user; + if (token) { + try { + user = await get("/me", auth(cookies)); + } catch (e) {} + } - if (user?.needsMigration) { - throw redirect(307, `/migrate`); - } + if (user?.needsMigration) { + throw redirect(307, `/migrate`); + } - if (user && ['/login', '/register'].includes(pathname) && request.method === 'GET') { - throw redirect(307, `/${user.username}`); - } + if ( + user && + ["/login", "/register"].includes(pathname) && + request.method === "GET" + ) { + throw redirect(307, `/${user.username}`); + } - if ( - protectedRoutes.find((p) => pathname.match(p)) && - (!user || user.username !== params.username) - ) { - throw redirect(307, '/login'); - } + if ( + protectedRoutes.find((p) => pathname.match(p)) && + (!user || user.username !== params.username) + ) { + throw redirect(307, "/login"); + } - if (user && !user.pubkey && !pathname.includes('generate')) - throw redirect(307, `/${user.username}/generate`); + if (user && !user.pubkey && !pathname.includes("generate")) + throw redirect(307, `/${user.username}/generate`); - try { - rate = await get('/rate'); - rates = await get('/rates'); - } catch (e) { - console.log(e); - } + try { + rate = await get("/rate"); + rates = await get("/rates"); + } catch (e) { + console.log(e); + } - return { user, token, rate, rates }; + return { user, token, rate, rates }; } diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 51ade9c7..c5a312e1 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,167 +1,174 @@ <script> - import { SvelteToast } from '@zerodevx/svelte-toast'; - import { onDestroy, onMount } from 'svelte'; - import { close, connect, send } from '$lib/socket'; - import { last, invoice, request, ndef, passwordPrompt, pin } from '$lib/store'; - import { page } from '$app/stores'; - import { browser } from '$app/environment'; - import { goto } from '$app/navigation'; - import { LoadingSplash, Invoice, Password, Request } from '$comp'; - import { warning, protectedRoutes } from '$lib/utils'; - import { t, locale } from '$lib/translations'; - import { onNavigate } from '$app/navigation'; + import { SvelteToast } from "@zerodevx/svelte-toast"; + import { onDestroy, onMount } from "svelte"; + import { close, connect, send } from "$lib/socket"; + import { + last, + invoice, + request, + ndef, + passwordPrompt, + pin, + } from "$lib/store"; + import { page } from "$app/stores"; + import { browser } from "$app/environment"; + import { goto } from "$app/navigation"; + import { LoadingSplash, Invoice, Password, Request } from "$comp"; + import { warning, protectedRoutes } from "$lib/utils"; + import { t, locale } from "$lib/translations"; + import { onNavigate } from "$app/navigation"; - export let data; + export let data; - onNavigate((navigation) => { - if (!document.startViewTransition) return; + onNavigate((navigation) => { + if (!document.startViewTransition) return; - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); - }); - }); + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; + }); + }); + }); - let { rate, user, subject, token, rates } = data; + let { rate, user, subject, token, rates } = data; - $: update(data); - let update = (data) => { - ({ rate, user, subject, token, rates } = data); - }; + $: update(data); + let update = (data) => { + ({ rate, user, subject, token, rates } = data); + }; - onMount(async () => { - let localeLocalStorageKey = 'sveltekit-i18n-locale'; - let localStorageLocale = localStorage.getItem(localeLocalStorageKey); - if (localStorageLocale) locale.set(localStorageLocale); + onMount(async () => { + let localeLocalStorageKey = "sveltekit-i18n-locale"; + let localStorageLocale = localStorage.getItem(localeLocalStorageKey); + if (localStorageLocale) locale.set(localStorageLocale); - locale.subscribe((lng) => { - if (lng) localStorage.setItem(localeLocalStorageKey, lng); - }); + locale.subscribe((lng) => { + if (lng) localStorage.setItem(localeLocalStorageKey, lng); + }); - if (browser) { - checkSocket(); - let locktime = user && user.locktime ? user.locktime : 300; - expireTimer = setTimeout(expirePin, locktime * 1000); + if (browser) { + checkSocket(); + let locktime = user && user.locktime ? user.locktime : 300; + expireTimer = setTimeout(expirePin, locktime * 1000); - // if (window.NDEFReader) { - // try { - // $ndef = new NDEFReader(); - // await $ndef.scan(); - // - // $ndef.addEventListener('readingerror', (e) => { - // console.log('nfc error', e); - // }); - // - // $ndef.addEventListener('reading', ({ message, serialNumber }) => { - // goto(`/${user.username}/card/${serialNumber.replace(/:/g, '-')}`); - // }); - // } catch (error) { - // console.log('Argh! ' + error); - // } - // } - } - }); + // if (window.NDEFReader) { + // try { + // $ndef = new NDEFReader(); + // await $ndef.scan(); + // + // $ndef.addEventListener('readingerror', (e) => { + // console.log('nfc error', e); + // }); + // + // $ndef.addEventListener('reading', ({ message, serialNumber }) => { + // goto(`/${user.username}/card/${serialNumber.replace(/:/g, '-')}`); + // }); + // } catch (error) { + // console.log('Argh! ' + error); + // } + // } + } + }); - $: browser && connect(token); + $: browser && connect(token); - let lost, - checkTimer, - expireTimer, - counter = 0; + let lost, + checkTimer, + expireTimer, + counter = 0; - let checkSocket = () => { - counter++; - lost = Date.now() - $last > 30000; - if (lost) connect(token); - if (counter > 5) { - send('heartbeat', token); - counter = 0; - } + let checkSocket = () => { + counter++; + lost = Date.now() - $last > 30000; + if (lost) connect(token); + if (counter > 5) { + send("heartbeat", token); + counter = 0; + } - checkTimer = setTimeout(checkSocket, 1000); - }; + checkTimer = setTimeout(checkSocket, 1000); + }; - let expirePin = () => { - $pin = null; - expireTimer = setTimeout(expirePin, user.locktime * 1000 || 300000); - }; + let expirePin = () => { + $pin = null; + expireTimer = setTimeout(expirePin, user.locktime * 1000 || 300000); + }; - onDestroy(() => { - if (browser) { - close(); - clearTimeout(checkTimer); - clearTimeout(expireTimer); - } - }); + onDestroy(() => { + if (browser) { + close(); + clearTimeout(checkTimer); + clearTimeout(expireTimer); + } + }); </script> {#if browser && $passwordPrompt} - <Password {user} /> + <Password {user} /> {/if} <svelte:head> - {#if subject} - <title>coinos - {subject.username}</title> - <meta name="lightning" content={`lnurlp:${subject.username}@coinos.io`} /> - {:else} - <title>coinos</title> - {/if} + {#if subject} + <title>coinos - {subject.username}</title> + <meta name="lightning" content={`lnurlp:${subject.username}@coinos.io`} /> + {:else} + <title>coinos</title> + {/if} - {#if subject?.profile} - <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> - {:else} - <meta property="og:image" content="/icons/logo.svg" /> - {/if} + {#if subject?.profile} + <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> + {:else} + <meta property="og:image" content="/icons/logo.svg" /> + {/if} </svelte:head> <SvelteToast options={{ reversed: true, intro: { y: 192 } }} /> <main class="pb-20"> - <slot /> + <slot /> </main> {#if $invoice} - <Invoice {user} /> + <Invoice {user} /> {/if} {#if $request} - <Request {user} /> + <Request {user} /> {/if} <style> - @keyframes fade-in { - from { - opacity: 0; - } - } + @keyframes fade-in { + from { + opacity: 0; + } + } - @keyframes fade-out { - to { - opacity: 0; - } - } + @keyframes fade-out { + to { + opacity: 0; + } + } - @keyframes slide-from-right { - from { - transform: translateX(60px); - } - } + @keyframes slide-from-right { + from { + transform: translateX(60px); + } + } - @keyframes slide-to-left { - to { - transform: translateX(-60px); - } - } + @keyframes slide-to-left { + to { + transform: translateX(-60px); + } + } - :root::view-transition-old(root) { - animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; - } + :root::view-transition-old(root) { + animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; + } - :root::view-transition-new(root) { - animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; - } + :root::view-transition-new(root) { + animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; + } </style> diff --git a/src/routes/(app)/[username]/(profile)/+layout.server.js b/src/routes/(app)/[username]/(profile)/+layout.server.js index f4f2ca0a..a508de92 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.server.js +++ b/src/routes/(app)/[username]/(profile)/+layout.server.js @@ -1,26 +1,26 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; export async function load({ parent }) { - let { subject, user } = await parent(); + let { subject, user } = await parent(); - subject.follows = []; - subject.followers = []; + subject.follows = []; + subject.followers = []; - try { - subject.follows = await get(`/${subject.pubkey}/follows`); - } catch (e) { - console.log('problem fetching follows', e); - } + try { + subject.follows = await get(`/${subject.pubkey}/follows`); + } catch (e) { + console.log("problem fetching follows", e); + } - try { - subject.followers = await get(`/${subject.pubkey}/followers`); - } catch (e) { - console.log('problem fetching followers', e); - } + try { + subject.followers = await get(`/${subject.pubkey}/followers`); + } catch (e) { + console.log("problem fetching followers", e); + } - if (user) { - user.follows = await get(`/${user.pubkey}/follows?tagsonly=true`); - } + if (user) { + user.follows = await get(`/${user.pubkey}/follows?tagsonly=true`); + } - return { subject, user }; + return { subject, user }; } diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 55c22bdb..6b166e2c 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -1,236 +1,246 @@ <script> - import { hexToUint8Array } from 'uint8array-extras'; - import { copy, f, s, sats } from '$lib/utils'; - import { animatedRate } from '$lib/store'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; - import { sign, send } from '$lib/nostr'; - import { bech32m } from '@scure/base'; - import { page } from '$app/stores'; + import { hexToUint8Array } from "uint8array-extras"; + import { copy, f, s, sats } from "$lib/utils"; + import { animatedRate } from "$lib/store"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; + import { sign, send } from "$lib/nostr"; + import { bech32m } from "@scure/base"; + import { page } from "$app/stores"; - export let data; + export let data; - let { encode, toWords } = bech32m; - let events, user, subject, rates, src, text; + let { encode, toWords } = bech32m; + let events, user, subject, rates, src, text; - $: ({ events, user, rates, subject, src, text } = data); - $: ({ currency, username: n, display } = subject); + $: ({ events, user, rates, subject, src, text } = data); + $: ({ currency, username: n, display } = subject); - $: username = n.length > 60 ? n.substr(0, 6) : display || n; - $: npub = bech32m.encode('npub', toWords(hexToUint8Array(subject.pubkey)), 180); - $: lnaddr = `${n}@${$page.url.host}`; - $: profile = `${$page.url.host}/${n}`; + $: username = n.length > 60 ? n.substr(0, 6) : display || n; + $: npub = bech32m.encode( + "npub", + toWords(hexToUint8Array(subject.pubkey)), + 180 + ); + $: lnaddr = `${n}@${$page.url.host}`; + $: profile = `${$page.url.host}/${n}`; - let follow = async () => { - user.follows.push(['p', subject.pubkey, 'wss://nostr.coinos.io', subject.username]); - update(); - }; + let follow = async () => { + user.follows.push([ + "p", + subject.pubkey, + "wss://nostr.coinos.io", + subject.username, + ]); + update(); + }; - let unfollow = async () => { - user.follows.splice( - user.follows.findIndex((t) => t[1] === subject.pubkey), - 1 - ); - update(); - }; + let unfollow = async () => { + user.follows.splice( + user.follows.findIndex((t) => t[1] === subject.pubkey), + 1 + ); + update(); + }; - let update = async () => { - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 3, - content: '', - tags: user.follows - }; + let update = async () => { + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 3, + content: "", + tags: user.follows, + }; - await sign({ event, user }); - await send(event); + await sign({ event, user }); + await send(event); - user.follows = user.follows; - }; + user.follows = user.follows; + }; - $: following = !!user?.follows.find((t) => t.includes(subject.pubkey)); + $: following = !!user?.follows.find((t) => t.includes(subject.pubkey)); - let showBio; - let toggleBio = () => (showBio = !showBio); + let showBio; + let toggleBio = () => (showBio = !showBio); - let showDetails; - let toggleDetails = () => (showDetails = !showDetails); + let showDetails; + let toggleDetails = () => (showDetails = !showDetails); </script> <div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap"> - <div class="hidden lg:block lg:w-[300px]" /> - <div class="lg:w-[300px] lg:absolute space-y-5 left-20 mx-auto max-w-sm"> - <div class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"> - <div class="my-auto">{display || username}</div> - <button on:click={toggleDetails}> - <Icon icon="stats" style="w-12" /> - </button> - </div> + <div class="hidden lg:block lg:w-[300px]" /> + <div class="lg:w-[300px] lg:absolute space-y-5 left-20 mx-auto max-w-sm"> + <div + class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" + > + <div class="my-auto">{display || username}</div> + <button on:click={toggleDetails}> + <Icon icon="stats" style="w-12" /> + </button> + </div> - {#if showDetails} - <div> - <div class="font-bold">{$t('user.profilePage')}</div> - <div class="flex gap-4"> - <div class="break-all grow text-secondary text-lg"> - {profile} - </div> - <div class="flex mb-auto gap-1"> - <button class="my-auto" on:click={() => copy(profile)} - ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button - > - <a - href={`/qr/${encodeURIComponent(profile)}`} - class="my-auto" - > - <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> - </a> - </div> - </div> - </div> - <div> - <div class="font-bold">{$t('user.lightningAddress')}</div> - <div class="flex gap-4"> - <div class="break-all grow text-secondary text-lg"> - {lnaddr} - </div> - <div class="flex mb-auto gap-1"> - <button class="my-auto" on:click={() => copy(lnaddr)} - ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button - > - <a - href={`/qr/${encodeURIComponent(lnaddr)}`} - class="my-auto" - > - <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> - </a> - </div> - </div> - </div> - <div> - <div class="font-bold">{$t('user.nostrPubkey')}</div> - <div class="flex gap-4"> - <div class="break-all grow text-secondary text-lg"> - {npub} - </div> - <div class="flex my-auto gap-1"> - <button class="my-auto" on:click={() => copy(npub)} - ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button - > - <a - href={`/qr/${encodeURIComponent(npub)}`} - class="my-auto" - > - <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> - </a> - </div> - </div> - </div> - {/if} + {#if showDetails} + <div> + <div class="font-bold">{$t("user.profilePage")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary text-lg"> + {profile} + </div> + <div class="flex mb-auto gap-1"> + <button class="my-auto" on:click={() => copy(profile)} + ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(profile)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + <div> + <div class="font-bold">{$t("user.lightningAddress")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary text-lg"> + {lnaddr} + </div> + <div class="flex mb-auto gap-1"> + <button class="my-auto" on:click={() => copy(lnaddr)} + ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + <div> + <div class="font-bold">{$t("user.nostrPubkey")}</div> + <div class="flex gap-4"> + <div class="break-all grow text-secondary text-lg"> + {npub} + </div> + <div class="flex my-auto gap-1"> + <button class="my-auto" on:click={() => copy(npub)} + ><Icon icon="copy" style="max-w-max min-w-[32px]" /></button + > + <a href={`/qr/${encodeURIComponent(npub)}`} class="my-auto"> + <Icon icon="qr" style="invert max-w-max min-w-[32px]" /> + </a> + </div> + </div> + </div> + {/if} - {#if subject.address && subject.id !== user?.id} - <div - class="text-secondary mx-auto text-center lg:text-left lg:mx-0" - class:line-clamp-3={showBio} - on:click={toggleBio} - on:keydown={toggleBio} - > - {subject.address} - </div> - {/if} + {#if subject.address && subject.id !== user?.id} + <div + class="text-secondary mx-auto text-center lg:text-left lg:mx-0" + class:line-clamp-3={showBio} + on:click={toggleBio} + on:keydown={toggleBio} + > + {subject.address} + </div> + {/if} - <div class="flex justify-center gap-2"> - <a href={`/${subject.pubkey}/follows`} - ><b>{subject.follows.length}</b> - <span class="text-secondary">{$t('user.following')}</span></a - > - <a href={`/${subject.pubkey}/followers`} - ><b>{subject.followers.length}</b> - <span class="text-secondary">{$t('user.followers')}</span></a - > - </div> + <div class="flex justify-center gap-2"> + <a href={`/${subject.pubkey}/follows`} + ><b>{subject.follows.length}</b> + <span class="text-secondary">{$t("user.following")}</span></a + > + <a href={`/${subject.pubkey}/followers`} + ><b>{subject.followers.length}</b> + <span class="text-secondary">{$t("user.followers")}</span></a + > + </div> - <div class="flex flex-wrap gap-2 w-full"> - {#if user && user.username !== subject.username && subject.pubkey} - {#if following} - <div class="w-full flex"> - <button - class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white" - on:click={unfollow} - > - <div class="mx-auto flex"> - <Icon icon={'profile'} style="my-auto h-6 mr-2 invert" /> - <div class="my-auto">{$t('user.following')}</div> - </div> - </button> - </div> - {:else} - <div class="w-full flex"> - <button - class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" - on:click={follow} - > - <div class="mx-auto flex"> - <Icon icon={'profile'} style="my-auto h-6 mr-2" /> - <div class="my-auto">{$t('user.follow')}</div> - </div> - </button> - </div> - {/if} - {/if} + <div class="flex flex-wrap gap-2 w-full"> + {#if user && user.username !== subject.username && subject.pubkey} + {#if following} + <div class="w-full flex"> + <button + class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white" + on:click={unfollow} + > + <div class="mx-auto flex"> + <Icon icon={"profile"} style="my-auto h-6 mr-2 invert" /> + <div class="my-auto">{$t("user.following")}</div> + </div> + </button> + </div> + {:else} + <div class="w-full flex"> + <button + class="mx-auto rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + on:click={follow} + > + <div class="mx-auto flex"> + <Icon icon={"profile"} style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.follow")}</div> + </div> + </button> + </div> + {/if} + {/if} - {#if user && user.username !== subject.username && subject.pubkey} - <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="message" style="mr-2 my-auto" /> - <div class="mt-1 my-auto">{$t('user.message')}</div> - </div> - </button> - </a> - </div> - {/if} + {#if user && user.username !== subject.username && subject.pubkey} + <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="message" style="mr-2 my-auto" /> + <div class="mt-1 my-auto">{$t("user.message")}</div> + </div> + </button> + </a> + </div> + {/if} - {#if !subject.anon && subject.username !== user?.username} - <div class="w-full flex"> - <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="send" style="mr-2" /> - <div class="mt-1">{$t('user.pay')}</div> - </div> - </button> - </a> - </div> - {/if} - </div> - </div> + {#if !subject.anon && subject.username !== user?.username} + <div class="w-full flex"> + <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="send" style="mr-2" /> + <div class="mt-1">{$t("user.pay")}</div> + </div> + </button> + </a> + </div> + {/if} + </div> + </div> - <div class="w-full"> - <div class="mx-auto space-y-5 lg:max-w-xl lg:pl-10 xl:max-w-2xl lg:pl-10"> - <slot /> - </div> - </div> + <div class="w-full"> + <div class="mx-auto space-y-5 lg:max-w-xl lg:pl-10 xl:max-w-2xl lg:pl-10"> + <slot /> + </div> + </div> </div> {#if currency && !isNaN($animatedRate)} - <div class="flex fixed w-full px-4 bg-white py-2 pb-3 bottom-0 bg-opacity-90 tabular-nums"> - <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, 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, currency)}</div> - </div> - </div> - </div> + <div + class="flex fixed w-full px-4 bg-white py-2 pb-3 bottom-0 bg-opacity-90 tabular-nums" + > + <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, 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, currency)}</div> + </div> + </div> + </div> {/if} diff --git a/src/routes/(app)/[username]/(profile)/+page.server.js b/src/routes/(app)/[username]/(profile)/+page.server.js index e37db1fe..3110ec40 100644 --- a/src/routes/(app)/[username]/(profile)/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/+page.server.js @@ -1,35 +1,35 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export async function load({ cookies, params, parent, url }) { - let { user, subject } = await parent(); - let { pubkey } = subject; - let { since = 0 } = params; + let { user, subject } = await parent(); + let { pubkey } = subject; + let { since = 0 } = params; - let messages, invoices, sent, received, notes; - messages = invoices = sent = received = notes = []; + let messages, invoices, sent, received, notes; + messages = invoices = sent = received = notes = []; - if (pubkey) { - if (user) { - try { - messages = await get(`/${user.pubkey}/${since}/messages`); - messages = messages.sort((a, b) => b.created_at - a.created_at); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } + if (pubkey) { + if (user) { + try { + messages = await get(`/${user.pubkey}/${since}/messages`); + messages = messages.sort((a, b) => b.created_at - a.created_at); + } catch (e) { + console.log(`failed to fetch nostr messages`, e); + } - ({ invoices, sent, received } = await get('/requests', auth(cookies))); - } + ({ invoices, sent, received } = await get("/requests", auth(cookies))); + } - try { - notes = await get(`/${pubkey}/notes`); - } catch (e) { - console.log(`failed to fetch nostr notes for ${pubkey}`, e); - } + try { + notes = await get(`/${pubkey}/notes`); + } catch (e) { + console.log(`failed to fetch nostr notes for ${pubkey}`, e); + } - notes.map((e) => { - e.seen = e.created_at; - }); - } + notes.map((e) => { + e.seen = e.created_at; + }); + } - return { invoices, messages, notes, sent, received }; + return { invoices, messages, notes, sent, received }; } diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 32a1f2cb..b0e36090 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -1,227 +1,245 @@ <script> - import { onMount } from 'svelte'; - import { Icon, Avatar, Balance, Feed } from '$comp'; - import { t } from '$lib/translations'; - import { ease, f, sat, sats } from '$lib/utils'; - import { sign, send, encrypt, decrypt } from '$lib/nostr'; - import { event as e, password, rate, animatedRate } from '$lib/store'; - import { browser } from '$app/environment'; + import { onMount } from "svelte"; + import { Icon, Avatar, Balance, Feed } from "$comp"; + import { t } from "$lib/translations"; + import { ease, f, sat, sats } from "$lib/utils"; + import { sign, send, encrypt, decrypt } from "$lib/nostr"; + 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, rates } = data; - $: refresh(data); - let refresh = (d) => ({ messages, notes, invoices, sent, received, subject, user, rates } = d); + export let data; + let { messages, notes, invoices, sent, received, subject, user, rates } = + data; + $: refresh(data); + let refresh = (d) => + ({ messages, notes, invoices, sent, received, subject, user, rates } = d); - let keys = new Set(); - let latest = []; - let ready; + let keys = new Set(); + let latest = []; + let ready; - e.subscribe(async (event) => { - if (!(event && ready)) return; - if (event.recipient?.id === user.id && !~latest.findIndex((m) => m.id === event.id)) { - event.content = await decrypt({ event, user }); + e.subscribe(async (event) => { + if (!(event && ready)) return; + 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); - let popped; - if (~i) popped = latest.splice(i, 1); - else popped = latest.pop(); + let i = latest.findIndex((m) => m.pubkey === event.pubkey); + let popped; + if (~i) popped = latest.splice(i, 1); + else popped = latest.pop(); - latest.unshift(event); - latest = latest; - } - }); + latest.unshift(event); + latest = latest; + } + }); - $: initialize($password); - let initialize = async (p) => { - let i = 0; - ready = false; - if (!p || !user) return; - while (i < messages.length) { - let event = messages[i]; - i++; + $: initialize($password); + let initialize = async (p) => { + let i = 0; + ready = false; + if (!p || !user) return; + while (i < messages.length) { + let event = messages[i]; + i++; - if (!(event.author && event.recipient)) continue; + 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); - } - } + 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); + } + } - latest = latest; - ready = true; - }; + latest = latest; + ready = true; + }; - let i; - let o; + let i; + let o; - rate.subscribe((n) => { - if (!user?.currency) return; - clearInterval(i); + rate.subscribe((n) => { + if (!user?.currency) return; + clearInterval(i); - let r = ((rates[user.currency] / rates['USD']) * n).toFixed(2); + let r = ((rates[user.currency] / rates["USD"]) * n).toFixed(2); - if (!r || isNaN(r)) return; - if (!o) o = r; + if (!r || isNaN(r)) return; + if (!o) o = r; - let t = 0; - let d = 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); - }); + 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-5"> - {#if user?.id === subject.id} - <div class="space-y-5"> - <div class="flex justify-center lg:justify-start"> - <Balance {user} /> - </div> + {#if user?.id === subject.id} + <div class="space-y-5"> + <div class="flex justify-center lg:justify-start"> + <Balance {user} /> + </div> - {#if !user.balance} - <div class="mb-8"> - <p class="text-secondary text-lg"> - {$t('user.welcome')} - </p> - </div> - {/if} + {#if !user.balance} + <div class="mb-8"> + <p class="text-secondary text-lg"> + {$t("user.welcome")} + </p> + </div> + {/if} - <div class="flex flex-wrap gap-2 justify-center w-full"> - <a href={`/${user.username}/receive`} class="w-full md:w-60"> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" - > - <div class="mx-auto flex"> - <Icon icon="numpad" style="my-auto h-6 mr-2" /> - <div class="my-auto">{$t('user.dashboard.receive')}</div> - </div> - </button> - </a> - <a href={`/send`} class="w-full md:w-60"> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" - > - <div class="mx-auto flex"> - <Icon icon="send" style="my-auto h-6 mr-2" /> - <div class="my-auto">{$t('user.dashboard.send')}</div> - </div> - </button> - </a> + <div class="flex flex-wrap gap-2 justify-center w-full"> + <a href={`/${user.username}/receive`} class="w-full md:w-60"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" + > + <div class="mx-auto flex"> + <Icon icon="numpad" style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.dashboard.receive")}</div> + </div> + </button> + </a> + <a href={`/send`} class="w-full md:w-60"> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-full" + > + <div class="mx-auto flex"> + <Icon icon="send" style="my-auto h-6 mr-2" /> + <div class="my-auto">{$t("user.dashboard.send")}</div> + </div> + </button> + </a> - {#if user.eligible} - <a href={`/buy`}> - <button class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60"> - <div class="mx-auto flex"> - <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> --> + {#if user.eligible} + <a href={`/buy`}> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + > + <div class="mx-auto flex"> + <!-- <Icon icon="plus" style="my-auto h-6 mr-2" /> --> - <img - src="/images/bitcoin.svg" - class="w-8 border-4 border-transparent mr-2" - alt="Bitcoin" - /> - <div class="my-auto mt-1">Buy Bitcoin</div> - </div> - </button> - </a> - {/if} - </div> - </div> - {/if} + <img + src="/images/bitcoin.svg" + class="w-8 border-4 border-transparent mr-2" + alt="Bitcoin" + /> + <div class="my-auto mt-1">Buy Bitcoin</div> + </div> + </button> + </a> + {/if} + </div> + </div> + {/if} - {#if invoices.length} - <div> - <h1 class="text-secondary text-xl">Invoices</h1> - {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }} - {@const requested = user.username === requester?.username} - <a href={`/${recipient.username}/invoice/${hash}` + (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> + {#if invoices.length} + <div> + <h1 class="text-secondary text-xl">Invoices</h1> + {#each invoices as { amount, currency, rate, request: { requester, recipient }, hash }} + {@const requested = user.username === requester?.username} + <a + href={`/${recipient.username}/invoice/${hash}` + + (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> - {/if} - {#if received.length} - <div> - <h1 class="text-secondary text-xl">Requests</h1> + <div class="text-secondary">{sat(amount)}</div> + </div> + </div> + </div> + </a> + {/each} + </div> + {/if} + {#if received.length} + <div> + <h1 class="text-secondary text-xl">Requests</h1> - {#each received 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> - {/if} + {#each received 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> + {/if} - {#if latest.length && user?.id === subject.id} - <div class="relative"> - {#each latest as { content, pubkey, author, recipient }} - {@const user = author.id === user.id ? recipient : author} - <a href={`/${user.username}/messages`}> - <div class="flex hover:bg-gray-100 p-4 rounded-2xl"> - <div class="my-auto"> - <Avatar {user} size={'20'} disabled={true} /> - </div> - <div class="my-auto truncate"> - <div class="my-auto ml-1 text-lg font-bold">{user.username}</div> - <div class="my-auto ml-1 text-secondary text-lg truncate"> - {content} - </div> - </div> - </div> - </a> - {/each} - </div> - {/if} + {#if latest.length && user?.id === subject.id} + <div class="relative"> + {#each latest as { content, pubkey, author, recipient }} + {@const user = author.id === user.id ? recipient : author} + <a href={`/${user.username}/messages`}> + <div class="flex hover:bg-gray-100 p-4 rounded-2xl"> + <div class="my-auto"> + <Avatar {user} size={"20"} disabled={true} /> + </div> + <div class="my-auto truncate"> + <div class="my-auto ml-1 text-lg font-bold">{user.username}</div> + <div class="my-auto ml-1 text-secondary text-lg truncate"> + {content} + </div> + </div> + </div> + </a> + {/each} + </div> + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js b/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js index 6182de17..ec4da865 100644 --- a/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js @@ -1,12 +1,12 @@ -import { get } from '$lib/utils'; -import { error } from '@sveltejs/kit'; +import { get } from "$lib/utils"; +import { error } from "@sveltejs/kit"; export async function load({ params: { id } }) { - try { - let event = await get(`/event/${id}`); - if (!event) throw error(500, 'Event not found'); - return { event }; - } catch (e) { - throw error(500, e.message); - } + try { + let event = await get(`/event/${id}`); + if (!event) throw error(500, "Event not found"); + return { event }; + } catch (e) { + throw error(500, e.message); + } } diff --git a/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte b/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte index 85fc815a..1347947f 100644 --- a/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/event/[id]/+page.svelte @@ -1,7 +1,7 @@ <script> - import { Event } from '$comp'; - export let data; - let { event } = data; + import { Event } from "$comp"; + export let data; + let { event } = data; </script> <Event {event} /> diff --git a/src/routes/(app)/[username]/(profile)/followers/+page.svelte b/src/routes/(app)/[username]/(profile)/followers/+page.svelte index 56b3ba16..f51af86b 100644 --- a/src/routes/(app)/[username]/(profile)/followers/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/followers/+page.svelte @@ -1,51 +1,52 @@ <script> - import { t } from '$lib/translations'; - import { Avatar } from '$comp'; - import { browser } from '$app/environment'; - import VirtualScroll from 'svelte-virtual-scroll-list'; + import { t } from "$lib/translations"; + import { Avatar } from "$comp"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; - export let data; - let { - subject: { followers } - } = data; + export let data; + let { + subject: { followers }, + } = data; - let w; + let w; </script> <svelte:window bind:innerWidth={w} /> <div class="px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('user.followers')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("user.followers")} + </h1> - {#if browser} - {#if followers.length} - <VirtualScroll data={followers} key="pubkey" let:data pageMode={true}> - <a href={`/${data.pubkey}`}> - <div - class="flex border-b last:border-b-0 py-4 text-sm lg:text-lg text-secondary" - :key={data.pubkey} - > - <div class="mb-auto mr-2"> - <div class="md:hidden"> - <Avatar size={12} user={data} disabled={true} /> - </div> - <div class="hidden md:block"> - <Avatar size={20} user={data} disabled={true} /> - </div> - </div> + {#if browser} + {#if followers.length} + <VirtualScroll data={followers} key="pubkey" let:data pageMode={true}> + <a href={`/${data.pubkey}`}> + <div + class="flex border-b last:border-b-0 py-4 text-sm lg:text-lg text-secondary" + :key={data.pubkey} + > + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} user={data} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} user={data} disabled={true} /> + </div> + </div> - <div class="w-full flex pb-1 text-black my-auto"> - <div> - {data.display_name || ''} <span class="text-secondary">@{data.username}</span> - </div> - </div> - </div> - </a> - </VirtualScroll> - {:else} - <div>No followers</div> - {/if} - {/if} + <div class="w-full flex pb-1 text-black my-auto"> + <div> + {data.display_name || ""} + <span class="text-secondary">@{data.username}</span> + </div> + </div> + </div> + </a> + </VirtualScroll> + {:else} + <div>No followers</div> + {/if} + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/follows/+page.svelte b/src/routes/(app)/[username]/(profile)/follows/+page.svelte index b238c3a7..4f1bf32b 100644 --- a/src/routes/(app)/[username]/(profile)/follows/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/follows/+page.svelte @@ -1,47 +1,51 @@ <script> - import { t } from '$lib/translations'; - import { Avatar } from '$comp'; - import { browser } from '$app/environment'; - import VirtualScroll from 'svelte-virtual-scroll-list'; + import { t } from "$lib/translations"; + import { Avatar } from "$comp"; + import { browser } from "$app/environment"; + import VirtualScroll from "svelte-virtual-scroll-list"; - export let data; - let { - subject: { follows } - } = data; - let w; + export let data; + let { + subject: { follows }, + } = data; + let w; </script> <svelte:window bind:innerWidth={w} /> <div class="px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('user.following')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("user.following")} + </h1> - {#if browser} - {#if follows.length} - <VirtualScroll data={follows} key="pubkey" let:data pageMode={true}> - <a href={`/${data.pubkey}`}> - <div class="flex py-4 text-sm lg:text-lg text-secondary" :key={data.pubkey}> - <div class="mb-auto mr-2"> - <div class="md:hidden"> - <Avatar size={12} user={data} disabled={true} /> - </div> - <div class="hidden md:block"> - <Avatar size={20} user={data} disabled={true} /> - </div> - </div> + {#if browser} + {#if follows.length} + <VirtualScroll data={follows} key="pubkey" let:data pageMode={true}> + <a href={`/${data.pubkey}`}> + <div + class="flex py-4 text-sm lg:text-lg text-secondary" + :key={data.pubkey} + > + <div class="mb-auto mr-2"> + <div class="md:hidden"> + <Avatar size={12} user={data} disabled={true} /> + </div> + <div class="hidden md:block"> + <Avatar size={20} user={data} disabled={true} /> + </div> + </div> - <div class="w-full flex pb-1 text-black my-auto"> - <div> - {data.display_name || ''} <span class="text-secondary">@{data.username}</span> - </div> - </div> - </div> - </a> - </VirtualScroll> - {:else} - <div>No follows</div> - {/if} - {/if} + <div class="w-full flex pb-1 text-black my-auto"> + <div> + {data.display_name || ""} + <span class="text-secondary">@{data.username}</span> + </div> + </div> + </div> + </a> + </VirtualScroll> + {:else} + <div>No follows</div> + {/if} + {/if} </div> diff --git a/src/routes/(app)/[username]/(profile)/request/+page.server.js b/src/routes/(app)/[username]/(profile)/request/+page.server.js index e7ad6a8f..754c5805 100644 --- a/src/routes/(app)/[username]/(profile)/request/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/request/+page.server.js @@ -1,15 +1,15 @@ -import { redirect } from '@sveltejs/kit'; -import { fd, auth, post } from '$lib/utils'; +import { redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; export const actions = { - create: async ({ cookies, params, request, url }) => { - let form = await fd(request); - let { id, requester } = await post('/requests', form, auth(cookies)); - throw redirect(307, `/${params.username}/request/${id}`); - }, + create: async ({ cookies, params, request, url }) => { + let form = await fd(request); + let { id, requester } = await post("/requests", form, auth(cookies)); + throw redirect(307, `/${params.username}/request/${id}`); + }, - delete: async ({ cookies, request }) => { - let form = await fd(request); - let r = await post('/requests/delete', form, auth(cookies)); - } + delete: async ({ cookies, request }) => { + let form = await fd(request); + let r = await post("/requests/delete", form, auth(cookies)); + }, }; diff --git a/src/routes/(app)/[username]/(profile)/request/+page.svelte b/src/routes/(app)/[username]/(profile)/request/+page.svelte index 75117ff4..5727c3d4 100644 --- a/src/routes/(app)/[username]/(profile)/request/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/request/+page.svelte @@ -1,35 +1,35 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon } from '$comp'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Icon } from "$comp"; - export let data; + export let data; - let { subject, user } = data; + let { subject, user } = data; </script> <div class="container max-w-lg px-4 mx-auto mt-10 space-y-5"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('payments.requestAnInvoice')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("payments.requestAnInvoice")} + </h1> - <form action="?/create" method="POST" use:enhance class="space-y-5"> - <input type="hidden" name="requester_id" value={user.id} /> - <input type="hidden" name="recipient" value={subject.username} /> + <form action="?/create" method="POST" use:enhance class="space-y-5"> + <input type="hidden" name="requester_id" value={user.id} /> + <input type="hidden" name="recipient" value={subject.username} /> - <textarea - name="memo" - placeholder={$t('payments.describeOrder')} - class="w-full p-4 border rounded-xl h-48" - /> + <textarea + name="memo" + placeholder={$t("payments.describeOrder")} + class="w-full p-4 border rounded-xl h-48" + /> - <div class="w-full flex"> - <button - type="submit" - class="bg-black text-white border rounded-full px-6 py-2 font-bold mx-auto" - > - {$t('payments.submit')} - </button> - </div> - </form> + <div class="w-full flex"> + <button + type="submit" + class="bg-black text-white border rounded-full px-6 py-2 font-bold mx-auto" + > + {$t("payments.submit")} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte b/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte index 4995bfb2..6ce0c9f6 100644 --- a/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/request/sent/+page.svelte @@ -1,18 +1,21 @@ <script> - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; - export let data; - let { subject } = data; + export let data; + let { subject } = data; </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">Request sent</h1> + <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">Request sent</h1> </div> <a href={`/${subject.username}`}> - <div class="opacity-0 w-screen h-screen fixed top-24 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-24 left-0 z-50" /> </a> diff --git a/src/routes/(app)/[username]/+layout.server.js b/src/routes/(app)/[username]/+layout.server.js index 8df75f0f..0e962173 100644 --- a/src/routes/(app)/[username]/+layout.server.js +++ b/src/routes/(app)/[username]/+layout.server.js @@ -1,13 +1,13 @@ -import { get } from '$lib/utils'; -import { error } from '@sveltejs/kit'; +import { get } from "$lib/utils"; +import { error } from "@sveltejs/kit"; export let load = async ({ depends, params: { username } }) => { - depends('app:user'); + depends("app:user"); - try { - let subject = await get(`/users/${username}`); - return { subject }; - } catch (e) { - throw error(500, 'Unable to retrieve user account data'); - } + try { + let subject = await get(`/users/${username}`); + return { subject }; + } catch (e) { + throw error(500, "Unable to retrieve user account data"); + } }; diff --git a/src/routes/(app)/[username]/+layout.svelte b/src/routes/(app)/[username]/+layout.svelte index 54fcd600..3a351e48 100644 --- a/src/routes/(app)/[username]/+layout.svelte +++ b/src/routes/(app)/[username]/+layout.svelte @@ -1,57 +1,57 @@ <script> - import { AppHeader } from '$comp'; - import { onNavigate } from '$app/navigation'; - export let data; + import { AppHeader } from "$comp"; + import { onNavigate } from "$app/navigation"; + export let data; - onNavigate((navigation) => { - if (!document.startViewTransition) return; + onNavigate((navigation) => { + if (!document.startViewTransition) return; - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); - }); - }); + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; + }); + }); + }); </script> <AppHeader {data} /> <div class="content"> - <slot /> + <slot /> </div> <style> - @keyframes fade-in { - from { - opacity: 0; - } - } + @keyframes fade-in { + from { + opacity: 0; + } + } - @keyframes fade-out { - to { - opacity: 0; - } - } + @keyframes fade-out { + to { + opacity: 0; + } + } - @keyframes slide-from-right { - from { - transform: translateX(60px); - } - } + @keyframes slide-from-right { + from { + transform: translateX(60px); + } + } - @keyframes slide-to-left { - to { - transform: translateX(-60px); - } - } + @keyframes slide-to-left { + to { + transform: translateX(-60px); + } + } - :root::view-transition-old(root) { - animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; - } + :root::view-transition-old(root) { + animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; + } - :root::view-transition-new(root) { - animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, - 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; - } + :root::view-transition-new(root) { + animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in, + 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; + } </style> diff --git a/src/routes/(app)/[username]/address/+page.server.js b/src/routes/(app)/[username]/address/+page.server.js index 247a33a2..1a782f9d 100644 --- a/src/routes/(app)/[username]/address/+page.server.js +++ b/src/routes/(app)/[username]/address/+page.server.js @@ -1,10 +1,10 @@ -import { auth, get, post } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get, post } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, params, parent }) { - let user = (await parent()).subject; - let rates = await get('/rates'); - let invoice = { type: 'bitcoin' }; - let { hash } = await post(`/invoice`, { invoice, user }, auth(cookies)); - throw redirect(307, `/${user.username}/invoice/${hash}`); + let user = (await parent()).subject; + let rates = await get("/rates"); + let invoice = { type: "bitcoin" }; + let { hash } = await post(`/invoice`, { invoice, user }, auth(cookies)); + throw redirect(307, `/${user.username}/invoice/${hash}`); } diff --git a/src/routes/(app)/[username]/card/[serial]/+page.server.js b/src/routes/(app)/[username]/card/[serial]/+page.server.js index 40e375aa..20b657a1 100644 --- a/src/routes/(app)/[username]/card/[serial]/+page.server.js +++ b/src/routes/(app)/[username]/card/[serial]/+page.server.js @@ -1,16 +1,16 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from "@sveltejs/kit" +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export let load = async ({ cookies, params }) => { - let { serial } = params; - let fund; - try { - fund = await get(`/fund/${serial}`, auth(cookies)); - } catch (e) { - console.log(e); - } + let { serial } = params; + let fund; + try { + fund = await get(`/fund/${serial}`, auth(cookies)); + } catch (e) { + console.log(e); + } - if (fund) throw redirect(307, `/fund/${serial}`); + if (fund) throw redirect(307, `/fund/${serial}`); - return params; + return params; }; diff --git a/src/routes/(app)/[username]/card/[serial]/+page.svelte b/src/routes/(app)/[username]/card/[serial]/+page.svelte index afdaf65d..a2e17dfc 100644 --- a/src/routes/(app)/[username]/card/[serial]/+page.svelte +++ b/src/routes/(app)/[username]/card/[serial]/+page.svelte @@ -1,35 +1,35 @@ <script> - import { Card, Icon } from '$comp'; - export let data; - let { serial } = data; - let balance = 0; + import { Card, Icon } from "$comp"; + export let data; + let { serial } = data; + let balance = 0; </script> <div class="space-y-5"> - <div class="relative flex justify-center gap-2"> - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1> - </div> - <div class="flex justify-center"> - <a href={`/send/pot/${serial}`}> - <button - class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2" - > - <svg - xmlns="http://www.w3.org/2000/svg" - class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto" - fill="none" - viewBox="0 0 24 24" - stroke="currentColor" - > - <path - stroke-linecap="round" - stroke-linejoin="round" - stroke-width="2" - d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" - /> - </svg> - <div class="my-auto">Add Funds</div> - </button> - </a> - </div> + <div class="relative flex justify-center gap-2"> + <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1> + </div> + <div class="flex justify-center"> + <a href={`/send/pot/${serial}`}> + <button + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2" + > + <svg + xmlns="http://www.w3.org/2000/svg" + class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" + > + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" + /> + </svg> + <div class="my-auto">Add Funds</div> + </button> + </a> + </div> </div> diff --git a/src/routes/(app)/[username]/funds/+page.svelte b/src/routes/(app)/[username]/funds/+page.svelte index 6479fb8b..667176e1 100644 --- a/src/routes/(app)/[username]/funds/+page.svelte +++ b/src/routes/(app)/[username]/funds/+page.svelte @@ -1,6 +1,6 @@ <script> - import { Avatar, Icon } from '$comp'; - import { t } from '$lib/translations'; + import { Avatar, Icon } from "$comp"; + import { t } from "$lib/translations"; export let data; @@ -8,49 +8,44 @@ </script> <div class="mt-24 mb-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - Funds - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + Funds + </h1> - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <div class="text-base"> - <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto"> + <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> + <div class="text-base"> + <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> + <div class="whitespace-nowrap my-auto"> + <div class="text-secondary" /> + </div> - <div class="text-secondary"> - - </div> - </div> + <div class="flex my-auto"> + <div class="text-secondary flex"> + <div class="my-auto mr-1"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> - <div class="flex my-auto"> - <div class="text-secondary flex"> - <div class="my-auto mr-1"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> + <div class="my-auto">Pot</div> + </div> + </div> - <div class="my-auto">Pot</div> - </div> - </div> + <div class="text-secondary text-right text-sm my-auto"> + <div /> + </div> + </div> + </div> - <div class="text-secondary text-right text-sm my-auto"> - <div> - - </div> - </div> - </div> - </div> - - <button - class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" - > - <Icon icon="plus" style="opacity-50 mr-2 my-auto" /> - <div class="my-auto">{$t('payments.create')}</div> - </button> - </div> + <button + class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" + > + <Icon icon="plus" style="opacity-50 mr-2 my-auto" /> + <div class="my-auto">{$t("payments.create")}</div> + </button> + </div> </div> <style> - .active * { - @apply bg-black text-white border-black; - } + .active * { + @apply bg-black text-white border-black; + } </style> diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index b9274434..4ebb5eae 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -1,62 +1,62 @@ <script> - import { fail, post, wait } from '$lib/utils'; - import { onMount } from 'svelte'; - import { goto, invalidate } from '$app/navigation'; - import { Pin } from '$comp'; - import { loginRedirect, pin, password, passwordPrompt } from '$lib/store'; - import { generate } from '$lib/nostr'; - import { browser } from '$app/environment'; + import { fail, post, wait } from "$lib/utils"; + import { onMount } from "svelte"; + import { goto, invalidate } from "$app/navigation"; + import { Pin } from "$comp"; + import { loginRedirect, pin, password, passwordPrompt } from "$lib/store"; + import { generate } from "$lib/nostr"; + import { browser } from "$app/environment"; - export let data; - let { user } = data; + export let data; + let { user } = data; - let loaded; - let gen = async () => { - if (!browser) return; + let loaded; + let gen = async () => { + if (!browser) return; - if (user.pubkey) { - goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); - return; - } + if (user.pubkey) { + goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); + return; + } - if (!$password) $passwordPrompt = true; + if (!$password) $passwordPrompt = true; - try { - await post('/password', { password: $password }); - } catch (e) { - $passwordPrompt = true; - } + try { + await post("/password", { password: $password }); + } catch (e) { + $passwordPrompt = true; + } - await wait(() => $password); + await wait(() => $password); - try { - await generate(user); - } catch (e) { - console.log('error generating', e); - } + try { + await generate(user); + } catch (e) { + console.log("error generating", e); + } - try { - user.pin = $pin; - await post(`/${user.username}/generate`, user); - goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); - } catch (e) { - $pin = ''; - if (e.message?.startsWith('Pin')) { - fail('Wrong pin, try again'); - } else { - fail('Failed to generate keys'); - goto('/'); - } - } - }; + try { + user.pin = $pin; + await post(`/${user.username}/generate`, user); + goto($loginRedirect || `/${user.username}`, { invalidateAll: true }); + } catch (e) { + $pin = ""; + if (e.message?.startsWith("Pin")) { + fail("Wrong pin, try again"); + } else { + fail("Failed to generate keys"); + goto("/"); + } + } + }; - onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50)); + onMount(() => browser && setTimeout(() => (loaded = true) && gen(), 50)); - $: $pin?.length === 6 && gen(user); + $: $pin?.length === 6 && gen(user); </script> {#if loaded && user?.haspin && $pin?.length !== 6} - <Pin /> + <Pin /> {/if} {loaded} {JSON.stringify(user)} diff --git a/src/routes/(app)/[username]/generate/+server.js b/src/routes/(app)/[username]/generate/+server.js index e1df08e3..1dca1ece 100644 --- a/src/routes/(app)/[username]/generate/+server.js +++ b/src/routes/(app)/[username]/generate/+server.js @@ -1,11 +1,11 @@ -import { auth, post } from '$lib/utils'; -import { error, json } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import { error, json } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - try { - let body = await request.json(); - return json(await post(`/user`, body, auth(cookies))); - } catch (e) { - throw error(500, e.message); - } + try { + 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/+server.js b/src/routes/(app)/[username]/invoice/+server.js index 461d380c..5e3d0300 100644 --- a/src/routes/(app)/[username]/invoice/+server.js +++ b/src/routes/(app)/[username]/invoice/+server.js @@ -1,7 +1,7 @@ -import { auth, post } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +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))); + let j = await r.json(); + return json(await post("/invoice", j, auth(c))); }; diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.server.js b/src/routes/(app)/[username]/invoice/[id]/+layout.server.js index 4d7fd3b8..b9d1c43d 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+layout.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/+layout.server.js @@ -1,31 +1,39 @@ -import Qr from 'qrcode-base64'; -import { get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import 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'); + depends("app:invoice"); - let { user } = await parent(); - let { id } = params; - let invoice = await get(`/invoice/${id}`); - let options = !!url.searchParams.get('options'); + let { user } = await parent(); + let { id } = params; + let invoice = await get(`/invoice/${id}`); + let options = !!url.searchParams.get("options"); - if (user && invoice.uid !== user.id && !url.pathname.includes('tip') && !options) { - throw redirect(307, `/send/invoice/${id}`); - } + if ( + user && + invoice.uid !== user.id && + !url.pathname.includes("tip") && + !options + ) { + throw redirect(307, `/send/invoice/${id}`); + } - let { amount, pending, received } = invoice; - amount = parseInt(amount); + let { amount, pending, received } = invoice; + amount = parseInt(amount); - let paid = (!amount && received) || (amount > 0 && (pending >= amount || received >= amount)); - if (paid && !url.pathname.endsWith('paid')) { - throw redirect( - 307, - `/${params.username}/invoice/${id}/paid` + (options ? '?options=true' : '') - ); - } + let paid = + (!amount && received) || + (amount > 0 && (pending >= amount || received >= amount)); + if (paid && !url.pathname.endsWith("paid")) { + throw redirect( + 307, + `/${params.username}/invoice/${id}/paid` + + (options ? "?options=true" : "") + ); + } - let sm = Qr.drawImg(invoice.text || '', { size: 300 }); + let sm = Qr.drawImg(invoice.text || "", { size: 300 }); - return { id, invoice, sm, lg: sm }; + return { id, invoice, sm, lg: sm }; } diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.server.js b/src/routes/(app)/[username]/invoice/[id]/+page.server.js index 1068f273..a57efa64 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/+page.server.js @@ -1,5 +1,5 @@ -import invoice from '$lib/invoice'; +import invoice from "$lib/invoice"; export const actions = { - default: invoice + default: invoice, }; diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/+page.svelte index a0894dd1..b6bf70b7 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/+page.svelte @@ -1,217 +1,235 @@ <script> - import { enhance } from '$app/forms'; - import { send } from '$lib/socket'; - import { 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 } from '$lib/store'; - import { Avatar, Icon, Heart, Image } from '$comp'; - import { t } from '$lib/translations'; - import { goto, invalidate } from '$app/navigation'; + import { enhance } from "$app/forms"; + import { send } from "$lib/socket"; + import { + 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 } from "$lib/store"; + import { Avatar, Icon, Heart, Image } from "$comp"; + import { t } from "$lib/translations"; + import { goto, invalidate } from "$app/navigation"; - export let data; + export let data; - let ndef; + let ndef; - let reading = async ({ message, serialNumber }) => { - let name = serialNumber.replace(/:/g, '-'); - let result = await post(`/fund/${name}/withdraw`, { amount, hash, name }); - }; + 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); + let readingerror = (e) => console.log("nfc error", e); - $: refresh(data); - let { invoice, id, user, sm, lg } = data; - let { - amount, - hash, - type, - rate, - received, - prompt, - text, - tip, - request_id, - user: { username, currency } - } = invoice; + $: refresh(data); + let { invoice, id, user, sm, lg } = data; + let { + amount, + hash, + type, + rate, + received, + prompt, + text, + tip, + request_id, + user: { username, currency }, + } = invoice; - $: showQr = invoice.uid === user?.id; + $: showQr = invoice.uid === user?.id; - $: src = sm; + $: src = sm; - let qr; - let tipPercent = 0; + let qr; + let tipPercent = 0; - let refresh = async (data) => { - ({ invoice, id, user, sm, lg } = data); - ({ - amount, - hash, - type, - rate, - received, - prompt, - text, - tip, - user: { username, currency } - } = invoice); + let refresh = async (data) => { + ({ invoice, id, user, sm, lg } = data); + ({ + amount, + hash, + type, + rate, + received, + prompt, + text, + tip, + user: { username, currency }, + } = invoice); - tipPercent = (tip / amount) * 100; + tipPercent = (tip / amount) * 100; - // if (browser && window.NDEFReader) { - // try { - // ndef = new NDEFReader(); - // await ndef.scan(); - // - // ndef.removeEventListener('readingerror', readingerror); - // ndef.removeEventListener('reading', reading); - // - // ndef.addEventListener('readingerror', readingerror); - // ndef.addEventListener('reading', reading); - // } catch (e) { - // console.log(e); - // } - // } - }; + // if (browser && window.NDEFReader) { + // try { + // ndef = new NDEFReader(); + // await ndef.scan(); + // + // ndef.removeEventListener('readingerror', readingerror); + // ndef.removeEventListener('reading', reading); + // + // ndef.addEventListener('readingerror', readingerror); + // ndef.addEventListener('reading', reading); + // } catch (e) { + // console.log(e); + // } + // } + }; - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - $: tipAmount = ((tip * rate) / sats).toFixed(2); + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $: tipAmount = ((tip * rate) / sats).toFixed(2); - let subbed; + let subbed; - onMount(async () => { - if (browser) { - last.subscribe((v) => { - if (!v || subbed) return; - subbed = true; - send('subscribe', invoice); - }); - } - }); + onMount(async () => { + if (browser) { + last.subscribe((v) => { + if (!v || subbed) return; + subbed = true; + send("subscribe", invoice); + }); + } + }); - onDestroy(() => { - if (ndef) { - ndef.removeEventListener('readingerror', readingerror); - ndef.removeEventListener('reading', reading); - } - }); + onDestroy(() => { + if (ndef) { + ndef.removeEventListener("readingerror", readingerror); + ndef.removeEventListener("reading", reading); + } + }); - $: link = type === 'bitcoin' ? text : `lightning:${text}`; - $: txt = type === 'bitcoin' ? hash : text; + $: link = type === "bitcoin" ? text : `lightning:${text}`; + $: txt = type === "bitcoin" ? hash : text; - let toggleType = async () => { - invoice.type = invoice.type === types.lightning ? types.bitcoin : types.lightning; - ({ id } = await post(`/${username}/invoice`, { - invoice, - user: { username, currency } - })); + let toggleType = async () => { + invoice.type = + invoice.type === types.lightning ? types.bitcoin : types.lightning; + ({ id } = await post(`/${username}/invoice`, { + invoice, + user: { username, currency }, + })); - goto(`./${id}`, { invalidateAll: true }); - }; + goto(`./${id}`, { invalidateAll: true }); + }; </script> <div class="container mx-auto max-w-lg px-4 space-y-8"> - <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" - class:bg-black={type === types.lightning} - class:text-white={type === types.lightning} - on:click={toggleType} - > - ⚡️ Lightning - </button> + <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" + class:bg-black={type === types.lightning} + class:text-white={type === types.lightning} + on:click={toggleType} + > + ⚡️ Lightning + </button> - <button - class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full flex justify-center " - class:bg-black={type === types.bitcoin} - class:text-white={type === types.bitcoin} - on:click={toggleType} - > - <img - src="/images/bitcoin.svg" - class="my-auto w-8 border-4 border-transparent" - alt="Bitcoin" - /> - <div class="my-auto">Bitcoin</div> - </button> - </div> + <button + class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full flex justify-center" + class:bg-black={type === types.bitcoin} + class:text-white={type === types.bitcoin} + on:click={toggleType} + > + <img + src="/images/bitcoin.svg" + class="my-auto w-8 border-4 border-transparent" + alt="Bitcoin" + /> + <div class="my-auto">Bitcoin</div> + </button> + </div> - {#if showQr} - <div> - <a href={invoice.type === 'bitcoin' ? invoice.text : 'lightning:' + invoice.text}> - <div class="relative flex"> - <div class="flex mx-auto w-[360px] h-[360px]"> - <img - {src} - class="w-[300px] h-[300px] mx-auto z-10 mt-[20px]" - bind:this={qr} - alt={txt} - /> - </div> - <div - class="absolute m-auto left-0 right-0 w-[340px] h-[340px] rounded-full bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] z-0" - /> - </div> - </a> - </div> - {/if} + {#if showQr} + <div> + <a + href={invoice.type === "bitcoin" + ? invoice.text + : "lightning:" + invoice.text} + > + <div class="relative flex"> + <div class="flex mx-auto w-[360px] h-[360px]"> + <img + {src} + class="w-[300px] h-[300px] mx-auto z-10 mt-[20px]" + bind:this={qr} + alt={txt} + /> + </div> + <div + class="absolute m-auto left-0 right-0 w-[340px] h-[340px] rounded-full bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] z-0" + /> + </div> + </a> + </div> + {/if} - {#if amount > 0} - <div class="text-center font-bold text-2xl"> - <div> - {f(amountFiat, currency)} + {#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-lg">{`${sat(amount)}`}</span> + {#if tip} + <span class="text-sm"> + +{f(tip * (rate / sats), currency)} + </span> + {/if} + </div> + <div> + <span class="text-secondary font-normal text-lg" + >{`${sat(amount)}`}</span + > - {#if tip} - <span class="text-sm text-secondary font-normal"> - +{sat(tip)} - </span> - {/if} - </div> - </div> - {/if} + {#if tip} + <span class="text-sm text-secondary font-normal"> + +{sat(tip)} + </span> + {/if} + </div> + </div> + {/if} - <div class="text-center break-all"> - <a href={link}> - {txt} - </a> - </div> + <div class="text-center break-all"> + <a href={link}> + {txt} + </a> + </div> - <div class="w-full flex justify-center gap-2 flex-wrap"> - <a href={link} class="w-full md:w-auto"> - <button - class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" - > - <Icon icon="mobile" style="mr-1 w-6" /> - <div class="text-secondary">{$t('payments.openLink')}</div> - </button> - </a> + <div class="w-full flex justify-center gap-2 flex-wrap"> + <a href={link} class="w-full md:w-auto"> + <button + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + > + <Icon icon="mobile" style="mr-1 w-6" /> + <div class="text-secondary">{$t("payments.openLink")}</div> + </button> + </a> - <button - class="flex rounded-full justify-center border py-3 px-5 hover:opacity-80 w-full md:w-auto" - on:click={() => copy(txt)} - > - <Icon icon="copy" style="mr-1" /> - <div class="text-secondary">{$t('payments.copyText')}</div></button - > + <button + class="flex rounded-full justify-center border py-3 px-5 hover:opacity-80 w-full md:w-auto" + on:click={() => copy(txt)} + > + <Icon icon="copy" style="mr-1" /> + <div class="text-secondary">{$t("payments.copyText")}</div></button + > - <button - class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" - on:click={() => (showQr = !showQr)} - > - <Icon icon="qr" style="mr-1 invert" /> - <div class="text-secondary"> - {showQr ? $t('payments.hide') : $t('payments.show')} - {$t('payments.qr')} - </div></button - > - </div> + <button + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + on:click={() => (showQr = !showQr)} + > + <Icon icon="qr" style="mr-1 invert" /> + <div class="text-secondary"> + {showQr ? $t("payments.hide") : $t("payments.show")} + {$t("payments.qr")} + </div></button + > + </div> </div> diff --git a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte index 84e493d9..655bc3e4 100644 --- a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte @@ -1,64 +1,70 @@ <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 { fiat, f, s, sat, sats } from '$lib/utils'; + 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 { fiat, f, s, sat, sats } from "$lib/utils"; - export let data; - let amount, currency, rate, received, pending, tip, user, id; - $: data && ({ amount, currency, rate, received, pending, tip, user, id } = data.invoice); + export let data; + let amount, currency, rate, received, pending, tip, user, id; + $: data && + ({ amount, currency, rate, received, pending, tip, user, id } = + data.invoice); - toast.pop(0); + toast.pop(0); </script> <div class="container px-4 text-center mx-auto"> - <div class="flex w-full py-5 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - {#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 - 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)} + <div class="flex w-full py-5 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> + <Icon icon="check" style="mx-auto" /> + </div> + {#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 - 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 tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> + {/if} - {#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 - 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} + {#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 - 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}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <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> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js index 814900fb..c9377e36 100644 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js @@ -1,11 +1,12 @@ -import invoice from '$lib/invoice'; -import { redirect } from '@sveltejs/kit'; +import invoice from "$lib/invoice"; +import { redirect } from "@sveltejs/kit"; export async function load({ parent, params }) { - let { invoice } = await parent(); - if (!invoice.amount) throw redirect(307, `/${params.username}/invoice/${invoice.id}`); + let { invoice } = await parent(); + if (!invoice.amount) + throw redirect(307, `/${params.username}/invoice/${invoice.id}`); } export const actions = { - default: invoice + default: invoice, }; diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte index fe6c67be..87e48f01 100644 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte @@ -1,201 +1,203 @@ <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, Slider } from '$comp'; - import { goto } from '$app/navigation'; - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { tick } from 'svelte'; + 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, Slider } from "$comp"; + 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; + const tipAmounts = ["No", "10%", "15%", "20%"]; + let showCustomAmount; - let customInput; - let customTipAmount; + let customInput; + let customTipAmount; - let apply = async () => { - if (customTipAmount > 0) { - tipPercent = (customTipAmount / amountFiat) * 100; - showCustomAmount = false; - } - }; + let apply = async () => { + if (customTipAmount > 0) { + tipPercent = (customTipAmount / amountFiat) * 100; + showCustomAmount = false; + } + }; - let active = (amount) => - !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2)); + let active = (amount) => + !customTipAmount && Math.round(tipPercent) === parseInt(amount.slice(0, 2)); - const handleCustomTipAmount = (e) => { - tipPercent = 0; - customTipAmount = e.target.value; - }; + const handleCustomTipAmount = (e) => { + tipPercent = 0; + customTipAmount = e.target.value; + }; - let timeout; - const handleSlide = (e) => { - customTipAmount = ''; - if (customInput) { - customInput.value = ''; - } - tipPercent = 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)); - } + let submitting; + const handleTipButtonClick = async (amount) => { + submitting = true; + if (amount === "No") { + tipPercent = 0; + } else { + tipPercent = parseInt(amount.slice(0, 2)); + } - await tick(); - submit.click(); - }; + await tick(); + submit.click(); + }; - let submit; + let submit; - export let data; - $: refresh(data); - let { invoice, id, user, rates } = data; - let { - amount, - hash, - type, - rate, - received, - prompt, - text, - tip, - user: { username, currency } - } = invoice; + export let data; + $: refresh(data); + let { invoice, id, user, rates } = data; + let { + amount, + hash, + type, + rate, + received, + prompt, + text, + tip, + user: { username, currency }, + } = invoice; - let qr; - let tipPercent = 0; + let qr; + let tipPercent = 0; - let fullscreen; + let fullscreen; - let refresh = (data) => { - $inv = null; + let refresh = (data) => { + $inv = null; - ({ invoice, id } = data); - ({ - amount, - hash, - type, - received, - prompt, - text, - tip, - user: { username } - } = invoice); + ({ invoice, id } = data); + ({ + amount, + hash, + type, + received, + prompt, + text, + tip, + user: { username }, + } = invoice); - tipPercent = (tip / amount) * 100; - }; + tipPercent = (tip / amount) * 100; + }; - $: currency = user?.currency || invoice.currency; - $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]); + $: currency = user?.currency || invoice.currency; + $: rate = invoice.rate * (rates[currency] / rates[invoice.currency]); - $: tip = Math.round((amount / 100) * tipPercent); + $: tip = Math.round((amount / 100) * tipPercent); - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - $: tipAmount = ((tip * rate) / sats).toFixed(2); + $: tipAmount = ((tip * rate) / sats).toFixed(2); - $: invoiceAmountFiatFormatted = f(amountFiat, currency); + $: invoiceAmountFiatFormatted = f(amountFiat, currency); - onMount(() => ($request = undefined)); + 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="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} /> + <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={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="request_id" value={invoice.request?.id} /> + <input type="hidden" name="request_id" value={invoice.request?.id} /> - <h1 class="text-4xl font-semibold my-8">{$t('invoice.addTipq')}</h1> - {#if !showCustomAmount} - <div class="my-2 text-xl">{Math.round(tipPercent)}%</div> + <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} /> + <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="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> + <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} + {#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} + <div> + <button type="button" on:click={() => (showCustomAmount = true)} + >Custom</button + > + </div> + {/if} - <button type="submit" bind:this={submit} /> - </form> + <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} + {#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; - } + .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; - } + .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)/[username]/invoice/sent/+page.svelte b/src/routes/(app)/[username]/invoice/sent/+page.svelte index ae71eb2e..8583de09 100644 --- a/src/routes/(app)/[username]/invoice/sent/+page.svelte +++ b/src/routes/(app)/[username]/invoice/sent/+page.svelte @@ -1,10 +1,10 @@ <script> - import { Avatar, Icon } from '$comp'; - import { request } from '$lib/store'; + import { Avatar, Icon } from "$comp"; + 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> + <Avatar user={$request.requester} size={12} /> + <h1 class="my-auto"><b>{$request.requester.username}</b> is ready to pay</h1> </div> diff --git a/src/routes/(app)/[username]/map/+page.server.js b/src/routes/(app)/[username]/map/+page.server.js index 7936e88b..616d28f6 100644 --- a/src/routes/(app)/[username]/map/+page.server.js +++ b/src/routes/(app)/[username]/map/+page.server.js @@ -1,12 +1,12 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; // export const prerender = true; export let load = async () => { - let data = { locations: [] }; + let data = { locations: [] }; - try { - data = await get('/locations'); - } catch (e) {} + try { + data = await get("/locations"); + } catch (e) {} - return data; + return data; }; diff --git a/src/routes/(app)/[username]/map/+page.svelte b/src/routes/(app)/[username]/map/+page.svelte index 99c822d3..a2ae657c 100644 --- a/src/routes/(app)/[username]/map/+page.svelte +++ b/src/routes/(app)/[username]/map/+page.svelte @@ -1,8 +1,8 @@ <script> - import { LocationsMap } from '$comp'; - export let data; + import { LocationsMap } from "$comp"; + export let data; - let { locations } = data; + let { locations } = data; </script> <LocationsMap {locations} /> diff --git a/src/routes/(app)/[username]/messages/+page.server.js b/src/routes/(app)/[username]/messages/+page.server.js index ec5c5eee..f0148be7 100644 --- a/src/routes/(app)/[username]/messages/+page.server.js +++ b/src/routes/(app)/[username]/messages/+page.server.js @@ -1,22 +1,25 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; export async function load({ params, parent, url }) { - let { user, subject } = await parent(); - let { since = 0 } = params; + let { user, subject } = await parent(); + let { since = 0 } = params; - let messages = []; + let messages = []; - try { - messages = await get(`/${user.pubkey}/${since}/messages`); + try { + messages = await get(`/${user.pubkey}/${since}/messages`); - messages = messages - .filter((m) => m.recipient?.id === subject.id || m.author?.id === subject.id) - .sort((a, b) => a.created_at - b.created_at); + messages = messages + .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); - } catch (e) { - console.log(`failed to fetch nostr messages`, e); - } + if (user.id === subject.id) + messages = messages.filter((m) => m.recipient.id === m.author.id); + } catch (e) { + console.log(`failed to fetch nostr messages`, e); + } - return { messages, user }; + return { messages, user }; } diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/[username]/messages/+page.svelte index cfa7f741..16b82679 100644 --- a/src/routes/(app)/[username]/messages/+page.svelte +++ b/src/routes/(app)/[username]/messages/+page.svelte @@ -1,171 +1,182 @@ <script> - import { cubicInOut } from 'svelte/easing'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { format } from 'date-fns'; - import { scale, fade, fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { Avatar, Icon } from '$comp'; - import { back, fail, focus } from '$lib/utils'; - import { sign, send, encrypt, decrypt } from '$lib/nostr'; - import { event as e, password } from '$lib/store'; - import { tick, onMount } from 'svelte'; - import { getEventHash } from 'nostr-tools'; + import { cubicInOut } from "svelte/easing"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { format } from "date-fns"; + import { scale, fade, fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { Avatar, Icon } from "$comp"; + import { back, fail, focus } from "$lib/utils"; + import { sign, send, encrypt, decrypt } from "$lib/nostr"; + import { event as e, password } from "$lib/store"; + import { tick, onMount } from "svelte"; + import { getEventHash } from "nostr-tools"; - export let data; + export let data; - function fadeScale(node, { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 }) { - const o = +getComputedStyle(node).opacity; - const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/); - const s = m ? m[1] : 1; - const is = 1 - baseScale; + function fadeScale( + node, + { delay = 0, duration = 200, easing = (x) => x, baseScale = 0 } + ) { + const o = +getComputedStyle(node).opacity; + const m = getComputedStyle(node).transform.match(/scale\(([0-9.]+)\)/); + const s = m ? m[1] : 1; + const is = 1 - baseScale; - return { - delay, - duration, - css: (t) => { - const eased = easing(t); - return `opacity: ${eased * o}; transform: scale(${eased * s * is + baseScale})`; - } - }; - } + return { + delay, + duration, + css: (t) => { + const eased = easing(t); + return `opacity: ${eased * o}; transform: scale(${ + eased * s * is + baseScale + })`; + }, + }; + } - let { messages, subject, user } = data; - let input, pane; + let { messages, subject, user } = data; + let input, pane; - $: initialize($password); - let initialize = async (p) => { - if (!p) return; - await Promise.all( - messages.map(async (event) => (event.message = await decrypt({ event, user }))) - ); + $: initialize($password); + let initialize = async (p) => { + if (!p) return; + await Promise.all( + messages.map( + async (event) => (event.message = await decrypt({ event, user })) + ) + ); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - }; + messages = messages; + tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); + }; - e.subscribe(async (event) => { - let found = ~messages.findIndex((m) => m.id === event?.id); - if (event?.recipient.id === user.id && !found) { - event.message = await decrypt({ event, user }); - messages.push(event); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - } - }); + e.subscribe(async (event) => { + let found = ~messages.findIndex((m) => m.id === event?.id); + if (event?.recipient.id === user.id && !found) { + event.message = await decrypt({ event, user }); + messages.push(event); + messages = messages; + tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); + } + }); - let sent, submitting, message; - let submit = async () => { - submitting = true; + let sent, submitting, message; + let submit = async () => { + submitting = true; - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 4, - tags: [['p', subject.pubkey]] - }; + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 4, + tags: [["p", subject.pubkey]], + }; - event.message = message; + event.message = message; - message = ''; + message = ""; - event.author = user; - event.recipient = subject; + event.author = user; + event.recipient = subject; - messages.push(event); - messages = messages; - tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); + messages.push(event); + messages = messages; + tick().then(() => pane && (pane.scrollTop = pane.scrollHeight)); - try { - event.content = await encrypt({ message: event.message, recipient: subject.pubkey, user }); - event.id = getEventHash(event); - await sign({ event, user }); - await send(event); + try { + event.content = await encrypt({ + message: event.message, + recipient: subject.pubkey, + user, + }); + event.id = getEventHash(event); + await sign({ event, user }); + await send(event); - sent = true; - } catch (e) { - console.log(e); - fail('Failed to send message'); - } + sent = true; + } catch (e) { + console.log(e); + fail("Failed to send message"); + } - message = ''; - submitting = false; - }; + message = ""; + submitting = false; + }; - let keydown = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - submit(); - } - }; + let keydown = (e) => { + if (e.key === "Enter") { + e.preventDefault(); + submit(); + } + }; </script> <div class="container max-w-xl mx-auto px-4 space-y-5"> - <div - class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8" - bind:this={pane} - > - {#each messages as { id, author, message, created_at, pubkey }} - {@const ours = pubkey === user.pubkey} - {@const theirs = !ours} + <div + class="h-[50vh] max-h-[50vh] overflow-y-scroll scrollbar-thin scrollbar-thumb-[#F2F6FC] scrollbar-track-white pr-8" + bind:this={pane} + > + {#each messages as { id, author, message, created_at, pubkey }} + {@const ours = pubkey === user.pubkey} + {@const theirs = !ours} - {#if message} - <div - class="flex gap-2" - class:flex-row-reverse={theirs} - class:justify-end={theirs} - in:fadeScale={{ - easing: cubicInOut, - baseScale: 0.5 - }} - > - <div - class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg" - class:ours - class:theirs - :key={id} - > - {message} - </div> - <div class="mt-auto"> - <Avatar user={author} size={'12'} /> - </div> - </div> - <div class="text-sm text-gray-400 mb-6" class:text-right={ours}> - {format(new Date(created_at * 1000), 'MMM d')}, - {format(new Date(created_at * 1000), 'h:mm aa')} - </div> - {/if} - {/each} - </div> + {#if message} + <div + class="flex gap-2" + class:flex-row-reverse={theirs} + class:justify-end={theirs} + in:fadeScale={{ + easing: cubicInOut, + baseScale: 0.5, + }} + > + <div + class="rounded-2xl px-4 py-2 max-w-[300px] mb-1 text-lg" + class:ours + class:theirs + :key={id} + > + {message} + </div> + <div class="mt-auto"> + <Avatar user={author} size={"12"} /> + </div> + </div> + <div class="text-sm text-gray-400 mb-6" class:text-right={ours}> + {format(new Date(created_at * 1000), "MMM d")}, + {format(new Date(created_at * 1000), "h:mm aa")} + </div> + {/if} + {/each} + </div> - <form - method="POST" - class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2" - on:submit|preventDefault={submit} - > - <input type="hidden" name="requester_id" value={user.id} /> - <input type="hidden" name="recipient" value={subject.username} /> + <form + method="POST" + class="w-full border rounded-xl outline-none gap-4 flex p-0 pr-2" + on:submit|preventDefault={submit} + > + <input type="hidden" name="requester_id" value={user.id} /> + <input type="hidden" name="recipient" value={subject.username} /> - <div - use:focus - contenteditable - class="grow break-all py-4 outline-none mt-0 pl-4" - bind:innerHTML={message} - on:keydown={keydown} - /> - <button type="submit" class="my-auto shrink-0"> - <Icon icon="send" style="w-8" /> - </button> - </form> + <div + use:focus + contenteditable + class="grow break-all py-4 outline-none mt-0 pl-4" + bind:innerHTML={message} + on:keydown={keydown} + /> + <button type="submit" class="my-auto shrink-0"> + <Icon icon="send" style="w-8" /> + </button> + </form> </div> <style> - .ours { - @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto; - } + .ours { + @apply bg-gradient-to-r from-[#F2F6FC] to-[#E1E3FF] text-black rounded-br-none ml-auto; + } - .theirs { - @apply bg-gray-100 text-black rounded-bl-none; - } + .theirs { + @apply bg-gray-100 text-black rounded-bl-none; + } </style> diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.server.js b/src/routes/(app)/[username]/payments/[...page]/+page.server.js index a3b0ad6f..7a3bc179 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.server.js +++ b/src/routes/(app)/[username]/payments/[...page]/+page.server.js @@ -1,26 +1,26 @@ -import { get, auth } from '$lib/utils'; +import { get, auth } from "$lib/utils"; let limit = 25; export async function load({ cookies, depends, params, request }) { - depends('app:payments'); + depends("app:payments"); - let parts = params.page.split('/'); + let parts = params.page.split("/"); - let start, end, page; - if (parts.length === 1) [page] = parts; - if (parts.length === 2) [start, page] = parts; - if (parts.length === 3) [start, end, page] = parts; - if (!parseInt(page)) page = 1; + let start, end, page; + if (parts.length === 1) [page] = parts; + if (parts.length === 2) [start, page] = parts; + if (parts.length === 3) [start, end, page] = parts; + if (!parseInt(page)) page = 1; - let offset = (page - 1) * limit; - if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + let offset = (page - 1) * limit; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`; - if (end) url += `&end=${end * 1000}`; + let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`; + if (end) url += `&end=${end * 1000}`; - let { count, payments, totals } = await get(url, auth(cookies)); - let pages = new Array(Math.ceil(count / limit)); + let { count, payments, totals } = await get(url, auth(cookies)); + let pages = new Array(Math.ceil(count / limit)); - return { payments, page, pages, start, end, totals }; + return { payments, page, pages, start, end, totals }; } diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte index 2ea225ad..e61e156c 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte +++ b/src/routes/(app)/[username]/payments/[...page]/+page.svelte @@ -1,249 +1,281 @@ <script> - import { Avatar, Icon } from '$comp'; - import { onMount } from 'svelte'; - import { format } from 'date-fns'; - import { newPayment } from '$lib/store'; - import { t } from '$lib/translations'; - import { get, f, s, sat, sats, types } from '$lib/utils'; - import { page } from '$app/stores'; - import { differenceInDays, getUnixTime, sub } from 'date-fns'; - import { goto, invalidate } from '$app/navigation'; + import { Avatar, Icon } from "$comp"; + import { onMount } from "svelte"; + import { format } from "date-fns"; + import { newPayment } from "$lib/store"; + import { t } from "$lib/translations"; + import { get, f, s, sat, sats, types } from "$lib/utils"; + import { page } from "$app/stores"; + import { differenceInDays, getUnixTime, sub } from "date-fns"; + import { goto, invalidate } from "$app/navigation"; - export let data; + export let data; - let { start, end, user, rates, payments } = data; + let { start, end, user, rates, payments } = data; - let change = ({ target: { value } }) => goto(value); - let link = (p) => { - if (p.pot) return `/pot/${p.pot}`; - if (p.with) return '/' + p.with.username; - return `/p/${p.id}`; - }; + let change = ({ target: { value } }) => goto(value); + let link = (p) => { + if (p.pot) return `/pot/${p.pot}`; + if (p.with) return "/" + p.with.username; + return `/p/${p.id}`; + }; - $: presets = [ - { title: $t('payments.day'), start: sub(new Date(), { days: 1 }), end: null }, - { title: $t('payments.week'), start: sub(new Date(), { days: 7 }), end: null }, - { title: $t('payments.month'), start: sub(new Date(), { months: 1 }), end: null }, - { title: $t('payments.all'), start: sub(new Date(), { years: 5 }), end: null } - ]; + $: presets = [ + { + title: $t("payments.day"), + start: sub(new Date(), { days: 1 }), + end: null, + }, + { + title: $t("payments.week"), + start: sub(new Date(), { days: 7 }), + end: null, + }, + { + title: $t("payments.month"), + start: sub(new Date(), { months: 1 }), + end: null, + }, + { + title: $t("payments.all"), + start: sub(new Date(), { years: 5 }), + end: null, + }, + ]; - $: selection = start - ? presets.findIndex((p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1) - : 0; + $: selection = start + ? presets.findIndex( + (p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1 + ) + : 0; - let p, - totals, - pages = []; - $: data && ({ page: p, pages, start, end, totals, payments } = data); + let p, + totals, + pages = []; + $: data && ({ page: p, pages, start, end, totals, payments } = data); - $: $page && ($newPayment = false); - $: $newPayment && invalidate(`/users/${user.username}`); + $: $page && ($newPayment = false); + $: $newPayment && invalidate(`/users/${user.username}`); - $: path = $page.params.page - ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf('/')) - : $page.url.pathname; + $: path = $page.params.page + ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf("/")) + : $page.url.pathname; - let csv = async () => { - let url = `/${user.username}/payments`; - if (start) url += `/${start}`; - if (end) url += `/${end}`; + let csv = async () => { + let url = `/${user.username}/payments`; + if (start) url += `/${start}`; + if (end) url += `/${end}`; - let { payments } = await get(url); + let { payments } = await get(url); - payments = payments.map((p) => ({ - ...p, - created: new Date(p.created), - total: p.amount + (p.tip || 0), - amount_fiat: f((p.amount * p.rate) / sats, p.currency), - fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null, - tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null, - total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency) - })); + payments = payments.map((p) => ({ + ...p, + created: new Date(p.created), + total: p.amount + (p.tip || 0), + amount_fiat: f((p.amount * p.rate) / sats, p.currency), + fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null, + tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null, + total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency), + })); - let keys = [ - 'type', - 'id', - 'created', - 'rate', - 'currency', - 'amount', - 'tip', - 'total', - 'fee', - 'amount_fiat', - 'tip_fiat', - 'total_fiat', - 'fee_fiat' - ]; + let keys = [ + "type", + "id", + "created", + "rate", + "currency", + "amount", + "tip", + "total", + "fee", + "amount_fiat", + "tip_fiat", + "total_fiat", + "fee_fiat", + ]; - let csv = - keys.map((k) => `"${k}"`).join(',') + - '\n' + - payments.map((r) => keys.map((k) => `"${r[k] || ''}"`).join(',')).join('\n'); + let csv = + keys.map((k) => `"${k}"`).join(",") + + "\n" + + payments + .map((r) => keys.map((k) => `"${r[k] || ""}"`).join(",")) + .join("\n"); - let filename = 'payments.csv'; - let blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); - if (navigator.msSaveBlob) { - navigator.msSaveBlob(blob, filename); - } else { - let link = document.createElement('a'); - if (link.download !== undefined) { - let url = URL.createObjectURL(blob); - link.setAttribute('href', url); - link.setAttribute('download', filename); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - } - }; + let filename = "payments.csv"; + let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + if (navigator.msSaveBlob) { + navigator.msSaveBlob(blob, filename); + } else { + let link = document.createElement("a"); + if (link.download !== undefined) { + let url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + }; </script> <div class="mt-24 mb-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t('payments.header')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t("payments.header")} + </h1> - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> - <div class="flex text-md text-secondary relative"> - <div class="mx-auto flex justify-center w-full gap-1"> - {#each presets as { start, end, title }, i} - <a - class:active={selection === i} - href={`/${user.username}/payments/${getUnixTime(start) + '/'}1`} - > - <button - class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]" - > - <div class="my-auto">{title}</div> - </button> - </a> - {/each} - </div> - </div> + <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> + <div class="flex text-md text-secondary relative"> + <div class="mx-auto flex justify-center w-full gap-1"> + {#each presets as { start, end, title }, i} + <a + class:active={selection === i} + href={`/${user.username}/payments/${getUnixTime(start) + "/"}1`} + > + <button + class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]" + > + <div class="my-auto">{title}</div> + </button> + </a> + {/each} + </div> + </div> - <div class="flex flex-wrap justify-center mb-8"> - {#if pages.length > 1} - {#each pages as _, i} - <a class="mr-1 last:mr-0" href={`${path}/${i + 1}`} class:active={parseInt(p) === i + 1}> - <div class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center"> - {i + 1} - </div> - </a> - {/each} - {/if} - </div> + <div class="flex flex-wrap justify-center mb-8"> + {#if pages.length > 1} + {#each pages as _, i} + <a + class="mr-1 last:mr-0" + href={`${path}/${i + 1}`} + class:active={parseInt(p) === i + 1} + > + <div + class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center" + > + {i + 1} + </div> + </a> + {/each} + {/if} + </div> - <div class="text-base"> - {#each payments as p} - <a href={`/payment/${p.id}`}> - <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.rate / sats), p.currency)} - {#if p.tip} - <span class="text-sm text-secondary"> - +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% - </span> - {/if} - </div> + <div class="text-base"> + {#each payments as p} + <a href={`/payment/${p.id}`}> + <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.rate / sats), p.currency)} + {#if p.tip} + <span class="text-sm text-secondary"> + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% + </span> + {/if} + </div> - <div class="text-secondary"> - {sat(Math.abs(p.amount) + (p.tip || 0))} - </div> - </div> + <div class="text-secondary"> + {sat(Math.abs(p.amount) + (p.tip || 0))} + </div> + </div> - <div class="flex my-auto"> - {#if p.type === types.pot} - <a href={`/pot/${p.memo}`}> - <div class="text-secondary flex"> - <div class="my-auto mr-1"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> + <div class="flex my-auto"> + {#if p.type === types.pot} + <a href={`/pot/${p.memo}`}> + <div class="text-secondary flex"> + <div class="my-auto mr-1"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> - <div class="my-auto">Pot</div> - </div> - </a> - {:else if p.with} - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.with} size={16} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary">{p.with.username}</div> - </div> - {:else if p.type === types.classic} - <img - src="/images/classic.png" - class="w-24 border-4 border-transparent mr-1" - alt="Bitcoin" - /> - <div class="my-auto text-secondary">{p.memo}</div> - {:else} - <div class="text-secondary flex"> - {#if p.type === types.lightning} - <div class="text-3xl">⚡️</div> - {:else if p.type === types.bitcoin} - <div class="my-auto mr-1"> - <img - src="/images/bitcoin.svg" - class="w-12 border-4 border-transparent" - alt="Bitcoin" - /> - </div> - {/if} + <div class="my-auto">Pot</div> + </div> + </a> + {:else if p.with} + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.with} size={16} disabled={true} /> + </div> + <div class="my-auto ml-1 text-secondary"> + {p.with.username} + </div> + </div> + {:else if p.type === types.classic} + <img + src="/images/classic.png" + class="w-24 border-4 border-transparent mr-1" + alt="Bitcoin" + /> + <div class="my-auto text-secondary">{p.memo}</div> + {:else} + <div class="text-secondary flex"> + {#if p.type === types.lightning} + <div class="text-3xl">⚡️</div> + {:else if p.type === types.bitcoin} + <div class="my-auto mr-1"> + <img + src="/images/bitcoin.svg" + class="w-12 border-4 border-transparent" + alt="Bitcoin" + /> + </div> + {/if} - <div class="my-auto"> - {p.amount > 0 ? (p.confirmed ? 'Received' : 'Pending') : 'Sent'} - </div> - </div> - {/if} - </div> + <div class="my-auto"> + {p.amount > 0 + ? p.confirmed + ? "Received" + : "Pending" + : "Sent"} + </div> + </div> + {/if} + </div> - <div class="text-secondary text-right text-sm my-auto"> - <div> - {format(new Date(p.created), 'h:mm aaa')} - </div> - <div> - {format(new Date(p.created), 'MMM d')} - </div> - </div> - </div> - </a> - {:else} - <p class="text-secondary text-lg text-center">{$t('payments.empty')}</p> - {/each} - </div> + <div class="text-secondary text-right text-sm my-auto"> + <div> + {format(new Date(p.created), "h:mm aaa")} + </div> + <div> + {format(new Date(p.created), "MMM d")} + </div> + </div> + </div> + </a> + {:else} + <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p> + {/each} + </div> - <div class="grid grid-cols-3 w-full text-center"> - <span class="text-lg text-secondary">{$t('payments.total')}</span> - <span class="text-lg text-secondary">{$t('payments.presentValue')}</span> - <span class="text-lg text-secondary">{$t('payments.gainLoss')}</span> + <div class="grid grid-cols-3 w-full text-center"> + <span class="text-lg text-secondary">{$t("payments.total")}</span> + <span class="text-lg text-secondary">{$t("payments.presentValue")}</span> + <span class="text-lg text-secondary">{$t("payments.gainLoss")}</span> - {#each Object.keys(totals) as c} - {@const total = totals[c]['fiat']} - {@const pv = (totals[c]['sats'] * rates[c]) / sats} - {@const gain = (pv * 100) / total - 100} - <span class="text-lg"><b>{f(total, c)}</b></span> - <span class="text-lg"><b>{f(pv, c)}</b></span> - <span class="text-lg" class:text-red-800={gain < 0} - ><b>{Math.abs(gain).toFixed(2)}%</b></span - > - {/each} - </div> + {#each Object.keys(totals) as c} + {@const total = totals[c]["fiat"]} + {@const pv = (totals[c]["sats"] * rates[c]) / sats} + {@const gain = (pv * 100) / total - 100} + <span class="text-lg"><b>{f(total, c)}</b></span> + <span class="text-lg"><b>{f(pv, c)}</b></span> + <span class="text-lg" class:text-red-800={gain < 0} + ><b>{Math.abs(gain).toFixed(2)}%</b></span + > + {/each} + </div> - <button - class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" - on:click={csv} - > - <Icon icon="save" style="opacity-50 mr-2 my-auto" /> - <div class="my-auto">{$t('payments.export')}</div> - </button> - </div> + <button + class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto" + on:click={csv} + > + <Icon icon="save" style="opacity-50 mr-2 my-auto" /> + <div class="my-auto">{$t("payments.export")}</div> + </button> + </div> </div> <style> - .active * { - @apply bg-black text-white border-black; - } + .active * { + @apply bg-black text-white border-black; + } </style> diff --git a/src/routes/(app)/[username]/payments/[...page]/+server.js b/src/routes/(app)/[username]/payments/[...page]/+server.js index c7c5222d..a81faaf3 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+server.js +++ b/src/routes/(app)/[username]/payments/[...page]/+server.js @@ -1,18 +1,18 @@ -import { get, auth } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { get, auth } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function GET({ cookies, params }) { - let parts = params.page.split('/'); + let parts = params.page.split("/"); - let start, end; - if (parts.length === 1) [start] = parts; - if (parts.length === 2) [start, end] = parts; + let start, end; + if (parts.length === 1) [start] = parts; + if (parts.length === 2) [start, end] = parts; - if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - let url = `/payments?start=${start * 1000}`; - if (end) url += `&end=${end * 1000}`; + let url = `/payments?start=${start * 1000}`; + if (end) url += `&end=${end * 1000}`; - let { payments } = await get(url, auth(cookies)); - return json({ payments }); + let { payments } = await get(url, auth(cookies)); + return json({ payments }); } diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.server.js b/src/routes/(app)/[username]/receive/[...param]/+page.server.js index cdd89c8b..a6e59175 100644 --- a/src/routes/(app)/[username]/receive/[...param]/+page.server.js +++ b/src/routes/(app)/[username]/receive/[...param]/+page.server.js @@ -1,15 +1,15 @@ -import { auth, get } from '$lib/utils'; -import invoice from '$lib/invoice'; +import { auth, get } from "$lib/utils"; +import invoice from "$lib/invoice"; export let load = async ({ cookies, params }) => { - let id = params.param; + let id = params.param; - let request; - if (id) ({ request } = await get(`/request/${id}`, auth(cookies))); + let request; + if (id) ({ request } = await get(`/request/${id}`, auth(cookies))); - return { ...params, request }; + return { ...params, request }; }; export const actions = { - default: invoice + default: invoice, }; diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte index d7001079..085a6f6d 100644 --- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte +++ b/src/routes/(app)/[username]/receive/[...param]/+page.svelte @@ -1,50 +1,54 @@ <script> - import { Icon, Numpad, Toggle } from '$comp'; - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { page } from '$app/stores'; - import { requestRedirect } from '$lib/store'; - import { browser } from '$app/environment'; + import { Icon, Numpad, Toggle } from "$comp"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { page } from "$app/stores"; + import { requestRedirect } from "$lib/store"; + import { browser } from "$app/environment"; - export let data; - let type = 'lightning'; + export let data; + let type = "lightning"; - if ($page.params.param === 'bitcoin') type = 'bitcoin'; + if ($page.params.param === "bitcoin") type = "bitcoin"; - let { request } = data; + let { request } = data; - let amount; + let amount; - let lightning = true; - let { currency, username, prompt } = data.subject; - let { rates, user } = data; - let submit; + let lightning = true; + let { currency, username, prompt } = data.subject; + let { rates, user } = data; + let submit; - $: rate = rates[currency]; - $: type = lightning ? 'lightning' : 'bitcoin'; + $: rate = rates[currency]; + $: type = lightning ? "lightning" : "bitcoin"; </script> -<form method="POST" class="flex justify-center items-center mt-24 mb-3 px-3" use:enhance> - <input type="hidden" name="amount" value={amount} /> - <input type="hidden" name="currency" value={currency} /> - <input type="hidden" name="username" value={username} /> - <input type="hidden" name="type" value={type} /> +<form + method="POST" + class="flex justify-center items-center mt-24 mb-3 px-3" + use:enhance +> + <input type="hidden" name="amount" value={amount} /> + <input type="hidden" name="currency" value={currency} /> + <input type="hidden" name="username" value={username} /> + <input type="hidden" name="type" value={type} /> - {#if request} - <input type="hidden" name="request_id" value={request.id} /> - {/if} + {#if request} + <input type="hidden" name="request_id" value={request.id} /> + {/if} - <input type="hidden" name="prompt" value={prompt} /> + <input type="hidden" name="prompt" value={prompt} /> - <div class="space-y-3 w-[300px] mx-auto"> - <Numpad bind:amount bind:currency bind:rate {submit} /> - <button - bind:this={submit} - type="submit" - class="bg-black text-white rounded-xl w-full h-[48px] flex justify-center items-center font-semibold + <div class="space-y-3 w-[300px] mx-auto"> + <Numpad bind:amount bind:currency bind:rate {submit} /> + <button + bind:this={submit} + type="submit" + class="bg-black text-white rounded-xl w-full h-[48px] flex justify-center items-center font-semibold opacity-100 hover:opacity-80" - > - {$t('payments.next')} - </button> - </div> + > + {$t("payments.next")} + </button> + </div> </form> diff --git a/src/routes/(app)/[username]/request/[id]/+page.server.js b/src/routes/(app)/[username]/request/[id]/+page.server.js index 89792ab8..09cce465 100644 --- a/src/routes/(app)/[username]/request/[id]/+page.server.js +++ b/src/routes/(app)/[username]/request/[id]/+page.server.js @@ -1,25 +1,26 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export let load = async ({ cookies, depends, params, parent }) => { - depends('app:invoice'); + depends("app:invoice"); - let { user } = await parent(); - if (!user) throw redirect(307, '/login'); + let { user } = await parent(); + if (!user) throw redirect(307, "/login"); - let { request } = await get(`/request/${params.id}`, auth(cookies)); - let { invoice } = request; + let { request } = await get(`/request/${params.id}`, auth(cookies)); + let { invoice } = request; - if (invoice) { - let { amount, received, id } = invoice; - if ( - request.recipient.username === user.username && - ((received && !amount) || received >= amount) - ) - throw redirect(307, `/${user.username}/invoice/${id}/paid`); + if (invoice) { + let { amount, received, id } = invoice; + if ( + request.recipient.username === user.username && + ((received && !amount) || received >= amount) + ) + throw redirect(307, `/${user.username}/invoice/${id}/paid`); - if (request.requester.username === user.username) throw redirect(307, `/send/${id}`); - } + if (request.requester.username === user.username) + throw redirect(307, `/send/${id}`); + } - return { request }; + return { request }; }; diff --git a/src/routes/(app)/[username]/request/[id]/+page.svelte b/src/routes/(app)/[username]/request/[id]/+page.svelte index bdd14f4a..f9672c6f 100644 --- a/src/routes/(app)/[username]/request/[id]/+page.svelte +++ b/src/routes/(app)/[username]/request/[id]/+page.svelte @@ -1,52 +1,57 @@ <script> - import { scale } from 'svelte/transition'; - import { t } from '$lib/translations'; - import { Avatar, Icon } from '$comp'; - import { f, sat, sats } from '$lib/utils'; - import { request as req, requestRedirect } from '$lib/store'; - import { onMount } from 'svelte'; + import { scale } from "svelte/transition"; + import { t } from "$lib/translations"; + import { Avatar, Icon } from "$comp"; + import { f, sat, sats } from "$lib/utils"; + import { request as req, requestRedirect } from "$lib/store"; + import { onMount } from "svelte"; - export let data; - let { request, user } = data; + export let data; + let { request, user } = data; - let amount, currency, rate, fiat; - if (request.invoice) { - ({ amount, rate, currency } = request.invoice); - fiat = (amount * rate) / sats; - } + let amount, currency, rate, fiat; + if (request.invoice) { + ({ amount, rate, currency } = request.invoice); + fiat = (amount * rate) / sats; + } - onMount(() => req.set()); + onMount(() => req.set()); </script> <div class="container px-4 max-w-xl mx-auto space-y-8"> - <div class="text-center mb-8 space-y-5"> - {#if request.recipient_id === user.id} - <p class="text-4xl break-words"> - <span class="text-xl md:text-2xl">{$t('payments.sentInvoiceTo')}</span> - </p> - <div class="flex p-1 gap-2 justify-center"> - <Avatar user={request.requester} size={20} /> - <p class="text-4xl break-words my-auto">{request.requester.username}</p> - </div> - <div> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat, currency)} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1">{sat(amount)}</h3> - </div> - {:else} - <div class="flex w-full max-w-[200px] mx-auto py-20" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <p class="text-3xl"> - {$t('payments.sentRequestTo')} - </p> - {/if} - </div> + <div class="text-center mb-8 space-y-5"> + {#if request.recipient_id === user.id} + <p class="text-4xl break-words"> + <span class="text-xl md:text-2xl">{$t("payments.sentInvoiceTo")}</span> + </p> + <div class="flex p-1 gap-2 justify-center"> + <Avatar user={request.requester} size={20} /> + <p class="text-4xl break-words my-auto">{request.requester.username}</p> + </div> + <div> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat, currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">{sat(amount)}</h3> + </div> + {:else} + <div + class="flex w-full max-w-[200px] mx-auto py-20" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <p class="text-3xl"> + {$t("payments.sentRequestTo")} + </p> + {/if} + </div> </div> <a href={$requestRedirect || `/${user.username}`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> -<div class="fixed bottom-10 left-0 text-center w-full">{$t('payments.tapAnywhere')}</div> +<div class="fixed bottom-10 left-0 text-center w-full"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/[username]/reset/+server.js b/src/routes/(app)/[username]/reset/+server.js index 52fff367..f7dba02c 100644 --- a/src/routes/(app)/[username]/reset/+server.js +++ b/src/routes/(app)/[username]/reset/+server.js @@ -1,10 +1,10 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +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); + 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.server.js b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js index b0c849d0..156b6062 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js @@ -1,23 +1,24 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { fd, auth, get, post } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, get, post } from "$lib/utils"; export function load({ params, url }) { - if (url.pathname.endsWith('settings')) throw redirect(307, url.pathname + '/account'); - return params; + if (url.pathname.endsWith("settings")) + throw redirect(307, url.pathname + "/account"); + return params; } export const actions = { - default: async ({ cookies, request }) => { - let form = await fd(request); - if (form.prompt) form.prompt = form.prompt === 'on'; - let user = { ...(await get('/me', auth(cookies))), ...form }; + default: async ({ cookies, request }) => { + let form = await fd(request); + if (form.prompt) form.prompt = form.prompt === "on"; + let user = { ...(await get("/me", auth(cookies))), ...form }; - try { - ({ user } = await post(`/user`, user, auth(cookies))); - } catch (e) { - return fail(400, { message: e.message }); - } + try { + ({ user } = await post(`/user`, user, auth(cookies))); + } catch (e) { + return fail(400, { message: e.message }); + } - return { user, success: true }; - } + return { user, success: true }; + }, }; diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index 933fa1f6..92772f43 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -1,206 +1,218 @@ <script> - import { browser } from '$app/environment'; - import { onMount, tick } from 'svelte'; - import { fly } from 'svelte/transition'; - import { applyAction, deserialize } from '$app/forms'; + import { browser } from "$app/environment"; + import { onMount, tick } from "svelte"; + import { fly } from "svelte/transition"; + import { applyAction, deserialize } from "$app/forms"; - import { Icon, Spinner, Pin } from '$comp'; - import { t } from '$lib/translations'; - import { fail, post, warning, success } from '$lib/utils'; - import { avatar, banner, password, pin } from '$lib/store'; - import { upload } from '$lib/upload'; - import { page } from '$app/stores'; - import { sign, send, reEncryptEntropy } from '$lib/nostr'; - import { invalidateAll } from '$app/navigation'; + import { Icon, Spinner, Pin } from "$comp"; + import { t } from "$lib/translations"; + import { fail, post, warning, success } from "$lib/utils"; + import { avatar, banner, password, pin } from "$lib/store"; + import { upload } from "$lib/upload"; + import { page } from "$app/stores"; + import { sign, send, reEncryptEntropy } from "$lib/nostr"; + import { invalidateAll } from "$app/navigation"; - import Account from './_account.svelte'; - import Pos from './_pos.svelte'; - import Security from './_security.svelte'; - import Shopify from './_shopify.svelte'; + import Account from "./_account.svelte"; + import Pos from "./_pos.svelte"; + import Security from "./_security.svelte"; + import Shopify from "./_shopify.svelte"; - import { PUBLIC_COINOS_URL } from '$env/static/public'; + import { PUBLIC_COINOS_URL } from "$env/static/public"; - export let data; - export let form; + export let data; + export let form; - let submit; - let formElement; + let submit; + let formElement; - let { user, token, rates, tab } = data; - let prev; + let { user, token, rates, tab } = data; + let prev; - $: update(data); - let update = () => { - ({ user, token, rates, tab } = data); - prev = { ...user }; - }; + $: update(data); + let update = () => { + ({ user, token, rates, tab } = data); + prev = { ...user }; + }; - $: form?.user && ({ user } = form); + $: form?.user && ({ user } = form); - $: form?.success && success('Settings saved!'); + $: form?.success && success("Settings saved!"); - $: form?.message && fail(form.message); + $: form?.message && fail(form.message); - $: if (form?.message?.startsWith('Pin')) { - fail('Wrong pin, try again'); - $pin = ''; - } + $: if (form?.message?.startsWith("Pin")) { + fail("Wrong pin, try again"); + $pin = ""; + } - let tabs = [ - { name: 'account', key: 'ACCOUNT', comp: Account }, - { name: 'pos', key: 'POINT_OF_SALE', comp: Pos }, - { name: 'security', key: 'SECURITY', comp: Security }, - { name: 'shopify', key: 'SHOPIFY', comp: Shopify } - ]; + let tabs = [ + { name: "account", key: "ACCOUNT", comp: Account }, + { name: "pos", key: "POINT_OF_SALE", comp: Pos }, + { name: "security", key: "SECURITY", comp: Security }, + { name: "shopify", key: "SHOPIFY", comp: Shopify }, + ]; - $: ({ comp } = tabs.find((t) => t.name === tab)); + $: ({ comp } = tabs.find((t) => t.name === tab)); - let { address, id, username } = user; - let loading; + let { address, id, username } = user; + let loading; - async function handleSubmit() { - try { - loading = true; - let data = new FormData(formElement); + async function handleSubmit() { + try { + loading = true; + let data = new FormData(formElement); - if (data.get('password')) { - try { - data.set('cipher', await reEncryptEntropy(user, data.get('password'))); - } catch (e) { - console.log('Failed to encrypt keys with new password'); - throw e; - } - } + if (data.get("password")) { + try { + data.set( + "cipher", + await reEncryptEntropy(user, data.get("password")) + ); + } catch (e) { + console.log("Failed to encrypt keys with new password"); + throw e; + } + } - if ($avatar) { - try { - let { hash } = JSON.parse( - await upload($avatar.file, $avatar.type, $avatar.progress, token) - ); + if ($avatar) { + try { + let { hash } = JSON.parse( + await upload($avatar.file, $avatar.type, $avatar.progress, token) + ); - data.set('profile', hash); + data.set("profile", hash); - await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); - } catch (e) { - console.log('problem uploading avatar', e); - } - } + await fetch(`/api/public/${hash}.webp`, { + cache: "reload", + mode: "no-cors", + }); + } catch (e) { + console.log("problem uploading avatar", e); + } + } - if ($banner) { - try { - let { hash } = JSON.parse( - await upload($banner.file, $banner.type, $banner.progress, token) - ); + if ($banner) { + try { + let { hash } = JSON.parse( + await upload($banner.file, $banner.type, $banner.progress, token) + ); - data.set('banner', hash); + data.set("banner", hash); - await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); - } catch (e) { - console.log('problem uploading banner', e); - } - } + await fetch(`/api/public/${hash}.webp`, { + cache: "reload", + mode: "no-cors", + }); + } catch (e) { + console.log("problem uploading banner", e); + } + } - if (['username', 'address', 'profile'].some((a) => user[a] !== prev[a])) { - let event = { - pubkey: user.pubkey, - created_at: Math.floor(Date.now() / 1000), - kind: 0, - content: JSON.stringify({ - name: user.username, - about: user.address, - picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp` - }), - tags: [] - }; + if (["username", "address", "profile"].some((a) => user[a] !== prev[a])) { + let event = { + pubkey: user.pubkey, + created_at: Math.floor(Date.now() / 1000), + kind: 0, + content: JSON.stringify({ + name: user.username, + about: user.address, + picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp`, + }), + tags: [], + }; - try { - await sign({ event, user }); - await send(event); - } catch (e) { - warning('nostr profile could not be updated'); - } - } + try { + await sign({ event, user }); + await send(event); + } catch (e) { + warning("nostr profile could not be updated"); + } + } - if (user.email !== prev.email) { - await post('/api/verify', { test: true }); - } + if (user.email !== prev.email) { + await post("/api/verify", { test: true }); + } - const response = await fetch(formElement.action, { - method: 'POST', - body: data - }); + const response = await fetch(formElement.action, { + method: "POST", + body: data, + }); - const result = deserialize(await response.text()); + const result = deserialize(await response.text()); - if (result.type === 'success') { - await invalidateAll(); - if (data.get('password')) $password = data.get('password'); - } + if (result.type === "success") { + await invalidateAll(); + if (data.get("password")) $password = data.get("password"); + } - applyAction(result); - } catch (e) { - console.log(e); - fail('Something went wrong'); - } + applyAction(result); + } catch (e) { + console.log(e); + fail("Something went wrong"); + } - loading = false; - } + loading = false; + } </script> {#if user.haspin && $pin?.length !== 6} - <Pin /> + <Pin /> {/if} <form - method="POST" - class="mb-[154px]" - on:submit|preventDefault={handleSubmit} - bind:this={formElement} + method="POST" + class="mb-[154px]" + on:submit|preventDefault={handleSubmit} + bind:this={formElement} > - <input type="hidden" name="pin" value={$pin} /> + <input type="hidden" name="pin" value={$pin} /> - <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> - <div class="header"> - <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t('user.settings.header')} - </h1> + <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + <div class="header"> + <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t("user.settings.header")} + </h1> - <div class="font-bold flex justify-around items-center border-b pb-3 text-secondary"> - {#each tabs.filter((t) => t.name !== 'shopify') as { name, key }} - <a href={`/${user.username}/settings/${name}`}> - <button - class="hover:opacity-80" - class:text-black={tab === name || (tab === 'shopify' && name === 'pos')} - >{$t(`user.settings.${key}`)}</button - > - </a> - {/each} - </div> - </div> + <div + class="font-bold flex justify-around items-center border-b pb-3 text-secondary" + > + {#each tabs.filter((t) => t.name !== "shopify") as { name, key }} + <a href={`/${user.username}/settings/${name}`}> + <button + class="hover:opacity-80" + class:text-black={tab === name || + (tab === "shopify" && name === "pos")} + >{$t(`user.settings.${key}`)}</button + > + </a> + {/each} + </div> + </div> - <svelte:component this={comp} {user} {rates} {submit} /> - </div> + <svelte:component this={comp} {user} {rates} {submit} /> + </div> - <div - class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3" - > - <button - bind:this={submit} - type="submit" - class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" - class:bg-black={loading} - > - {#if loading} - <Spinner /> - {:else} - <div class="my-auto">{$t('user.settings.saveSettings')}</div> - {/if} - </button> - </div> + <div + class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3" + > + <button + bind:this={submit} + type="submit" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" + class:bg-black={loading} + > + {#if loading} + <Spinner /> + {:else} + <div class="my-auto">{$t("user.settings.saveSettings")}</div> + {/if} + </button> + </div> </form> <style> - .header { - view-transition-name: tabs; - } + .header { + view-transition-name: tabs; + } </style> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte index 2a06b31d..64ab669f 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte @@ -1,183 +1,193 @@ <script> - import { tick } from 'svelte'; - import { colorTheme, avatar, banner } from '$lib/store'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; - import { page } from '$app/stores'; + import { tick } from "svelte"; + import { colorTheme, avatar, banner } from "$lib/store"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; - export let user, rates, submit; + export let user, rates, submit; - let { id } = user; + let { id } = user; - let selectedTheme = 1; - let avatarFile, avatarInput, bannerFile, bannerInput; + let selectedTheme = 1; + let avatarFile, avatarInput, bannerFile, bannerInput; - let colorThemes = [ - { theme: 1, color1: 'from-[#F5F7FA]', color2: 'to-[#C3CFE2]' }, - { theme: 2, color1: 'from-[#FDFCFB]', color2: 'to-[#E2D1C3]' }, - { theme: 3, color1: 'from-[#E6E9F0]', color2: 'to-[#EEF1F5]' }, - { theme: 4, color1: 'from-[#D5D4D0]', color2: 'to-[#EEEEEC]' }, - { theme: 5, color1: 'from-[#F3E7E9]', color2: 'to-[#E3EEFF]' }, - { theme: 6, color1: 'from-[#F5F7FA]', color2: 'via-[#EAF2FF] to-[#DEDFFF]' } - ]; + let colorThemes = [ + { theme: 1, color1: "from-[#F5F7FA]", color2: "to-[#C3CFE2]" }, + { theme: 2, color1: "from-[#FDFCFB]", color2: "to-[#E2D1C3]" }, + { theme: 3, color1: "from-[#E6E9F0]", color2: "to-[#EEF1F5]" }, + { theme: 4, color1: "from-[#D5D4D0]", color2: "to-[#EEEEEC]" }, + { theme: 5, color1: "from-[#F3E7E9]", color2: "to-[#E3EEFF]" }, + { + theme: 6, + color1: "from-[#F5F7FA]", + color2: "via-[#EAF2FF] to-[#DEDFFF]", + }, + ]; - let handleThemeClick = (colors) => { - selectedTheme = colors.theme; - $colorTheme = colors.color1 + ' ' + colors.color2; - }; + let handleThemeClick = (colors) => { + selectedTheme = colors.theme; + $colorTheme = colors.color1 + " " + colors.color2; + }; - let selectAvatar = () => avatarInput.click(); - let selectBanner = () => bannerInput.click(); + let selectAvatar = () => avatarInput.click(); + let selectBanner = () => bannerInput.click(); - let percent; - let progress = async (event) => { - percent = Math.round((event.loaded / event.total) * 100); - }; + let percent; + let progress = async (event) => { + percent = Math.round((event.loaded / event.total) * 100); + }; - let tooLarge = {}; + let tooLarge = {}; - let handleFile = async ({ target }, type) => { - tooLarge[type] = false; - let file = target.files[0]; - if (!file) return; + let handleFile = async ({ target }, type) => { + tooLarge[type] = false; + let file = target.files[0]; + if (!file) return; - if (file.size > 10000000) return (tooLarge[type] = true); + if (file.size > 10000000) return (tooLarge[type] = true); - if (type === 'profile') { - $avatar = { id, file, type, progress }; - } else if (type === 'banner') { - $banner = { id, file, type, progress }; - } + if (type === "profile") { + $avatar = { id, file, type, progress }; + } else if (type === "banner") { + $banner = { id, file, type, progress }; + } - var reader = new FileReader(); - reader.onload = async (e) => { - if (type === 'profile') { - $avatar.src = e.target.result; - } else if (type === 'banner') { - $banner.src = e.target.result; - } - }; + var reader = new FileReader(); + reader.onload = async (e) => { + if (type === "profile") { + $avatar.src = e.target.result; + } else if (type === "banner") { + $banner.src = e.target.result; + } + }; - reader.readAsDataURL(file); - }; + reader.readAsDataURL(file); + }; - if (!user.display) user.display = user.username; + if (!user.display) user.display = user.username; - $: url = `${$page.url.host}/${user.username}`; - $: full = `${$page.url.protocol}//${url}`; - $: addr = `${user.username}@${$page.url.host}`; + $: url = `${$page.url.host}/${user.username}`; + $: full = `${$page.url.protocol}//${url}`; + $: addr = `${user.username}@${$page.url.host}`; </script> <div> - <label for="username" class="font-bold mb-1 block">{$t('user.settings.username')}</label> - <div class="flex mb-2"> - <input - type="text" - name="username" - bind:value={user.username} - class="w-auto min-w-0 pr-1 grow" - /> - </div> + <label for="username" class="font-bold mb-1 block" + >{$t("user.settings.username")}</label + > + <div class="flex mb-2"> + <input + type="text" + name="username" + bind:value={user.username} + class="w-auto min-w-0 pr-1 grow" + /> + </div> </div> <div> - <label for="display" class="font-bold mb-1 block">{$t('user.settings.displayName')}</label> - <input type="text" name="display" bind:value={user.display} /> + <label for="display" class="font-bold mb-1 block" + >{$t("user.settings.displayName")}</label + > + <input type="text" name="display" bind:value={user.display} /> </div> <div> - <span class="font-bold">{$t('user.settings.profileImage')}</span> + <span class="font-bold">{$t("user.settings.profileImage")}</span> - <div class="flex"> - {#if $avatar || user.profile} - <div - class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" - on:click={selectAvatar} - on:keydown={selectAvatar} - > - <img - src={$avatar?.src || `/api/public/${user.profile}.webp`} - class="absolute w-full h-full object-cover object-center visible overflow-hidden" - alt={user.username} - /> - </div> - {:else} - <div - class="rounded-full border-4 border-white p-4 bg-gradient-to-r {$colorTheme} w-24 my-auto hover:opacity-80 cursor-pointer" - on:click={selectAvatar} - on:keydown={selectAvatar} - > - <Icon icon="logo-symbol-white" style="mx-auto" /> - </div> - {/if} - <div class="ml-2 p-2"> - <!-- found missing translation --> - <button - type="button" - class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={selectAvatar} - on:keydown={selectAvatar}>{$t('user.settings.select')}</button - > - <input - type="file" - class="hidden" - bind:this={avatarInput} - on:change={(e) => handleFile(e, 'profile')} - /> - </div> - </div> + <div class="flex"> + {#if $avatar || user.profile} + <div + class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" + on:click={selectAvatar} + on:keydown={selectAvatar} + > + <img + src={$avatar?.src || `/api/public/${user.profile}.webp`} + class="absolute w-full h-full object-cover object-center visible overflow-hidden" + alt={user.username} + /> + </div> + {:else} + <div + class="rounded-full border-4 border-white p-4 bg-gradient-to-r {$colorTheme} w-24 my-auto hover:opacity-80 cursor-pointer" + on:click={selectAvatar} + on:keydown={selectAvatar} + > + <Icon icon="logo-symbol-white" style="mx-auto" /> + </div> + {/if} + <div class="ml-2 p-2"> + <!-- found missing translation --> + <button + type="button" + class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" + on:click={selectAvatar} + on:keydown={selectAvatar}>{$t("user.settings.select")}</button + > + <input + type="file" + class="hidden" + bind:this={avatarInput} + on:change={(e) => handleFile(e, "profile")} + /> + </div> + </div> - {#if tooLarge['avatar']} - <div class="text-red-600">Max file size 10MB</div> - {/if} + {#if tooLarge["avatar"]} + <div class="text-red-600">Max file size 10MB</div> + {/if} </div> <div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t('user.settings.bannerImage')}</span> - </div> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.bannerImage")}</span> + </div> - {#if $banner || user.banner} - <img - src={$banner ? $banner.src : `/api/public/${user.banner}.webp`} - class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner} - alt="Banner" - /> - {:else} - <div - class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner} - alt="Banner" - /> - {/if} + {#if $banner || user.banner} + <img + src={$banner ? $banner.src : `/api/public/${user.banner}.webp`} + class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" + on:click={selectBanner} + on:keydown={selectBanner} + alt="Banner" + /> + {:else} + <div + class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80" + on:click={selectBanner} + on:keydown={selectBanner} + alt="Banner" + /> + {/if} - <button - type="button" - class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={selectBanner} - on:keydown={selectBanner}>{$t('user.settings.select')}</button - > - <input - type="file" - class="hidden" - bind:this={bannerInput} - on:change={(e) => handleFile(e, 'banner')} - /> + <button + type="button" + class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" + on:click={selectBanner} + on:keydown={selectBanner}>{$t("user.settings.select")}</button + > + <input + type="file" + class="hidden" + bind:this={bannerInput} + on:change={(e) => handleFile(e, "banner")} + /> - {#if tooLarge['banner']} - <div class="text-red-600">Max file size 10MB</div> - {/if} + {#if tooLarge["banner"]} + <div class="text-red-600">Max file size 10MB</div> + {/if} </div> <div> - <label for="address" class="font-bold mb-1 block">{$t('user.settings.about')}</label> - <textarea - type="text" - name="address" - bind:value={user.address} - placeholder={$t('user.settings.aboutPlaceholder')} - /> + <label for="address" class="font-bold mb-1 block" + >{$t("user.settings.about")}</label + > + <textarea + type="text" + name="address" + bind:value={user.address} + placeholder={$t("user.settings.aboutPlaceholder")} + /> </div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 9100401a..94a35493 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -1,61 +1,71 @@ <script> - import { Icon, LocaleSelector, Toggle } from '$comp'; - import { t } from '$lib/translations'; - import { success, fail } from '$lib/utils'; - import { page } from '$app/stores'; + import { Icon, LocaleSelector, Toggle } from "$comp"; + import { t } from "$lib/translations"; + import { success, fail } from "$lib/utils"; + import { page } from "$app/stores"; - export let user, rates, submit; + export let user, rates, submit; - let fiats = Object.keys(rates); + let fiats = Object.keys(rates); </script> <div> - <label for="language" class="font-bold block mb-1">{$t('user.settings.locale')}</label> - <LocaleSelector style="select-styles block py-3 w-full" /> + <label for="language" class="font-bold block mb-1" + >{$t("user.settings.locale")}</label + > + <LocaleSelector style="select-styles block py-3 w-full" /> </div> <div> - <label for="currency" class="font-bold block mb-1">{$t('user.settings.localCurrency')}</label> - <select name="currency" class="select-styles block py-3 w-full" value={user.currency}> - {#each fiats as fiat} - <option value={fiat}>{fiat}</option> - {/each} - </select> + <label for="currency" class="font-bold block mb-1" + >{$t("user.settings.localCurrency")}</label + > + <select + name="currency" + class="select-styles block py-3 w-full" + value={user.currency} + > + {#each fiats as fiat} + <option value={fiat}>{fiat}</option> + {/each} + </select> </div> <div class="space-y-1"> - <label for="email" class="font-bold block">{$t('user.settings.email')}</label> + <label for="email" class="font-bold block">{$t("user.settings.email")}</label> <p class="text-secondary"> - {$t('user.settings.emailDescription')} + {$t("user.settings.emailDescription")} </p> - <input type="text" name="email" bind:value={user.email} /> + <input type="text" name="email" bind:value={user.email} /> </div> <div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t('user.settings.tipPrompt')}</span> - <Toggle id="prompt" bind:value={user.prompt} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t('user.settings.tipPromptDescription')} - </p> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.tipPrompt")}</span> + <Toggle id="prompt" bind:value={user.prompt} /> + </div> + <p class="text-secondary mt-1 w-9/12"> + {$t("user.settings.tipPromptDescription")} + </p> </div> <div> - <div class="flex justify-between items-center"> - <span class="font-bold">{$t('user.settings.publishNip5')}</span> - <Toggle id="nip5" bind:value={user.nip5} /> - </div> - <p class="text-secondary mt-1 w-9/12"> - {$t('user.settings.nip5a')} <span class="font-bold">{user.username}@{$page.url.host}</span> {$t('user.settings.nip5b')} - </p> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.publishNip5")}</span> + <Toggle id="nip5" bind:value={user.nip5} /> + </div> + <p class="text-secondary mt-1 w-9/12"> + {$t("user.settings.nip5a")} + <span class="font-bold">{user.username}@{$page.url.host}</span> + {$t("user.settings.nip5b")} + </p> </div> <a href="./shopify"> - <button - class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center" - > - <img src="/icons/shopify.svg" class="w-8" /> - <div class="my-auto">Shopify Integration</div> - </button> + <button + class="flex rounded-full border py-2 px-4 font-bold hover:opacity-80 w-full gap-2 my-5 justify-center" + > + <img src="/icons/shopify.svg" class="w-8" /> + <div class="my-auto">Shopify Integration</div> + </button> </a> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte index 3cd0480e..f8c14e7b 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_security.svelte @@ -1,290 +1,317 @@ <script> - import { getMnemonic, getNsec } from '$lib/nostr'; - import { tick } from 'svelte'; - import { t } from '$lib/translations'; - import { Pin, Icon, Qr } from '$comp'; - import { copy, post, success, fail } from '$lib/utils'; - import { pin as current } from '$lib/store'; - import { invalidate } from '$app/navigation'; - import { page } from '$app/stores'; + import { getMnemonic, getNsec } from "$lib/nostr"; + import { tick } from "svelte"; + import { t } from "$lib/translations"; + import { Pin, Icon, Qr } from "$comp"; + import { copy, post, success, fail } from "$lib/utils"; + import { pin as current } from "$lib/store"; + import { invalidate } from "$app/navigation"; + import { page } from "$app/stores"; - export let user, rates, submit; + export let user, rates, submit; - let confirming2fa, - disabling2fa, - locked, - mnemonic, - nsec, - old, - otp, - password, - pin, - revealPassword, - revealSeed, - revealNsec, - token = '', - setting2fa, - settingPin, - verify; + let confirming2fa, + disabling2fa, + locked, + mnemonic, + nsec, + old, + otp, + password, + pin, + revealPassword, + revealSeed, + revealNsec, + token = "", + setting2fa, + settingPin, + verify; - let toggleNsec = async () => { - try { - nsec = await getNsec(user); - revealNsec = !revealNsec; - } catch (e) { - console.log(e); - } - }; + let toggleNsec = async () => { + try { + nsec = await getNsec(user); + revealNsec = !revealNsec; + } catch (e) { + console.log(e); + } + }; - let togglePassword = () => (revealPassword = !revealPassword); - let toggleSeed = async () => { - try { - mnemonic = await getMnemonic(user); - revealSeed = !revealSeed; - } catch (e) { - console.log(e); - } - }; + let togglePassword = () => (revealPassword = !revealPassword); + let toggleSeed = async () => { + try { + mnemonic = await getMnemonic(user); + revealSeed = !revealSeed; + } catch (e) { + console.log(e); + } + }; - $: verifying = pin?.length > 5; - $: verify && checkPin(pin); - let checkPin = async () => { - old = $current; + $: verifying = pin?.length > 5; + $: verify && checkPin(pin); + let checkPin = async () => { + old = $current; - try { - if (pin.length > 5 && pin === verify) { - user.haspin = true; - $current = pin; - pin = ''; - verify = ''; - submit.click(); - settingPin = false; - verifying = false; - } else { - fail('Pin mismatch, try again'); - pin = ''; - verify = ''; - verifying = false; - settingPin = true; - } - } catch (e) { - console.log(e); - fail('Problem setting PIN'); - } - }; + try { + if (pin.length > 5 && pin === verify) { + user.haspin = true; + $current = pin; + pin = ""; + verify = ""; + submit.click(); + settingPin = false; + verifying = false; + } else { + fail("Pin mismatch, try again"); + pin = ""; + verify = ""; + verifying = false; + settingPin = true; + } + } catch (e) { + console.log(e); + fail("Problem setting PIN"); + } + }; - let reset = () => { - token = ''; - return true; - }; + let reset = () => { + token = ""; + return true; + }; - let disablingPin = false; + let disablingPin = false; - let togglePin = async () => { - if (user.haspin) { - try { - disablingPin = true; - await tick(); - submit.click(); - } catch (e) { - console.log(e); - fail('Failed to disable pin'); - } - } else { - settingPin = true; + let togglePin = async () => { + if (user.haspin) { + try { + disablingPin = true; + await tick(); + submit.click(); + } catch (e) { + console.log(e); + fail("Failed to disable pin"); + } + } else { + settingPin = true; disablingPin = false; - } - }; + } + }; - let startEnabling2fa = async () => { - reset(); - try { - if (!otp) otp = await post('/otpsecret', { pin: $current }); - } catch (e) { - console.log(e); - } + let startEnabling2fa = async () => { + reset(); + try { + if (!otp) otp = await post("/otpsecret", { pin: $current }); + } catch (e) { + console.log(e); + } - setting2fa = true; - }; + setting2fa = true; + }; - let startDisabling2fa = () => (disabling2fa = true); - let startConfirming2fa = () => (confirming2fa = true); - let cancel = () => { - pin = null; - token = null; - verifying = false; - settingPin = false; - setting2fa = false; - confirming2fa = false; - disabling2fa = false; - }; + let startDisabling2fa = () => (disabling2fa = true); + let startConfirming2fa = () => (confirming2fa = true); + let cancel = () => { + pin = null; + token = null; + verifying = false; + settingPin = false; + setting2fa = false; + confirming2fa = false; + disabling2fa = false; + }; - $: enable2fa(token); - let enable2fa = async (twoFa) => { - try { - if (setting2fa && token.length === 6) { - await post('/enable2fa', { token }); - success('2FA enabled'); - user.twofa = 1; - cancel(); - } - } catch (e) { - fail('Failed to enable 2FA, try again'); - } - }; + $: enable2fa(token); + let enable2fa = async (twoFa) => { + try { + if (setting2fa && token.length === 6) { + await post("/enable2fa", { token }); + success("2FA enabled"); + user.twofa = 1; + cancel(); + } + } catch (e) { + fail("Failed to enable 2FA, try again"); + } + }; - $: disable2fa(token); - let disable2fa = async () => { - try { - if (disabling2fa && token.length === 6) { - await post('/disable2fa', { token }); - success('2FA disabled'); - delete user.twofa; - disabling2fa = false; - } - } catch (e) { - console.log(e); - fail('Failed to disable 2FA, try again'); - } - }; + $: disable2fa(token); + let disable2fa = async () => { + try { + if (disabling2fa && token.length === 6) { + await post("/disable2fa", { token }); + success("2FA disabled"); + delete user.twofa; + disabling2fa = false; + } + } catch (e) { + console.log(e); + fail("Failed to disable 2FA, try again"); + } + }; </script> -<input type="hidden" name="newpin" value={disablingPin ? 'delete' : pin} /> +<input type="hidden" name="newpin" value={disablingPin ? "delete" : pin} /> <input type="hidden" name="confirm" bind:value={password} /> <div class="relative mb-5"> - <label for="password" class="block font-bold block mb-1">{$t('user.settings.newPassword')}</label> - {#if revealPassword} - <input name="password" type="text" bind:value={password} /> - {:else} - <input name="password" type="password" bind:value={password} /> - {/if} - <button - type="button" - on:click={togglePassword} - on:keydown={togglePassword} - class="absolute right-5 top-11" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> - </button> + <label for="password" class="block font-bold block mb-1" + >{$t("user.settings.newPassword")}</label + > + {#if revealPassword} + <input name="password" type="text" bind:value={password} /> + {:else} + <input name="password" type="password" bind:value={password} /> + {/if} + <button + type="button" + on:click={togglePassword} + on:keydown={togglePassword} + class="absolute right-5 top-11" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> </div> <div> - <span class="font-bold mb-1" - >{verifying ? $t('user.settings.verifyPIN') : $t('user.settings.securityPIN')}</span - > - <p class="text-secondary mb-1"> - {$t('user.settings.securityPINDescription')} - </p> - {#if verifying} - <Pin bind:value={verify} {cancel} notify={false} /> - {:else if settingPin} - <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" /> - {user.haspin ? $t('user.settings.disablePIN') : $t('user.settings.enablePIN')}</button - > - {/if} + <span class="font-bold mb-1" + >{verifying + ? $t("user.settings.verifyPIN") + : $t("user.settings.securityPIN")}</span + > + <p class="text-secondary mb-1"> + {$t("user.settings.securityPINDescription")} + </p> + {#if verifying} + <Pin bind:value={verify} {cancel} notify={false} /> + {:else if settingPin} + <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" /> + {user.haspin + ? $t("user.settings.disablePIN") + : $t("user.settings.enablePIN")}</button + > + {/if} </div> {#if user.haspin} - <div> - <label for="locktime" class="font-bold mb-1">{$t('user.settings.autoLock')}</label> - <p class="text-secondary mb-1"> - {$t('user.settings.autoLockDescription')} - </p> - <select name="locktime" class="select-styles block py-3 w-full" value={parseInt(user.locktime)}> - <option value={5 * 60}>5 {$t('user.settings.minutes')}</option> - <option value={10 * 60}>10 {$t('user.settings.minutes')}</option> - <option value={30 * 60}>30 {$t('user.settings.minutes')}</option> - <option value={60 * 60}>1 {$t('user.settings.hour')}</option> - <option value={8 * 60 * 60}>8 {$t('user.settings.hours')}</option> - </select> - </div> + <div> + <label for="locktime" class="font-bold mb-1" + >{$t("user.settings.autoLock")}</label + > + <p class="text-secondary mb-1"> + {$t("user.settings.autoLockDescription")} + </p> + <select + name="locktime" + class="select-styles block py-3 w-full" + value={parseInt(user.locktime)} + > + <option value={5 * 60}>5 {$t("user.settings.minutes")}</option> + <option value={10 * 60}>10 {$t("user.settings.minutes")}</option> + <option value={30 * 60}>30 {$t("user.settings.minutes")}</option> + <option value={60 * 60}>1 {$t("user.settings.hour")}</option> + <option value={8 * 60 * 60}>8 {$t("user.settings.hours")}</option> + </select> + </div> {/if} <div> - <span class="font-bold mb-1">{$t('user.settings.twofa')}</span> - <p class="text-secondary mb-4"> - {$t('user.settings.twofaDescription')} - </p> + <span class="font-bold mb-1">{$t("user.settings.twofa")}</span> + <p class="text-secondary mb-4"> + {$t("user.settings.twofaDescription")} + </p> - {#if setting2fa} - <a href={otp.uri}> - <Qr src={otp.qr} /> - </a> + {#if setting2fa} + <a href={otp.uri}> + <Qr src={otp.qr} /> + </a> - <div class="text-center my-4"> - {$t('user.settings.accountId')}<br /> - <b>{otp.secret}</b> - </div> + <div class="text-center my-4"> + {$t("user.settings.accountId")}<br /> + <b>{otp.secret}</b> + </div> - <button type="button" class="primary" on:click={startConfirming2fa}> - <Icon icon="mobile" style="mr-1" /> - Confirm - </button> - {:else if user.twofa} - <button type="button" class="primary" on:click={startDisabling2fa}> - <Icon icon="mobile" style="mr-1" /> - {$t('user.settings.twofaDisable')} - </button> - {:else} - <button type="button" class="primary" on:click={startEnabling2fa}> - <Icon icon="mobile" style="mr-1" /> - {$t('user.settings.twofaSetup')} - </button> - {/if} + <button type="button" class="primary" on:click={startConfirming2fa}> + <Icon icon="mobile" style="mr-1" /> + Confirm + </button> + {:else if user.twofa} + <button type="button" class="primary" on:click={startDisabling2fa}> + <Icon icon="mobile" style="mr-1" /> + {$t("user.settings.twofaDisable")} + </button> + {:else} + <button type="button" class="primary" on:click={startEnabling2fa}> + <Icon icon="mobile" style="mr-1" /> + {$t("user.settings.twofaSetup")} + </button> + {/if} - {#if confirming2fa || disabling2fa} - <Pin bind:value={token} title="Enter 2FA Code" {cancel} persist={false} notify={false} /> - {/if} + {#if confirming2fa || disabling2fa} + <Pin + bind:value={token} + title="Enter 2FA Code" + {cancel} + persist={false} + notify={false} + /> + {/if} </div> <div> - <label for="seedphrase" class="font-bold">{$t('user.settings.seedPhrase')}</label> + <label for="seedphrase" class="font-bold" + >{$t("user.settings.seedPhrase")}</label + > - <p class="text-secondary mb-1"> - {$t('user.settings.seedDescription')} - </p> + <p class="text-secondary mb-1"> + {$t("user.settings.seedDescription")} + </p> - <button type="button" class="primary" on:click={toggleSeed}> - <Icon icon="warning" style="mr-1 w-6 my-auto" /> - {revealSeed ? $t('user.settings.hideSeed') : $t('user.settings.revealSeed')} - </button> + <button type="button" class="primary" on:click={toggleSeed}> + <Icon icon="warning" style="mr-1 w-6 my-auto" /> + {revealSeed ? $t("user.settings.hideSeed") : $t("user.settings.revealSeed")} + </button> - {#if revealSeed} - <div class="text-lg"> - {mnemonic} - </div> + {#if revealSeed} + <div class="text-lg"> + {mnemonic} + </div> - <button type="button" class="primary" on:click={() => copy(mnemonic)}> - <Icon icon="copy" style="mr-1 w-6 my-auto" /> - Copy - </button> - {/if} + <button type="button" class="primary" on:click={() => copy(mnemonic)}> + <Icon icon="copy" style="mr-1 w-6 my-auto" /> + Copy + </button> + {/if} </div> <div> - <label for="seedphrase" class="font-bold">{$t('user.settings.nostrKeys')}</label> + <label for="seedphrase" class="font-bold" + >{$t("user.settings.nostrKeys")}</label + > - <p class="text-secondary mb-1"> - {$t('user.settings.nostrDescription')} - </p> + <p class="text-secondary mb-1"> + {$t("user.settings.nostrDescription")} + </p> - <button type="button" class="primary" on:click={toggleNsec}> - <Icon icon="warning" style="mr-1 w-6 my-auto" /> - {revealNsec ? $t('user.settings.hideNsec') : $t('user.settings.revealNsec')} - </button> + <button type="button" class="primary" on:click={toggleNsec}> + <Icon icon="warning" style="mr-1 w-6 my-auto" /> + {revealNsec ? $t("user.settings.hideNsec") : $t("user.settings.revealNsec")} + </button> - {#if revealNsec} - <div class="text-lg break-all"> - {nsec} - </div> + {#if revealNsec} + <div class="text-lg break-all"> + {nsec} + </div> - <button type="button" class="primary" on:click={() => copy(nsec)}> - <Icon icon="copy" style="mr-1 w-6 my-auto" /> - Copy - </button> - {/if} + <button type="button" class="primary" on:click={() => copy(nsec)}> + <Icon icon="copy" style="mr-1 w-6 my-auto" /> + Copy + </button> + {/if} </div> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte index aeecc4d7..3e2a8dae 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte @@ -1,69 +1,92 @@ <script> - import { t } from '$lib/translations'; - import { Icon } from '$comp'; - import { page } from '$app/stores'; + import { t } from "$lib/translations"; + import { Icon } from "$comp"; + import { page } from "$app/stores"; - export let user; - $: paymentsUrl = - user.shopifyStore && - 'https://admin.shopify.com/store/' + user.shopifyStore + '/settings/payments'; - $: checkoutUrl = - user.shopifyStore && - 'https://admin.shopify.com/store/' + user.shopifyStore + '/settings/checkout'; - $: appsUrl = - user.shopifyStore && - 'https://admin.shopify.com/store/' + user.shopifyStore + '/settings/apps/development'; + export let user; + $: paymentsUrl = + user.shopifyStore && + "https://admin.shopify.com/store/" + + user.shopifyStore + + "/settings/payments"; + $: checkoutUrl = + user.shopifyStore && + "https://admin.shopify.com/store/" + + user.shopifyStore + + "/settings/checkout"; + $: appsUrl = + user.shopifyStore && + "https://admin.shopify.com/store/" + + user.shopifyStore + + "/settings/apps/development"; + + let script = `<script src="${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js"><\/script>`; </script> <div> - <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label> - <div class="flex mb-2"> - <div class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1"> - admin.shopify.com/store/ - </div> - <input - type="text" - name="shopifyStore" - bind:value={user.shopifyStore} - class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow" - /> - </div> - <p class="text-secondary my-2"> - Add Bitcoin under <a class:active={!!paymentsUrl} href={paymentsUrl || undefined} - >Payments > Manual payment methods</a - > with a description like "Pay with Bitcoin". - </p> + <label for="shopifyStore" class="font-bold mb-1 block">Store ID</label> + <div class="flex mb-2"> + <div + class="text-gray-600 p-4 my-auto bg-gray-100 border rounded-l-2xl border-r-0 pr-1" + > + admin.shopify.com/store/ + </div> + <input + type="text" + name="shopifyStore" + bind:value={user.shopifyStore} + class="border-l-0 rounded-l-none w-auto min-w-0 pl-1 grow" + /> + </div> + <p class="text-secondary my-2"> + Add Bitcoin under <a + class:active={!!paymentsUrl} + href={paymentsUrl || undefined}>Payments > Manual payment methods</a + > with a description like "Pay with Bitcoin". + </p> </div> <div> - <label for="display" class="font-bold mb-1 block">Additional scripts</label> - <p class="text-secondary my-2"> - Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl} - >Checkout -> Additional scripts</a - > - </p> - <textarea rows={5} type="text" name="shopifyScripts" readonly><script src="{`${$page.url.protocol}//${$page.url.host}/${user.username}/shopify.js`}"> </script></textarea> + <label for="display" class="font-bold mb-1 block">Additional scripts</label> + <p class="text-secondary my-2"> + Copy this into <a class:active={!!paymentsUrl} href={checkoutUrl} + >Checkout -> Additional scripts</a + > + </p> + <textarea rows={5} type="text" name="shopifyScripts" readonly + >{script}</textarea + > </div> <div> - <label for="shopifyToken" class="font-bold mb-1 block">Admin API token <span class="font-normal">(Optional)</span></label> - <p class="text-secondary my-2">Coinos can automatically mark orders as Paid if you provide an API token. - <input class="my-4" type="text" name="shopifyToken" bind:value={user.shopifyToken} /> - - <p class="text-secondary my-2"> - Create a new app in <a class:active={!!appsUrl} href={appsUrl || undefined} - >Apps > App Development</a - > - </p> + <label for="shopifyToken" class="font-bold mb-1 block" + >Admin API token <span class="font-normal">(Optional)</span></label + > + <p class="text-secondary my-2"> + Coinos can automatically mark orders as Paid if you provide an API token. + <input + class="my-4" + type="text" + name="shopifyToken" + bind:value={user.shopifyToken} + /> + </p> + <p class="text-secondary my-2"> + Create a new app in <a class:active={!!appsUrl} href={appsUrl || undefined} + >Apps > App Development</a + > + </p> - <p class="text-secondary my-2"> - Edit the Admin API scopes to enable <code class="text-black">write_orders</code> permission then install the - app into your store and copy the Admin API access token here. - </p> + <p class="text-secondary my-2"> + Edit the Admin API scopes to enable <code class="text-black" + >write_orders</code + > permission then install the app into your store and copy the Admin API access + token here. + </p> </div> <style> - .active { - @apply text-blue-500; - } + .active { + @apply text-blue-500; + } </style> diff --git a/src/routes/(app)/[username]/shopify.js/+server.js b/src/routes/(app)/[username]/shopify.js/+server.js index 7b5c9d41..c93ac729 100644 --- a/src/routes/(app)/[username]/shopify.js/+server.js +++ b/src/routes/(app)/[username]/shopify.js/+server.js @@ -1,7 +1,7 @@ export async function GET({ cookies, params, url }) { - let { username } = params; + let { username } = params; - let js = ` + let js = ` var qrcode=function(r,_){var t=r,e=QRErrorCorrectLevel[_],n=null,$=0,o=null,a=[],f={},i=function(r,_){n=function(r){for(var _=Array(r),t=0;t<r;t+=1){_[t]=Array(r);for(var e=0;e<r;e+=1)_[t][e]=null}return _}($=4*t+17),u(0,0),u($-7,0),u(0,$-7),l(),g(),v(r,_),t>=7&&h(r),null==o&&(o=x(t,e,a)),s(o,_)},u=function(r,_){for(var t=-1;t<=7;t+=1)if(!(r+t<=-1)&&!($<=r+t))for(var e=-1;e<=7;e+=1)_+e<=-1||$<=_+e||(0<=t&&t<=6&&(0==e||6==e)||0<=e&&e<=6&&(0==t||6==t)||2<=t&&t<=4&&2<=e&&e<=4?n[r+t][_+e]=!0:n[r+t][_+e]=!1)},c=function(){for(var r=0,_=0,t=0;t<8;t+=1){i(!0,t);var e=QRUtil.getLostPoint(f);(0==t||r>e)&&(r=e,_=t)}return _},g=function(){for(var r=8;r<$-8;r+=1)null==n[r][6]&&(n[r][6]=r%2==0);for(var _=8;_<$-8;_+=1)null==n[6][_]&&(n[6][_]=_%2==0)},l=function(){for(var r=QRUtil.getPatternPosition(t),_=0;_<r.length;_+=1)for(var e=0;e<r.length;e+=1){var $=r[_],o=r[e];if(null==n[$][o])for(var a=-2;a<=2;a+=1)for(var f=-2;f<=2;f+=1)-2==a||2==a||-2==f||2==f||0==a&&0==f?n[$+a][o+f]=!0:n[$+a][o+f]=!1}},h=function(r){for(var _=QRUtil.getBCHTypeNumber(t),e=0;e<18;e+=1){var o=!r&&(_>>e&1)==1;n[Math.floor(e/3)][e%3+$-8-3]=o}for(var e=0;e<18;e+=1){var o=!r&&(_>>e&1)==1;n[e%3+$-8-3][Math.floor(e/3)]=o}},v=function(r,_){for(var t=QRUtil.getBCHTypeInfo(e<<3|_),o=0;o<15;o+=1){var a=!r&&(t>>o&1)==1;o<6?n[o][8]=a:o<8?n[o+1][8]=a:n[$-15+o][8]=a}for(var o=0;o<15;o+=1){var a=!r&&(t>>o&1)==1;o<8?n[8][$-o-1]=a:o<9?n[8][15-o-1+1]=a:n[8][15-o-1]=a}n[$-8][8]=!r},s=function(r,_){for(var t=-1,e=$-1,o=7,a=0,f=QRUtil.getMaskFunction(_),i=$-1;i>0;i-=2)for(6==i&&(i-=1);;){for(var u=0;u<2;u+=1)if(null==n[e][i-u]){var c=!1;a<r.length&&(c=(r[a]>>>o&1)==1),f(e,i-u)&&(c=!c),n[e][i-u]=c,-1==(o-=1)&&(a+=1,o=7)}if((e+=t)<0||$<=e){e-=t,t=-t;break}}},d=function(r,_){for(var t=0,e=0,n=0,$=Array(_.length),o=Array(_.length),a=0;a<_.length;a+=1){var f=_[a].dataCount,i=_[a].totalCount-f;e=Math.max(e,f),n=Math.max(n,i),$[a]=Array(f);for(var u=0;u<$[a].length;u+=1)$[a][u]=255&r.getBuffer()[u+t];t+=f;var c=QRUtil.getErrorCorrectPolynomial(i),g=qrPolynomial($[a],c.getLength()-1).mod(c);o[a]=Array(c.getLength()-1);for(var u=0;u<o[a].length;u+=1){var l=u+g.getLength()-o[a].length;o[a][u]=l>=0?g.getAt(l):0}}for(var h=0,u=0;u<_.length;u+=1)h+=_[u].totalCount;for(var v=Array(h),s=0,u=0;u<e;u+=1)for(var a=0;a<_.length;a+=1)u<$[a].length&&(v[s]=$[a][u],s+=1);for(var u=0;u<n;u+=1)for(var a=0;a<_.length;a+=1)u<o[a].length&&(v[s]=o[a][u],s+=1);return v},x=function(r,_,t){for(var e=QRRSBlock.getRSBlocks(r,_),n=qrBitBuffer(),$=0;$<t.length;$+=1){var o=t[$];n.put(o.getMode(),4),n.put(o.getLength(),QRUtil.getLengthInBits(o.getMode(),r)),o.write(n)}for(var a=0,$=0;$<e.length;$+=1)a+=e[$].dataCount;if(n.getLengthInBits()>8*a)throw Error("code length overflow. ("+n.getLengthInBits()+">"+8*a+")");for(n.getLengthInBits()+4<=8*a&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*a)&&(n.put(236,8),!(n.getLengthInBits()>=8*a));){;n.put(17,8)}return d(n,e)};return f.addData=function(r){var _=qr8BitByte(r);a.push(_),o=null},f.isDark=function(r,_){if(r<0||$<=r||_<0||$<=_)throw Error(r+","+_);return n[r][_]},f.getModuleCount=function(){return $},f.make=function(){i(!1,c())},f.createTableTag=function(r,_){r=r||2;var t="";t+='<table style="',t+=" border-width: 0px; border-style: none;",t+=" border-collapse: collapse;",t+=" padding: 0px; margin: "+(_=void 0===_?4*r:_)+"px;",t+='">',t+="<tbody>";for(var e=0;e<f.getModuleCount();e+=1){t+="<tr>";for(var n=0;n<f.getModuleCount();n+=1)t+='<td style="',t+=" border-width: 0px; border-style: none;",t+=" border-collapse: collapse;",t+=" padding: 0px; margin: 0px;",t+=" width: "+r+"px;",t+=" height: "+r+"px;",t+=" background-color: ",t+=f.isDark(e,n)?"#000000":"#ffffff",t+=";",t+='"/>';t+="</tr>"}return t+="</tbody>",t+="</table>"},f.createImgTag=function(r,_,t){r=r||2;var e=_=void 0===_?4*r:_,n=f.getModuleCount()*r+_;return createImgTag(t,t,function(_,t){if(!(e<=_)||!(_<n)||!(e<=t)||!(t<n))return 1;var $=Math.floor((_-e)/r),o=Math.floor((t-e)/r);return f.isDark(o,$)?0:1})},f};qrcode.stringToBytes=function(r){for(var _=[],t=0;t<r.length;t+=1){var e=r.charCodeAt(t);_.push(255&e)}return _},qrcode.createStringToBytes=function(r,_){var t=function(){for(var t=base64DecodeInputStream(r),e=function(){var r=t.read();if(-1==r)throw Error();return r},n=0,$={};;){var o=t.read();if(-1==o)break;var a=e(),f=e(),i=e(),u=String.fromCharCode(o<<8|a),c=f<<8|i;$[u]=c,n+=1}if(n!=_)throw Error(n+" != "+_);return $}();return function(r){for(var _=[],e=0;e<r.length;e+=1){var n=r.charCodeAt(e);if(n<128)_.push(n);else{var $=t[r.charAt(e)];"number"==typeof $?(255&$)==$?_.push($):(_.push($>>>8),_.push(255&$)):_.push(63)}}return _}};var QRMode={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},QRErrorCorrectLevel={L:1,M:0,Q:3,H:2},QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},QRUtil=function(){var r=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],_={},t=function(r){for(var _=0;0!=r;)_+=1,r>>>=1;return _};return _.getBCHTypeInfo=function(r){for(var _=r<<10;t(_)-t(1335)>=0;)_^=1335<<t(_)-t(1335);return(r<<10|_)^21522},_.getBCHTypeNumber=function(r){for(var _=r<<12;t(_)-t(7973)>=0;)_^=7973<<t(_)-t(7973);return r<<12|_},_.getPatternPosition=function(_){return r[_-1]},_.getMaskFunction=function(r){switch(r){case QRMaskPattern.PATTERN000:return function(r,_){return(r+_)%2==0};case QRMaskPattern.PATTERN001:return function(r,_){return r%2==0};case QRMaskPattern.PATTERN010:return function(r,_){return _%3==0};case QRMaskPattern.PATTERN011:return function(r,_){return(r+_)%3==0};case QRMaskPattern.PATTERN100:return function(r,_){return(Math.floor(r/2)+Math.floor(_/3))%2==0};case QRMaskPattern.PATTERN101:return function(r,_){return r*_%2+r*_%3==0};case QRMaskPattern.PATTERN110:return function(r,_){return(r*_%2+r*_%3)%2==0};case QRMaskPattern.PATTERN111:return function(r,_){return(r*_%3+(r+_)%2)%2==0};default:throw Error("bad maskPattern:"+r)}},_.getErrorCorrectPolynomial=function(r){for(var _=qrPolynomial([1],0),t=0;t<r;t+=1)_=_.multiply(qrPolynomial([1,QRMath.gexp(t)],0));return _},_.getLengthInBits=function(r,_){if(1<=_&&_<10)switch(r){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:case QRMode.MODE_KANJI:return 8;default:throw Error("mode:"+r)}else if(_<27)switch(r){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw Error("mode:"+r)}else if(_<41)switch(r){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw Error("mode:"+r)}else throw Error("type:"+_)},_.getLostPoint=function(r){for(var _=r.getModuleCount(),t=0,e=0;e<_;e+=1)for(var n=0;n<_;n+=1){for(var $=0,o=r.isDark(e,n),a=-1;a<=1;a+=1)if(!(e+a<0)&&!(_<=e+a))for(var f=-1;f<=1;f+=1)!(n+f<0)&&!(_<=n+f)&&(0!=a||0!=f)&&o==r.isDark(e+a,n+f)&&($+=1);$>5&&(t+=3+$-5)}for(var e=0;e<_-1;e+=1)for(var n=0;n<_-1;n+=1){var i=0;r.isDark(e,n)&&(i+=1),r.isDark(e+1,n)&&(i+=1),r.isDark(e,n+1)&&(i+=1),r.isDark(e+1,n+1)&&(i+=1),(0==i||4==i)&&(t+=3)}for(var e=0;e<_;e+=1)for(var n=0;n<_-6;n+=1)r.isDark(e,n)&&!r.isDark(e,n+1)&&r.isDark(e,n+2)&&r.isDark(e,n+3)&&r.isDark(e,n+4)&&!r.isDark(e,n+5)&&r.isDark(e,n+6)&&(t+=40);for(var n=0;n<_;n+=1)for(var e=0;e<_-6;e+=1)r.isDark(e,n)&&!r.isDark(e+1,n)&&r.isDark(e+2,n)&&r.isDark(e+3,n)&&r.isDark(e+4,n)&&!r.isDark(e+5,n)&&r.isDark(e+6,n)&&(t+=40);for(var u=0,n=0;n<_;n+=1)for(var e=0;e<_;e+=1)r.isDark(e,n)&&(u+=1);return t+10*(Math.abs(100*u/_/_-50)/5)},_}(),QRMath=function(){for(var r=Array(256),_=Array(256),t=0;t<8;t+=1)r[t]=1<<t;for(var t=8;t<256;t+=1)r[t]=r[t-4]^r[t-5]^r[t-6]^r[t-8];for(var t=0;t<255;t+=1)_[r[t]]=t;var e={};return e.glog=function(r){if(r<1)throw Error("glog("+r+")");return _[r]},e.gexp=function(_){for(;_<0;)_+=255;for(;_>=256;)_-=255;return r[_]},e}();function qrPolynomial(r,_){if(void 0===r.length)throw Error(r.length+"/"+_);var t=function(){for(var t=0;t<r.length&&0==r[t];)t+=1;for(var e=Array(r.length-t+_),n=0;n<r.length-t;n+=1)e[n]=r[n+t];return e}(),e={};return e.getAt=function(r){return t[r]},e.getLength=function(){return t.length},e.multiply=function(r){for(var _=Array(e.getLength()+r.getLength()-1),t=0;t<e.getLength();t+=1)for(var n=0;n<r.getLength();n+=1)_[t+n]^=QRMath.gexp(QRMath.glog(e.getAt(t))+QRMath.glog(r.getAt(n)));return qrPolynomial(_,0)},e.mod=function(r){if(e.getLength()-r.getLength()<0)return e;for(var _=QRMath.glog(e.getAt(0))-QRMath.glog(r.getAt(0)),t=Array(e.getLength()),n=0;n<e.getLength();n+=1)t[n]=e.getAt(n);for(var n=0;n<r.getLength();n+=1)t[n]^=QRMath.gexp(QRMath.glog(r.getAt(n))+_);return qrPolynomial(t,0).mod(r)},e}var QRRSBlock=function(){var r=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],_=function(r,_){var t={};return t.totalCount=r,t.dataCount=_,t},t={},e=function(_,t){switch(t){case QRErrorCorrectLevel.L:return r[(_-1)*4+0];case QRErrorCorrectLevel.M:return r[(_-1)*4+1];case QRErrorCorrectLevel.Q:return r[(_-1)*4+2];case QRErrorCorrectLevel.H:return r[(_-1)*4+3];default:return}};return t.getRSBlocks=function(r,t){var n=e(r,t);if(void 0===n)throw Error("bad rs block @ typeNumber:"+r+"/errorCorrectLevel:"+t);for(var $=n.length/3,o=[],a=0;a<$;a+=1)for(var f=n[3*a+0],i=n[3*a+1],u=n[3*a+2],c=0;c<f;c+=1)o.push(_(i,u));return o},t}(),qrBitBuffer=function(){var r=[],_=0,t={};return t.getBuffer=function(){return r},t.getAt=function(_){return(r[Math.floor(_/8)]>>>7-_%8&1)==1},t.put=function(r,_){for(var e=0;e<_;e+=1)t.putBit((r>>>_-e-1&1)==1)},t.getLengthInBits=function(){return _},t.putBit=function(t){var e=Math.floor(_/8);r.length<=e&&r.push(0),t&&(r[e]|=128>>>_%8),_+=1},t},qr8BitByte=function(r){for(var _=QRMode.MODE_8BIT_BYTE,t=r,e=[],n={},$=0,o=t.length;$<o;$++){var a=[],f=t.charCodeAt($);f>65536?(a[0]=240|(1835008&f)>>>18,a[1]=128|(258048&f)>>>12,a[2]=128|(4032&f)>>>6,a[3]=128|63&f):f>2048?(a[0]=224|(61440&f)>>>12,a[1]=128|(4032&f)>>>6,a[2]=128|63&f):f>128?(a[0]=192|(1984&f)>>>6,a[1]=128|63&f):a[0]=f,e.push(a)}(e=Array.prototype.concat.apply([],e)).length!=t.length&&(e.unshift(191),e.unshift(187),e.unshift(239));var i=e;return n.getMode=function(){return _},n.getLength=function(r){return i.length},n.write=function(r){for(var _=0;_<i.length;_+=1)r.put(i[_],8)},n},byteArrayOutputStream=function(){var r=[],_={};return _.writeByte=function(_){r.push(255&_)},_.writeShort=function(r){_.writeByte(r),_.writeByte(r>>>8)},_.writeBytes=function(r,t,e){t=t||0,e=e||r.length;for(var n=0;n<e;n+=1)_.writeByte(r[n+t])},_.writeString=function(r){for(var t=0;t<r.length;t+=1)_.writeByte(r.charCodeAt(t))},_.toByteArray=function(){return r},_.toString=function(){var _="";_+="[";for(var t=0;t<r.length;t+=1)t>0&&(_+=","),_+=r[t];return _+"]"},_},base64EncodeOutputStream=function(){var r=0,_=0,t=0,e="",n={},$=function(r){e+=String.fromCharCode(o(63&r))},o=function(r){if(r<0);else if(r<26)return 65+r;else if(r<52)return 97+(r-26);else if(r<62)return 48+(r-52);else if(62==r)return 43;else if(63==r)return 47;throw Error("n:"+r)};return n.writeByte=function(e){for(r=r<<8|255&e,_+=8,t+=1;_>=6;)$(r>>>_-6),_-=6},n.flush=function(){if(_>0&&($(r<<6-_),r=0,_=0),t%3!=0)for(var n=3-t%3,o=0;o<n;o+=1)e+="="},n.toString=function(){return e},n},base64DecodeInputStream=function(r){var _=r,t=0,e=0,n=0,$={};$.read=function(){for(;n<8;){if(t>=_.length){if(0==n)return -1;throw Error("unexpected end of file./"+n)}var r=_.charAt(t);if(t+=1,"="==r)return n=0,-1;!r.match(/^\s$/)&&(e=e<<6|o(r.charCodeAt(0)),n+=6)}var $=e>>>n-8&255;return n-=8,$};var o=function(r){if(65<=r&&r<=90)return r-65;if(97<=r&&r<=122)return r-97+26;if(48<=r&&r<=57)return r-48+52;if(43==r)return 62;if(47==r)return 63;else throw Error("c:"+r)};return $},gifImage=function(r,_){var t=r,e=_,n=Array(r*_),$={};$.setPixel=function(r,_,e){n[_*t+r]=e},$.write=function(r){r.writeString("GIF87a"),r.writeShort(t),r.writeShort(e),r.writeByte(128),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(255),r.writeByte(255),r.writeByte(255),r.writeString(","),r.writeShort(0),r.writeShort(0),r.writeShort(t),r.writeShort(e),r.writeByte(0);var _=a(2);r.writeByte(2);for(var n=0;_.length-n>255;)r.writeByte(255),r.writeBytes(_,n,255),n+=255;r.writeByte(_.length-n),r.writeBytes(_,n,_.length-n),r.writeByte(0),r.writeString(";")};var o=function(r){var _=r,t=0,e=0,n={};return n.write=function(r,n){if(r>>>n!=0)throw Error("length over");for(;t+n>=8;)_.writeByte(255&(r<<t|e)),n-=8-t,r>>>=8-t,e=0,t=0;e=r<<t|e,t+=n},n.flush=function(){t>0&&_.writeByte(e)},n},a=function(r){for(var _=1<<r,t=(1<<r)+1,e=r+1,$=f(),a=0;a<_;a+=1)$.add(String.fromCharCode(a));$.add(String.fromCharCode(_)),$.add(String.fromCharCode(t));var i=byteArrayOutputStream(),u=o(i);u.write(_,e);var c=0,g=String.fromCharCode(n[c]);for(c+=1;c<n.length;){var l=String.fromCharCode(n[c]);c+=1,$.contains(g+l)?g+=l:(u.write($.indexOf(g),e),4095>$.size()&&($.size()==1<<e&&(e+=1),$.add(g+l)),g=l)}return u.write($.indexOf(g),e),u.write(t,e),u.flush(),i.toByteArray()},f=function(){var r={},_=0,t={};return t.add=function(e){if(t.contains(e))throw Error("dup key:"+e);r[e]=_,_+=1},t.size=function(){return _},t.indexOf=function(_){return r[_]},t.contains=function(_){return void 0!==r[_]},t};return $},createImgTag=function(r,_,t,e){for(var n=gifImage(r,_),$=0;$<_;$+=1)for(var o=0;o<r;o+=1)n.setPixel(o,$,t(o,$));var a=byteArrayOutputStream();n.write(a);for(var f=base64EncodeOutputStream(),i=a.toByteArray(),u=0;u<i.length;u+=1)f.writeByte(i[u]);f.flush();var c="";return c+="data:image/gif;base64,",c+=f},drawImg=function(r,_){var t,e=(_=_||{}).typeNumber||4,n=_.errorCorrectLevel||"M",$=_.size||500;try{(t=qrcode(e,n||"M")).addData(r),t.make()}catch(o){if(!(e>=40))return drawImg(r,{size:$,errorCorrectLevel:n,typeNumber:e+1});throw Error("Text too long to encode")}var a=parseInt($/t.getModuleCount()),f=parseInt(($-t.getModuleCount()*a)/2);return t.createImgTag(a,f,$)}; @@ -183,9 +183,9 @@ window.addEventListener('load', async () => { }); `; - return new Response(js, { - headers: { - 'Content-Type': 'application/javascript' - } - }); + return new Response(js, { + headers: { + "Content-Type": "application/javascript", + }, + }); } diff --git a/src/routes/(app)/admin/+layout.server.js b/src/routes/(app)/admin/+layout.server.js index 26449467..e49c6e40 100644 --- a/src/routes/(app)/admin/+layout.server.js +++ b/src/routes/(app)/admin/+layout.server.js @@ -1,11 +1,11 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, parent }) { - let token = cookies.get('token'); - if (!token) throw redirect(307, '/login'); - let user = await get('/me', auth(cookies)); - if (!user) throw redirect(307, '/login'); + let token = cookies.get("token"); + if (!token) throw redirect(307, "/login"); + let user = await get("/me", auth(cookies)); + if (!user) throw redirect(307, "/login"); - return { user }; + return { user }; } diff --git a/src/routes/(app)/admin/+layout.svelte b/src/routes/(app)/admin/+layout.svelte index d7561e32..33b64b6a 100644 --- a/src/routes/(app)/admin/+layout.svelte +++ b/src/routes/(app)/admin/+layout.svelte @@ -1,10 +1,10 @@ <script> - import { LandingHeader } from '$comp'; - export let data; - let { user } = data; + import { LandingHeader } from "$comp"; + export let data; + let { user } = data; </script> <LandingHeader {user} /> <main class="container px-10 md:px-40 mx-auto min-h-[600px] pb-20"> - <slot /> + <slot /> </main> diff --git a/src/routes/(app)/admin/+page.server.js b/src/routes/(app)/admin/+page.server.js index e19f894c..93931aa3 100644 --- a/src/routes/(app)/admin/+page.server.js +++ b/src/routes/(app)/admin/+page.server.js @@ -1,10 +1,12 @@ -import { auth, get } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, parent }) { - let { user } = await parent(); - if (!user) throw redirect(307, '/login'); + let { user } = await parent(); + if (!user) throw redirect(307, "/login"); - let users = (await get('/users', auth(cookies))).sort((a, b) => b.balance - a.balance); - return { users }; + 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 529d2f77..c61a2e45 100644 --- a/src/routes/(app)/admin/+page.svelte +++ b/src/routes/(app)/admin/+page.svelte @@ -1,22 +1,22 @@ <script> - import { post } from '$lib/utils'; - export let data; - let reset = (username) => post(`/${username}/reset`, { password }); - let { users } = data; - let password; + 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, 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> + <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.server.js b/src/routes/(app)/buy/+page.server.js index fc7ae473..3d3265cb 100644 --- a/src/routes/(app)/buy/+page.server.js +++ b/src/routes/(app)/buy/+page.server.js @@ -1,27 +1,28 @@ -import { env } from '$env/dynamic/private'; -import { error, fail, redirect } from '@sveltejs/kit'; -import { post, auth } from '$lib/utils'; -import { createTicket } from '$lib/ticket'; +import { env } from "$env/dynamic/private"; +import { error, fail, redirect } from "@sveltejs/kit"; +import { post, auth } from "$lib/utils"; +import { createTicket } from "$lib/ticket"; export const actions = { - default: async ({ cookies, request }) => { - let form = await request.formData(); + default: async ({ cookies, request }) => { + let form = await request.formData(); - let amount = parseFloat(form.get('amount')); - if (!amount || amount < 0 || amount > 500) return fail(400, { message: 'Invalid amount' }); + let amount = parseFloat(form.get("amount")); + if (!amount || amount < 0 || amount > 500) + return fail(400, { message: "Invalid amount" }); - await post( - '/buy', - { - amount: form.get('amount'), - number: form.get('number').replace(/\s+/g, ''), - month: form.get('expiry').slice(0, 2), - year: form.get('expiry').slice(-2), - cvc: form.get('cvc') - }, - auth(cookies) - ); + await post( + "/buy", + { + amount: form.get("amount"), + number: form.get("number").replace(/\s+/g, ""), + month: form.get("expiry").slice(0, 2), + year: form.get("expiry").slice(-2), + cvc: form.get("cvc"), + }, + auth(cookies) + ); - throw redirect(307, `/${form.get('username')}/payments`); - } + throw redirect(307, `/${form.get("username")}/payments`); + }, }; diff --git a/src/routes/(app)/buy/+page.svelte b/src/routes/(app)/buy/+page.svelte index 22e7e9ec..2f999555 100644 --- a/src/routes/(app)/buy/+page.svelte +++ b/src/routes/(app)/buy/+page.svelte @@ -1,131 +1,135 @@ <script> - import { enhance } from '$app/forms'; - import { Icon, Lock, Card, Calendar, Spinner } from '$comp'; - import { animatedRate } from '$lib/store'; - import { f } from '$lib/utils'; + import { enhance } from "$app/forms"; + import { Icon, Lock, Card, Calendar, Spinner } from "$comp"; + import { animatedRate } from "$lib/store"; + import { f } from "$lib/utils"; - export let data, form; + export let data, form; - let { username, currency } = data.user; - let loading; + let { username, currency } = data.user; + let loading; - let amount, n, d, cvc; + let amount, n, d, cvc; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> - <div class="relative"> - <h1 class="text-2xl md:text-3xl font-semibold text-center">Buy Bitcoin</h1> - </div> - <p class="text-secondary text-center">Purchase up to $500 worth of Bitcoin</p> + <div class="flex justify-center items-center"> + <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> + <div class="relative"> + <h1 class="text-2xl md:text-3xl font-semibold text-center"> + Buy Bitcoin + </h1> + </div> + <p class="text-secondary text-center"> + Purchase up to $500 worth of Bitcoin + </p> - {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> - {/if} + {#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> + {/if} - <form - method="POST" - class="flex flex-wrap gap-5 w-full p-5 mt-20" - use:enhance={() => { - loading = true; - return ({ update }) => update() && (loading = false); - }} - > - <input type="hidden" name="username" value={username} /> + <form + method="POST" + class="flex flex-wrap gap-5 w-full p-5 mt-20" + use:enhance={() => { + loading = true; + return ({ update }) => update() && (loading = false); + }} + > + <input type="hidden" name="username" value={username} /> - <label class="relative w-full flex flex-col"> - <span class="font-bold mb-3">Amount</span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="amount" - placeholder="•••" - bind:value={amount} - /> - <span - class="absolute bottom-[9px] left-0 translate-x-1/2 w-6 text-black peer-placeholder-shown:text-gray-300 + <label class="relative w-full flex flex-col"> + <span class="font-bold mb-3">Amount</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="amount" + placeholder="•••" + bind:value={amount} + /> + <span + class="absolute bottom-[9px] left-0 translate-x-1/2 w-6 text-black peer-placeholder-shown:text-gray-300 ">$</span - > - </label> + > + </label> - <label class="relative w-full flex flex-col"> - <span class="font-bold mb-3">Card number</span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="number" - placeholder="0000 0000 0000 0000" - bind:value={n} - /> - <Card /> - </label> + <label class="relative w-full flex flex-col"> + <span class="font-bold mb-3">Card number</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="number" + placeholder="0000 0000 0000 0000" + bind:value={n} + /> + <Card /> + </label> - <label class="relative flex-1 flex flex-col"> - <span class="font-bold mb-3">Expire date</span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="expiry" - placeholder="MM/YY" - bind:value={d} - /> - <Calendar /> - </label> + <label class="relative flex-1 flex flex-col"> + <span class="font-bold mb-3">Expire date</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="expiry" + placeholder="MM/YY" + bind:value={d} + /> + <Calendar /> + </label> - <label class="relative flex-1 flex flex-col"> - <span class="font-bold flex items-center gap-3 mb-3"> - CVC/CVV - <span class="relative group"> - <span - class="hidden group-hover:flex justify-center items-center px-2 py-1 text-xs absolute -right-2 transform translate-x-full -translate-y-1/2 w-max top-1/2 bg-black text-white" - > - 3-digit number on the back of your card</span - > - </span> - </span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="cvc" - placeholder="•••" - bind:value={cvc} - /> - <Lock /> - </label> + <label class="relative flex-1 flex flex-col"> + <span class="font-bold flex items-center gap-3 mb-3"> + CVC/CVV + <span class="relative group"> + <span + class="hidden group-hover:flex justify-center items-center px-2 py-1 text-xs absolute -right-2 transform translate-x-full -translate-y-1/2 w-max top-1/2 bg-black text-white" + > + 3-digit number on the back of your card</span + > + </span> + </span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="cvc" + placeholder="•••" + bind:value={cvc} + /> + <Lock /> + </label> - <div class="w-full flex mt-8"> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-xl" - type="submit" - disabled={loading} - > - {#if loading} - <Spinner /> - {:else} - Submit - {/if} - </button> - </div> - </form> + <div class="w-full flex mt-8"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-xl" + type="submit" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + Submit + {/if} + </button> + </div> + </form> - <div class="flex"> - <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> - <p class="text-secondary">3% credit card fee applies</p> - </div> - </div> - </div> + <div class="flex"> + <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> + <p class="text-secondary">3% credit card fee applies</p> + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/disable2fa/+server.js b/src/routes/(app)/disable2fa/+server.js index b2bb07ec..1e264398 100644 --- a/src/routes/(app)/disable2fa/+server.js +++ b/src/routes/(app)/disable2fa/+server.js @@ -1,13 +1,13 @@ -import { error, json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { error, json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - try { - let body = await request.json(); - let res = await post('/disable2fa', body, auth(cookies)); + try { + let body = await request.json(); + let res = await post("/disable2fa", body, auth(cookies)); - return json(res); - } catch (e) { - throw error(500, 'Problem disabling 2fa'); - } + return json(res); + } catch (e) { + throw error(500, "Problem disabling 2fa"); + } } diff --git a/src/routes/(app)/enable2fa/+server.js b/src/routes/(app)/enable2fa/+server.js index 344850da..6917ae21 100644 --- a/src/routes/(app)/enable2fa/+server.js +++ b/src/routes/(app)/enable2fa/+server.js @@ -1,8 +1,8 @@ -import { auth, post } from '$lib/utils'; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let body = await request.json(); - let res = await post('/2fa', body, auth(cookies)); + let body = await request.json(); + let res = await post("/2fa", body, auth(cookies)); - return new Response(JSON.stringify(res)); + return new Response(JSON.stringify(res)); } diff --git a/src/routes/(app)/events/+server.js b/src/routes/(app)/events/+server.js index decd43cb..95df0bc5 100644 --- a/src/routes/(app)/events/+server.js +++ b/src/routes/(app)/events/+server.js @@ -1,6 +1,6 @@ -import { auth, post } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - return json(post('/event', await request.json(), auth(cookies))); + return json(post("/event", await request.json(), auth(cookies))); } diff --git a/src/routes/(app)/fund/[id]/+page.server.js b/src/routes/(app)/fund/[id]/+page.server.js index 4194deed..6c5c943f 100644 --- a/src/routes/(app)/fund/[id]/+page.server.js +++ b/src/routes/(app)/fund/[id]/+page.server.js @@ -1,23 +1,23 @@ -import { error, redirect } from '@sveltejs/kit'; -import { auth, get, post } from '$lib/utils'; -import Qr from 'qrcode-base64'; +import { error, redirect } from "@sveltejs/kit"; +import { auth, get, post } from "$lib/utils"; +import Qr from "qrcode-base64"; export let load = async ({ params: { id }, url }) => { - let { amount, payments } = await get(`/fund/${id}`); + let { amount, payments } = await get(`/fund/${id}`); - let src = Qr.drawImg(url.href, { size: 300 }); - return { amount, payments, src }; + let src = Qr.drawImg(url.href, { size: 300 }); + return { amount, payments, src }; }; export const actions = { - default: async ({ cookies, locals, params, request }) => { - let { user } = locals; + default: async ({ cookies, locals, params, request }) => { + let { user } = locals; - if (user) { - await post('/redeem', params, auth(cookies)); - throw redirect(307, `/${user.username}/payments`); - } + if (user) { + await post("/redeem", params, auth(cookies)); + throw redirect(307, `/${user.username}/payments`); + } - throw redirect(307, '/register'); - } + throw redirect(307, "/register"); + }, }; diff --git a/src/routes/(app)/fund/[id]/+page.svelte b/src/routes/(app)/fund/[id]/+page.svelte index d2d3079c..117a4b42 100644 --- a/src/routes/(app)/fund/[id]/+page.svelte +++ b/src/routes/(app)/fund/[id]/+page.svelte @@ -1,138 +1,146 @@ <script> - import { format } from 'date-fns'; - import { enhance } from '$app/forms'; - import { page } from '$app/stores'; - import { Avatar, Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { t } from '$lib/translations'; - import { types, copy, f, s, sat, sats } from '$lib/utils'; - import { loginRedirect } from '$lib/store'; + import { format } from "date-fns"; + import { enhance } from "$app/forms"; + import { page } from "$app/stores"; + import { Avatar, Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { t } from "$lib/translations"; + import { types, copy, f, s, sat, sats } from "$lib/utils"; + import { loginRedirect } from "$lib/store"; - export let data; + export let data; - let { amount, src, payments, rates, user } = data; - let { id } = $page.params; + let { amount, src, payments, rates, user } = data; + let { id } = $page.params; - toast.pop(0); + toast.pop(0); - let currency = user ? user.currency : 'CAD'; - let rate = rates[currency]; + let currency = user ? user.currency : "CAD"; + let rate = rates[currency]; - $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); - $loginRedirect = $page.url.pathname; + $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); + $loginRedirect = $page.url.pathname; - let show; - let toggle = () => (show = !show); + let show; + let toggle = () => (show = !show); </script> <div class="container px-4 max-w-4xl mx-auto mt-10 space-y-5"> - <div class="flex justify-center items-center"> - <div class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5"> - <div class="relative flex justify-center gap-2"> - <!-- <p class="absolute right-0 top-1 underline"><Icon icon="settings" /></p> --> - {#if id.split('-').length === 4} - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {id}</h1> - {:else} - <img src="/images/moneypot.jpg" class="w-24" alt="Fund" /> - <div class="my-auto"> - <h1 class="text-2xl font-semibold my-auto">{$t('payments.fund')}</h1> - <h1 class="text-secondary my-auto">{id}</h1> - </div> - {/if} - </div> - {#if show} - <img {src} class="mx-auto" alt={id} /> - {/if} - <div class="flex justify-center gap-4"> - <div class="my-auto"> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(amountFiat, currency)} - </h2> - <h3 class="text-secondary md:text-lg mt-1"> - {sat(amount)} - </h3> - </div> - </div> - <div class="flex gap-2" data-sveltekit-prefetch="off"> - <div class="grow"> - <a href={`/send/fund/${id}`}> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - > - <Icon icon="plus" /> - {$t('payments.addFunds')} - </button> - </a> - </div> - <div class="grow"> - <a href={`/fund/${id}/withdraw`}> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - > - <Icon icon="minus" style="rotate-180" /> - {$t('payments.takeFunds')} - </button> - </a> - </div> - </div> - <div class="flex gap-2" data-sveltekit-prefetch="off"> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - on:click={toggle} - > - <Icon icon="qr" style="invert" /> - {show ? $t('payments.hide') : $t('payments.show')} QR - </button> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - on:click={() => copy($page.url.host + $page.url.pathname)} - > - <Icon icon="copy" style="w-6" /> - {$t('payments.copyLink')} - </button> - </div> - <div class="text-base"> - {#each payments as p} - <a href={`/payment/${p.id}`}> - <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.rate / sats), p.currency)} - {#if p.tip} - <span class="text-sm text-secondary"> - +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% - </span> - {/if} - </div> + <div class="flex justify-center items-center"> + <div + class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5" + > + <div class="relative flex justify-center gap-2"> + <!-- <p class="absolute right-0 top-1 underline"><Icon icon="settings" /></p> --> + {#if id.split("-").length === 4} + <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {id}</h1> + {:else} + <img src="/images/moneypot.jpg" class="w-24" alt="Fund" /> + <div class="my-auto"> + <h1 class="text-2xl font-semibold my-auto"> + {$t("payments.fund")} + </h1> + <h1 class="text-secondary my-auto">{id}</h1> + </div> + {/if} + </div> + {#if show} + <img {src} class="mx-auto" alt={id} /> + {/if} + <div class="flex justify-center gap-4"> + <div class="my-auto"> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(amountFiat, currency)} + </h2> + <h3 class="text-secondary md:text-lg mt-1"> + {sat(amount)} + </h3> + </div> + </div> + <div class="flex gap-2" data-sveltekit-prefetch="off"> + <div class="grow"> + <a href={`/send/fund/${id}`}> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + > + <Icon icon="plus" /> + {$t("payments.addFunds")} + </button> + </a> + </div> + <div class="grow"> + <a href={`/fund/${id}/withdraw`}> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + > + <Icon icon="minus" style="rotate-180" /> + {$t("payments.takeFunds")} + </button> + </a> + </div> + </div> + <div class="flex gap-2" data-sveltekit-prefetch="off"> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + on:click={toggle} + > + <Icon icon="qr" style="invert" /> + {show ? $t("payments.hide") : $t("payments.show")} QR + </button> + <button + class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" + on:click={() => copy($page.url.host + $page.url.pathname)} + > + <Icon icon="copy" style="w-6" /> + {$t("payments.copyLink")} + </button> + </div> + <div class="text-base"> + {#each payments as p} + <a href={`/payment/${p.id}`}> + <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.rate / sats), p.currency)} + {#if p.tip} + <span class="text-sm text-secondary"> + +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% + </span> + {/if} + </div> - <div class="text-secondary"> - {sat(Math.abs(p.amount) + (p.tip || 0))} - </div> - </div> + <div class="text-secondary"> + {sat(Math.abs(p.amount) + (p.tip || 0))} + </div> + </div> - <div class="flex my-auto"> - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.user} size={16} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary">{p.user.username}</div> - </div> - </div> + <div class="flex my-auto"> + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.user} size={16} disabled={true} /> + </div> + <div class="my-auto ml-1 text-secondary"> + {p.user.username} + </div> + </div> + </div> - <div class="text-secondary text-right text-sm my-auto"> - <div> - {format(new Date(p.created), 'h:mm aaa')} - </div> - <div> - {format(new Date(p.created), 'MMM d')} - </div> - </div> - </div> - </a> - {:else} - <p class="text-secondary text-lg text-center">{$t('payments.empty')}</p> - {/each} - </div> - </div> - </div> + <div class="text-secondary text-right text-sm my-auto"> + <div> + {format(new Date(p.created), "h:mm aaa")} + </div> + <div> + {format(new Date(p.created), "MMM d")} + </div> + </div> + </div> + </a> + {:else} + <p class="text-secondary text-lg text-center"> + {$t("payments.empty")} + </p> + {/each} + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/fund/[id]/sweep/+page.server.js b/src/routes/(app)/fund/[id]/sweep/+page.server.js index 9fd244a3..05fb18b6 100644 --- a/src/routes/(app)/fund/[id]/sweep/+page.server.js +++ b/src/routes/(app)/fund/[id]/sweep/+page.server.js @@ -1,14 +1,13 @@ -import { get, auth, post } from '$lib/utils'; -import { redirect } from '@sveltejs/kit'; +import { get, auth, post } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; export async function load({ cookies, params, parent }) { - let { user } = await parent(); - let { id } = params; - let { amount } = await get(`/fund/${id}`); - + let { user } = await parent(); + let { id } = params; + let { amount } = await get(`/fund/${id}`); if (!amount) throw redirect(307, `/fund/${id}`); - if (!user) throw redirect(307, `/register?redirect=/fund/${id}/sweep`); - await post('/take', { amount, id }, auth(cookies)); - return { amount, id }; + if (!user) throw redirect(307, `/register?redirect=/fund/${id}/sweep`); + await post("/take", { amount, id }, auth(cookies)); + return { amount, id }; } diff --git a/src/routes/(app)/fund/[id]/sweep/+page.svelte b/src/routes/(app)/fund/[id]/sweep/+page.svelte index 64bed4dd..bbd7013f 100644 --- a/src/routes/(app)/fund/[id]/sweep/+page.svelte +++ b/src/routes/(app)/fund/[id]/sweep/+page.svelte @@ -1,37 +1,41 @@ <script> - import { scale } from 'svelte/transition'; - import { fiat, f, s } from '$lib/utils'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; + import { scale } from "svelte/transition"; + import { fiat, f, s } from "$lib/utils"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; - export let data; + export let data; - let { amount, id, user, rates } = data; - let { currency } = user; - $: rate = rates[currency]; + let { amount, id, user, rates } = data; + let { currency } = user; + $: rate = rates[currency]; </script> <div class="container px-4 max-w-xl mx-auto mt-10 space-y-5 text-center"> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> - <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('payments.fundsClaimed')}</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(amount, rate), currency)} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(amount)} - </h3> + <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("payments.fundsClaimed")} + </h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(amount, rate), currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(amount)} + </h3> </div> <a href={`/${user.username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <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> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/fund/[id]/withdraw/+page.server.js b/src/routes/(app)/fund/[id]/withdraw/+page.server.js index 476149f2..6fb08f61 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.server.js +++ b/src/routes/(app)/fund/[id]/withdraw/+page.server.js @@ -1,20 +1,20 @@ -import { fail, json, redirect } from '@sveltejs/kit'; -import { fd, auth, post } from '$lib/utils'; +import { fail, json, redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; export async function load({ parent }) { - let { user } = await parent(); - if (!user) throw redirect(307, '/register'); + let { user } = await parent(); + if (!user) throw redirect(307, "/register"); } export const actions = { - default: async ({ cookies, request }) => { - let body = await fd(request); - try { - await post('/take', body, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + default: async ({ cookies, request }) => { + let body = await fd(request); + try { + await post("/take", body, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - throw redirect(307, `/fund/${body.id}`); - } + throw redirect(307, `/fund/${body.id}`); + }, }; diff --git a/src/routes/(app)/fund/[id]/withdraw/+page.svelte b/src/routes/(app)/fund/[id]/withdraw/+page.svelte index 9cad945a..6b2010b7 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.svelte +++ b/src/routes/(app)/fund/[id]/withdraw/+page.svelte @@ -1,58 +1,58 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { back } from '$lib/utils'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { back } from "$lib/utils"; + import { pin } from "$lib/store"; - export let data; - export let form; + export let data; + export let form; - let { rates } = data; - let { amount, id } = $page.params; - let { currency } = data.user; - let loading; + let { rates } = data; + let { amount, id } = $page.params; + let { currency } = data.user; + let loading; - let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message.includes('pin')) $pin = undefined; - loading = false; - }; + let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message.includes("pin")) $pin = undefined; + loading = false; + }; - $: rate = rates[currency]; + $: rate = rates[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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount bind:currency bind:rate {submit} /> + <Numpad bind:amount bind:currency bind:rate {submit} /> - <form method="POST" use:enhance on:submit={submit}> - <input name="id" value={id} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="id" value={id} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/fund/[id]/withdraw/+server.js b/src/routes/(app)/fund/[id]/withdraw/+server.js index 0f9a23f3..96d66413 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+server.js +++ b/src/routes/(app)/fund/[id]/withdraw/+server.js @@ -1,6 +1,6 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - return json(await post('/take', await request.json(), auth(cookies))); + return json(await post("/take", await request.json(), auth(cookies))); } diff --git a/src/routes/(app)/launch/+page.server.js b/src/routes/(app)/launch/+page.server.js index 2b44dfae..d11af500 100644 --- a/src/routes/(app)/launch/+page.server.js +++ b/src/routes/(app)/launch/+page.server.js @@ -1,6 +1,6 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export const load = async ({ parent }) => { - let tickets = await get('/tickets'); - return { ...(await parent()), tickets }; + let tickets = await get("/tickets"); + return { ...(await parent()), tickets }; }; diff --git a/src/routes/(app)/launch/+page.svelte b/src/routes/(app)/launch/+page.svelte index 3469f0bc..028274fa 100644 --- a/src/routes/(app)/launch/+page.svelte +++ b/src/routes/(app)/launch/+page.svelte @@ -1,103 +1,118 @@ <script> - import { LandingHeader } from '$comp'; - import { loginRedirect } from '$lib/store'; - import { beforeNavigate } from '$app/navigation'; - import { info } from '$lib/utils'; - import { Icon } from '$comp'; - import { t } from '$lib/translations'; - import { goto } from '$app/navigation'; - import { scroll } from '$lib/utils'; + import { LandingHeader } from "$comp"; + import { loginRedirect } from "$lib/store"; + import { beforeNavigate } from "$app/navigation"; + import { info } from "$lib/utils"; + import { Icon } from "$comp"; + import { t } from "$lib/translations"; + import { goto } from "$app/navigation"; + import { scroll } from "$lib/utils"; - export let data; - let { user, tickets } = data; - $loginRedirect = '/launch/purchase'; + export let data; + let { user, tickets } = data; + $loginRedirect = "/launch/purchase"; - let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); - $: link = hasTicket ? '/launch/ticket' : user ? $loginRedirect : '/register'; - $: text = (hasTicket ? 'View' : 'Purchase') + ' Ticket'; + let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); + $: link = hasTicket ? "/launch/ticket" : user ? $loginRedirect : "/register"; + $: text = (hasTicket ? "View" : "Purchase") + " Ticket"; - beforeNavigate(({ to }) => { - if (to?.routeId === 'register') info('Please sign in so we can get you a ticket'); - }); + beforeNavigate(({ to }) => { + if (to?.routeId === "register") + info("Please sign in so we can get you a ticket"); + }); - let showMobileMenu = false; - const mobileMenuButtonClick = (section) => { - showMobileMenu = false; - scroll(section); - }; + let showMobileMenu = false; + const mobileMenuButtonClick = (section) => { + showMobileMenu = false; + scroll(section); + }; </script> -<header class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90"> - <nav class="block xl:flex flex-wrap justify-between items-center"> - <div class="flex justify-start md:justify-center items-center md:space-x-10"> - <a href="/" class="hidden md:block"> - <Icon icon="logo" style="mb-5 xl:mb-0" /> - </a> - <Icon icon="logo" style="block md:hidden ml-5 w-48" /> - </div> - </nav> +<header + class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90" +> + <nav class="block xl:flex flex-wrap justify-between items-center"> + <div + class="flex justify-start md:justify-center items-center md:space-x-10" + > + <a href="/" class="hidden md:block"> + <Icon icon="logo" style="mb-5 xl:mb-0" /> + </a> + <Icon icon="logo" style="block md:hidden ml-5 w-48" /> + </div> + </nav> </header> <div class="mx-auto flex items-center justify-center px-8 mt-20"> - <div class="flex flex-col w-full bg-white rounded shadow-lg sm:w-3/4 lg:w-3/5"> - <div class="flex flex-col w-full md:flex-row"> - <div - class="flex flex-row justify-around p-4 font-bold leading-none text-gray-800 uppercase bg-gray-400 rounded md:flex-col md:items-center md:justify-center md:w-1/4" - > - <div class="md:text-3xl">Oct</div> - <div class="md:text-6xl">26</div> - <div class="md:text-xl">6 pm</div> - </div> - <div class="p-4 font-normal text-gray-800 md:w-3/4 space-y-5"> - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800"> - Launch Party! - </h1> + <div + class="flex flex-col w-full bg-white rounded shadow-lg sm:w-3/4 lg:w-3/5" + > + <div class="flex flex-col w-full md:flex-row"> + <div + class="flex flex-row justify-around p-4 font-bold leading-none text-gray-800 uppercase bg-gray-400 rounded md:flex-col md:items-center md:justify-center md:w-1/4" + > + <div class="md:text-3xl">Oct</div> + <div class="md:text-6xl">26</div> + <div class="md:text-xl">6 pm</div> + </div> + <div class="p-4 font-normal text-gray-800 md:w-3/4 space-y-5"> + <h1 + class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800" + > + Launch Party! + </h1> - <p class="leading-normal mb-2"> - Come join us at our launch party celebrating our new design and 10 years serving the - Vancouver Bitcoin community! - </p> + <p class="leading-normal mb-2"> + Come join us at our launch party celebrating our new design and 10 + years serving the Vancouver Bitcoin community! + </p> - <div class="flex"> - <div class="space-y-5"> - <div class="ml-8"> - <p><b>When</b></p> - <p>6pm October 26</p> - </div> - <div class="ml-8"> - <p><b>Where</b></p> - <p>The Loft Lounge</p> - <p>1184 Denman Street, #203</p> - <p>Vancouver, BC</p> - </div> - </div> + <div class="flex"> + <div class="space-y-5"> + <div class="ml-8"> + <p><b>When</b></p> + <p>6pm October 26</p> + </div> + <div class="ml-8"> + <p><b>Where</b></p> + <p>The Loft Lounge</p> + <p>1184 Denman Street, #203</p> + <p>Vancouver, BC</p> + </div> + </div> - <img src="/icons/party.svg" alt="Partygoers" class="w-[120px] mx-auto" /> - </div> + <img + src="/icons/party.svg" + alt="Partygoers" + class="w-[120px] mx-auto" + /> + </div> - <p> - We'll introduce our new interface and then network and enjoy tasty food and drinks from <a - href="https://loftondenman.com/" - class="underline">The Loft</a - >! - </p> + <p> + We'll introduce our new interface and then network and enjoy tasty + food and drinks from <a + href="https://loftondenman.com/" + class="underline">The Loft</a + >! + </p> - <p> - The Loft will be accepting Lightning payments at the event and going forward into the - future as well! We're charging $6 for for an NFT ticket that gets you entry, appies, and - the chance to win some swag and sats. - </p> + <p> + The Loft will be accepting Lightning payments at the event and going + forward into the future as well! We're charging $6 for for an NFT + ticket that gets you entry, appies, and the chance to win some swag + and sats. + </p> - <div class="flex w-full my-8"> - <a href={link} class="mx-auto"> - <button - class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" - > - {text} - </button> - </a> - </div> - </div> - </div> - </div> + <div class="flex w-full my-8"> + <a href={link} class="mx-auto"> + <button + class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" + > + {text} + </button> + </a> + </div> + </div> + </div> + </div> </div> diff --git a/src/routes/(app)/launch/purchase/+page.server.js b/src/routes/(app)/launch/purchase/+page.server.js index a3043d9c..58669606 100644 --- a/src/routes/(app)/launch/purchase/+page.server.js +++ b/src/routes/(app)/launch/purchase/+page.server.js @@ -1,45 +1,49 @@ -import { auth, get, post } from '$lib/utils'; -import tickets from '$lib/tickets'; -import { redirect } from '@sveltejs/kit'; -import { env } from '$env/dynamic/private'; -import { invalidateAll } from '$app/navigation'; -import { browser } from '$app/environment'; -import { createTicket } from '$lib/ticket'; +import { auth, get, post } from "$lib/utils"; +import tickets from "$lib/tickets"; +import { redirect } from "@sveltejs/kit"; +import { env } from "$env/dynamic/private"; +import { invalidateAll } from "$app/navigation"; +import { browser } from "$app/environment"; +import { createTicket } from "$lib/ticket"; export const load = async ({ parent }) => { - let tickets = await get('/tickets'); - let { user } = await parent(); - let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); - if (hasTicket) throw redirect(307, '/launch/ticket'); - return { tickets }; + let tickets = await get("/tickets"); + let { user } = await parent(); + let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); + if (hasTicket) throw redirect(307, "/launch/ticket"); + return { tickets }; }; export const actions = { - internal: async ({ cookies, request }) => { - let form = await request.formData(); - let asset = await createTicket(cookies, form.get('username')); - await post('/payments', { amount: 22000, username: 'launch' }, auth(cookies)); + internal: async ({ cookies, request }) => { + let form = await request.formData(); + let asset = await createTicket(cookies, form.get("username")); + await post( + "/payments", + { amount: 22000, username: "launch" }, + auth(cookies) + ); - throw redirect(307, `/launch/thanks/${asset}`); - }, + throw redirect(307, `/launch/thanks/${asset}`); + }, - lightning: async ({ cookies, request }) => { - let form = await request.formData(); + lightning: async ({ cookies, request }) => { + let form = await request.formData(); - let rates = await get('/rates'); + let rates = await get("/rates"); - let invoice = { - amount: 22000, - memo: 'launch', - type: 'lightning', - prompt: false, - rate: rates[form.get('currency')] - }; + let invoice = { + amount: 22000, + memo: "launch", + type: "lightning", + prompt: false, + rate: rates[form.get("currency")], + }; - let user = { username: form.get('username') }; + let user = { username: form.get("username") }; - let { id } = await post('/invoice', { invoice, user }, auth(cookies)); + let { id } = await post("/invoice", { invoice, user }, auth(cookies)); - throw redirect(307, `/${user.username}/invoice/${id}`); - } + throw redirect(307, `/${user.username}/invoice/${id}`); + }, }; diff --git a/src/routes/(app)/launch/purchase/+page.svelte b/src/routes/(app)/launch/purchase/+page.svelte index fbaa58c0..49a51973 100644 --- a/src/routes/(app)/launch/purchase/+page.svelte +++ b/src/routes/(app)/launch/purchase/+page.svelte @@ -1,66 +1,74 @@ <script> - import { onMount } from 'svelte'; - import { enhance } from '$app/forms'; - import { post } from '$lib/utils'; - import { goto } from '$app/navigation'; + import { onMount } from "svelte"; + import { enhance } from "$app/forms"; + import { post } from "$lib/utils"; + import { goto } from "$app/navigation"; - export let data; + export let data; - let amount = 22000; - let { - currency, - account: { balance }, - username - } = data.user; + let amount = 22000; + let { + currency, + account: { balance }, + username, + } = data.user; - let { tickets } = data; - let ticket = tickets.length; + let { tickets } = data; + let ticket = tickets.length; </script> <div class="mx-auto h-screen flex items-center justify-center px-4"> - <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Purchase Ticket - </h1> + <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> + <h1 + class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center" + > + Purchase Ticket + </h1> - <img - src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`} - class="w-full max-w-[800px]" - alt="Ticket" - /> + <img + src={`/tickets/${(ticket + 1).toString().padStart(2, "0")}.png`} + class="w-full max-w-[800px]" + alt="Ticket" + /> - {#if balance >= amount} - <form action="?/internal" method="POST" use:enhance> - <input type="hidden" name="username" value={username} /> + {#if balance >= amount} + <form action="?/internal" method="POST" use:enhance> + <input type="hidden" name="username" value={username} /> - <div class="w-full flex"> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" - > - Count me in - </button> - </div> - </form> - {:else} - <h1 class="mb-4 text-xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Payment Options - </h1> + <div class="w-full flex"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" + > + Count me in + </button> + </div> + </form> + {:else} + <h1 + class="mb-4 text-xl font-bold leading-none tracking-tight text-gray-800 text-center" + > + Payment Options + </h1> - <div class="flex justify-center gap-4"> - <form action="?/lightning" method="POST"> - <input type="hidden" name="currency" value={currency} /> - <input type="hidden" name="username" value={username} /> - <button class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"> - Lightning - </button> - </form> + <div class="flex justify-center gap-4"> + <form action="?/lightning" method="POST"> + <input type="hidden" name="currency" value={currency} /> + <input type="hidden" name="username" value={username} /> + <button + class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" + > + Lightning + </button> + </form> - <a href={`purchase/card`}> - <button class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"> - Credit Card - </button> - </a> - </div> - {/if} - </div> + <a href={`purchase/card`}> + <button + class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" + > + Credit Card + </button> + </a> + </div> + {/if} + </div> </div> diff --git a/src/routes/(app)/launch/purchase/card/+page.server.js b/src/routes/(app)/launch/purchase/card/+page.server.js index 1f564f78..bca601d3 100644 --- a/src/routes/(app)/launch/purchase/card/+page.server.js +++ b/src/routes/(app)/launch/purchase/card/+page.server.js @@ -1,44 +1,52 @@ -import { env } from '$env/dynamic/private'; -import { error, redirect } from '@sveltejs/kit'; -import { post, auth } from '$lib/utils'; -import { createTicket } from '$lib/ticket'; +import { env } from "$env/dynamic/private"; +import { error, redirect } from "@sveltejs/kit"; +import { post, auth } from "$lib/utils"; +import { createTicket } from "$lib/ticket"; -let stripe = 'https://api.stripe.com/v1'; +let stripe = "https://api.stripe.com/v1"; export const actions = { - default: async ({ cookies, request }) => { - let form = await request.formData(); + default: async ({ cookies, request }) => { + let form = await request.formData(); - let headers = new Headers(); - headers.set('Authorization', 'Basic ' + new Buffer(env.STRIPE + ':').toString('base64')); + let headers = new Headers(); + headers.set( + "Authorization", + "Basic " + new Buffer(env.STRIPE + ":").toString("base64") + ); - let body = new URLSearchParams({ - 'card[number]': form.get('number').replace(/\s+/g, ''), - 'card[exp_month]': form.get('expiry').slice(0, 2), - 'card[exp_year]': form.get('expiry').slice(-2), - 'card[cvc]': form.get('cvc') - }); + let body = new URLSearchParams({ + "card[number]": form.get("number").replace(/\s+/g, ""), + "card[exp_month]": form.get("expiry").slice(0, 2), + "card[exp_year]": form.get("expiry").slice(-2), + "card[cvc]": form.get("cvc"), + }); - let method = 'POST'; + let method = "POST"; - let { id: source } = await fetch(`${stripe}/tokens`, { body, headers, method }).then((r) => - r.json() - ); + let { id: source } = await fetch(`${stripe}/tokens`, { + body, + headers, + method, + }).then((r) => r.json()); - body = new URLSearchParams({ - amount: 600, - currency: 'cad', - source, - description: 'launch ticket' - }); + body = new URLSearchParams({ + amount: 600, + currency: "cad", + source, + description: "launch ticket", + }); - let { status } = await fetch(`${stripe}/charges`, { body, headers, method }).then((r) => - r.json() - ); + let { status } = await fetch(`${stripe}/charges`, { + body, + headers, + method, + }).then((r) => r.json()); - if (status !== 'succeeded') throw error(500, { message: 'Card payment failed' }); - let asset = await createTicket(cookies, form.get('username')); + if (status !== "succeeded") + throw error(500, { message: "Card payment failed" }); + let asset = await createTicket(cookies, form.get("username")); - throw redirect(307, `/launch/thanks/${asset}`); - } + throw redirect(307, `/launch/thanks/${asset}`); + }, }; diff --git a/src/routes/(app)/launch/purchase/card/+page.svelte b/src/routes/(app)/launch/purchase/card/+page.svelte index 05d09025..037a65b2 100644 --- a/src/routes/(app)/launch/purchase/card/+page.svelte +++ b/src/routes/(app)/launch/purchase/card/+page.svelte @@ -1,77 +1,77 @@ <script> - import { enhance } from '$app/forms'; - import { Lock, Card, Calendar, Spinner } from '$comp'; + import { enhance } from "$app/forms"; + import { Lock, Card, Calendar, Spinner } from "$comp"; - export let data; + export let data; - let { username } = data.user; - let loading; + let { username } = data.user; + let loading; </script> <div class="container max-w-lg px-4 mx-auto space-y-5"> - <form - method="POST" - class="flex flex-wrap gap-5 w-full p-5 mt-20" - use:enhance={() => { - loading = true; - return ({ update }) => update() && (loading = false); - }} - > - <input type="hidden" name="username" value={username} /> - <label class="relative w-full flex flex-col"> - <span class="font-bold mb-3">Card number</span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="number" - placeholder="0000 0000 0000 0000" - /> - <Card /> - </label> + <form + method="POST" + class="flex flex-wrap gap-5 w-full p-5 mt-20" + use:enhance={() => { + loading = true; + return ({ update }) => update() && (loading = false); + }} + > + <input type="hidden" name="username" value={username} /> + <label class="relative w-full flex flex-col"> + <span class="font-bold mb-3">Card number</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="number" + placeholder="0000 0000 0000 0000" + /> + <Card /> + </label> - <label class="relative flex-1 flex flex-col"> - <span class="font-bold mb-3">Expire date</span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="expiry" - placeholder="MM/YY" - /> - <Calendar /> - </label> + <label class="relative flex-1 flex flex-col"> + <span class="font-bold mb-3">Expire date</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="expiry" + placeholder="MM/YY" + /> + <Calendar /> + </label> - <label class="relative flex-1 flex flex-col"> - <span class="font-bold flex items-center gap-3 mb-3"> - CVC/CVV - <span class="relative group"> - <span - class="hidden group-hover:flex justify-center items-center px-2 py-1 text-xs absolute -right-2 transform translate-x-full -translate-y-1/2 w-max top-1/2 bg-black text-white" - > - 3-digit number on the back of your card</span - > - </span> - </span> - <input - class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" - type="text" - name="cvc" - placeholder="•••" - /> - <Lock /> - </label> + <label class="relative flex-1 flex flex-col"> + <span class="font-bold flex items-center gap-3 mb-3"> + CVC/CVV + <span class="relative group"> + <span + class="hidden group-hover:flex justify-center items-center px-2 py-1 text-xs absolute -right-2 transform translate-x-full -translate-y-1/2 w-max top-1/2 bg-black text-white" + > + 3-digit number on the back of your card</span + > + </span> + </span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="cvc" + placeholder="•••" + /> + <Lock /> + </label> - <div class="w-full flex mt-8"> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-xl" - type="submit" - disabled={loading} - > - {#if loading} - <Spinner /> - {:else} - Submit - {/if} - </button> - </div> - </form> + <div class="w-full flex mt-8"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-xl" + type="submit" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + Submit + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/launch/thanks/[asset]/+page.svelte b/src/routes/(app)/launch/thanks/[asset]/+page.svelte index 43f10057..9b37002b 100644 --- a/src/routes/(app)/launch/thanks/[asset]/+page.svelte +++ b/src/routes/(app)/launch/thanks/[asset]/+page.svelte @@ -1,29 +1,31 @@ <script> - import { page } from '$app/stores'; + import { page } from "$app/stores"; - export let data; + export let data; - let { user } = data; + let { user } = data; - let { asset } = $page.params; + let { asset } = $page.params; </script> <div class="container mx-auto max-w-lg px-4 mt-20"> - <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Thanks! - </h1> + <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> + <h1 + class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center" + > + Thanks! + </h1> - <p>We look forward to seeing you!</p> - </div> + <p>We look forward to seeing you!</p> + </div> - <div class="w-full flex"> - <a href="/launch/ticket" class="mx-auto"> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" - > - Done - </button> - </a> - </div> + <div class="w-full flex"> + <a href="/launch/ticket" class="mx-auto"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" + > + Done + </button> + </a> + </div> </div> diff --git a/src/routes/(app)/launch/ticket/+page.server.js b/src/routes/(app)/launch/ticket/+page.server.js index 0362eb7d..7b51e679 100644 --- a/src/routes/(app)/launch/ticket/+page.server.js +++ b/src/routes/(app)/launch/ticket/+page.server.js @@ -1,9 +1,11 @@ -import { get } from '$lib/utils'; +import { get } from "$lib/utils"; export const load = async ({ cookies, parent }) => { - let { user } = await parent(); - let tickets = await get('/tickets'); - let ticket = tickets.findIndex((t) => user.accounts.find((a) => a.asset === t)); - let asset = tickets[ticket]; - return { asset, ticket }; + let { user } = await parent(); + let tickets = await get("/tickets"); + let ticket = tickets.findIndex((t) => + user.accounts.find((a) => a.asset === t) + ); + let asset = tickets[ticket]; + return { asset, ticket }; }; diff --git a/src/routes/(app)/launch/ticket/+page.svelte b/src/routes/(app)/launch/ticket/+page.svelte index 3879abcd..6b16e804 100644 --- a/src/routes/(app)/launch/ticket/+page.svelte +++ b/src/routes/(app)/launch/ticket/+page.svelte @@ -1,35 +1,37 @@ <script> - export let data; - let { asset, ticket } = data; + export let data; + let { asset, ticket } = data; </script> <div class="mx-auto h-screen flex items-center justify-center px-4"> - <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Your Ticket - </h1> + <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> + <h1 + class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center" + > + Your Ticket + </h1> - <p> - We've minted your ticket as an <a - href={`https://blockstream.info/liquid/asset/${asset}`} - class="underline">NFT on the Liquid Blockchain</a - > as a proof of purchase and commemorative token. - </p> + <p> + We've minted your ticket as an <a + href={`https://blockstream.info/liquid/asset/${asset}`} + class="underline">NFT on the Liquid Blockchain</a + > as a proof of purchase and commemorative token. + </p> - <img - src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`} - class="w-full max-w-[800px]" - alt="Ticket" - /> + <img + src={`/tickets/${(ticket + 1).toString().padStart(2, "0")}.png`} + class="w-full max-w-[800px]" + alt="Ticket" + /> - <div class="w-full flex"> - <a href="/" class="mx-auto"> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" - > - Done - </button> - </a> - </div> - </div> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" + > + Done + </button> + </a> + </div> + </div> </div> diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.js b/src/routes/(app)/ln/[lnurl]/+page.server.js index ddd714bf..332bc9e3 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.server.js +++ b/src/routes/(app)/ln/[lnurl]/+page.server.js @@ -1,63 +1,75 @@ -import { fd, auth, get, post } from '$lib/utils'; -import { error, fail, redirect } from '@sveltejs/kit'; +import { fd, auth, get, post } from "$lib/utils"; +import { error, fail, redirect } from "@sveltejs/kit"; export async function load({ params, parent }) { - let { user } = await parent(); - if (!user) throw redirect(307, '/register'); + let { user } = await parent(); + if (!user) throw redirect(307, "/register"); - let data; + let data; - try { - let { lnurl } = params; - data = await get(`/decode?text=${lnurl}`); - } catch (e) { - throw error(500, e.message); - } + try { + let { lnurl } = params; + data = await get(`/decode?text=${lnurl}`); + } catch (e) { + throw error(500, e.message); + } - if (!['payRequest', 'withdrawRequest'].includes(data.tag)) - throw error(500, 'We only support LNURLp and LNURLw at this time'); + if (!["payRequest", "withdrawRequest"].includes(data.tag)) + throw error(500, "We only support LNURLp and LNURLw at this time"); - return data; + return data; } export const actions = { - pay: async ({ cookies, fetch, request }) => { - let error; + pay: async ({ cookies, fetch, request }) => { + let error; - let { callback, amount, minSendable, maxSendable } = await fd(request); - minSendable = Math.round(minSendable / 1000); - maxSendable = Math.round(maxSendable / 1000); + let { callback, amount, minSendable, maxSendable } = await fd(request); + minSendable = Math.round(minSendable / 1000); + maxSendable = Math.round(maxSendable / 1000); - if (amount < minSendable) error = `Amount must be at least ${minSendable} sats`; - if (amount > maxSendable) error = `Amount must be at most ${maxSendable} sats`; - if (error) return fail(400, { error }); + if (amount < minSendable) + error = `Amount must be at least ${minSendable} sats`; + if (amount > maxSendable) + error = `Amount must be at most ${maxSendable} sats`; + if (error) return fail(400, { error }); - let { pr } = await fetch(`${callback}?amount=${amount * 1000}`).then((r) => r.json()); - throw redirect(307, `/send/lightning/${pr}`); - }, + let { pr } = await fetch(`${callback}?amount=${amount * 1000}`).then((r) => + r.json() + ); + throw redirect(307, `/send/lightning/${pr}`); + }, - withdraw: async ({ cookies, fetch, request }) => { - let error; + withdraw: async ({ cookies, fetch, request }) => { + let error; - let { callback, amount, username, currency, minWithdrawable, maxWithdrawable, k1 } = await fd( - request - ); + let { + callback, + amount, + username, + currency, + minWithdrawable, + maxWithdrawable, + k1, + } = await fd(request); - minWithdrawable = Math.round(minWithdrawable / 1000); - maxWithdrawable = Math.round(maxWithdrawable / 1000); + minWithdrawable = Math.round(minWithdrawable / 1000); + maxWithdrawable = Math.round(maxWithdrawable / 1000); - if (amount < minWithdrawable) error = `Amount must be at least ${minWithdrawable} sats`; - if (amount > maxWithdrawable) error = `Amount must be at most ${maxWithdrawable} sats`; - if (error) return fail(400, { error }); + if (amount < minWithdrawable) + error = `Amount must be at least ${minWithdrawable} sats`; + if (amount > maxWithdrawable) + error = `Amount must be at most ${maxWithdrawable} sats`; + if (error) return fail(400, { error }); - let invoice = { amount, type: 'lightning' }; - let user = { username, currency }; + let invoice = { amount, type: "lightning" }; + let user = { username, currency }; - let { text: pr } = await post('/invoice', { invoice, user }, auth(cookies)); + let { text: pr } = await post("/invoice", { invoice, user }, auth(cookies)); - let c = callback.includes('?') ? '&' : '?'; - await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); + let c = callback.includes("?") ? "&" : "?"; + await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); - throw redirect(307, `/ln/withdrawal/sent`); - } + throw redirect(307, `/ln/withdrawal/sent`); + }, }; diff --git a/src/routes/(app)/ln/[lnurl]/+page.svelte b/src/routes/(app)/ln/[lnurl]/+page.svelte index 7656c9fb..9b733f89 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.svelte +++ b/src/routes/(app)/ln/[lnurl]/+page.svelte @@ -1,11 +1,11 @@ <script> - import { Lnurlp, Lnurlw } from '$comp'; - export let data; - let { tag } = data; + import { Lnurlp, Lnurlw } from "$comp"; + export let data; + let { tag } = data; </script> -{#if tag === 'payRequest'} - <Lnurlp {data} /> -{:else if tag === 'withdrawRequest'} - <Lnurlw {data} /> +{#if tag === "payRequest"} + <Lnurlp {data} /> +{:else if tag === "withdrawRequest"} + <Lnurlw {data} /> {/if} diff --git a/src/routes/(app)/ln/withdrawal/sent/+page.svelte b/src/routes/(app)/ln/withdrawal/sent/+page.svelte index 84e6a7e0..c527c017 100644 --- a/src/routes/(app)/ln/withdrawal/sent/+page.svelte +++ b/src/routes/(app)/ln/withdrawal/sent/+page.svelte @@ -1,23 +1,28 @@ <script> - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { t } from '$lib/translations'; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { t } from "$lib/translations"; - export let data; - let { user } = data; - toast.pop(0); + export let data; + let { user } = data; + toast.pop(0); </script> <div class="text-center mt-20 md:mt-0"> - <div class="flex w-full max-w-[200px] mx-auto py-20" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <h1 class="text-3xl md:text-4xl font-bold mb-6">Request Sent!</h1> + <div + class="flex w-full max-w-[200px] mx-auto py-20" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6">Request Sent!</h1> </div> <a href={`/${user.username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <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> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/locations/+server.js b/src/routes/(app)/locations/+server.js index dd9e984b..8a046475 100644 --- a/src/routes/(app)/locations/+server.js +++ b/src/routes/(app)/locations/+server.js @@ -1,15 +1,15 @@ -import { get } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +import { get } from "$lib/utils"; +import { json } from "@sveltejs/kit"; export async function GET({ fetch, setHeaders }) { - let locations = []; + let locations = []; - try { - ({ locations } = await get('/locations')); - setHeaders({ 'cache-control': 'public, max-age=600' }); - } catch (e) { - console.log('failed to fetch locations'); - } + try { + ({ locations } = await get("/locations")); + setHeaders({ "cache-control": "public, max-age=600" }); + } catch (e) { + console.log("failed to fetch locations"); + } - return json({ locations }); + return json({ locations }); } diff --git a/src/routes/(app)/login/+page.server.js b/src/routes/(app)/login/+page.server.js index a9cf867b..6876c93d 100644 --- a/src/routes/(app)/login/+page.server.js +++ b/src/routes/(app)/login/+page.server.js @@ -1,25 +1,29 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { login, fd } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { login, fd } from "$lib/utils"; export const load = async ({ parent }) => { - let { user } = await parent(); - if (user?.pubkey) throw redirect(307, `/${user.username}`); + let { user } = await parent(); + if (user?.pubkey) throw redirect(307, `/${user.username}`); }; export const actions = { - default: async ({ cookies, request }) => { - let form = await fd(request); - let { username, password, token, loginRedirect } = form; - let user = { username, password, token }; + default: async ({ cookies, request }) => { + let form = await fd(request); + let { username, password, token, loginRedirect } = form; + let user = { username, password, token }; - if (loginRedirect === 'undefined') loginRedirect = undefined; + if (loginRedirect === "undefined") loginRedirect = undefined; - try { - let r = await login(user, cookies, request.headers.get('cf-connecting-ip')); - } catch (e) { - return fail(400, { error: 'Login failed', message: e.message, ...form }); - } + try { + let r = await login( + user, + cookies, + request.headers.get("cf-connecting-ip") + ); + } catch (e) { + return fail(400, { error: "Login failed", message: e.message, ...form }); + } - throw redirect(303, loginRedirect || `/${user.username}`); - } + throw redirect(303, loginRedirect || `/${user.username}`); + }, }; diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index 84ecc3ad..6190ae0d 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -1,16 +1,16 @@ <script> - import { RegisterOrLogin } from '$comp'; - import { onMount } from 'svelte'; - import { animatedRate, pin } from '$lib/store'; + import { RegisterOrLogin } from "$comp"; + import { onMount } from "svelte"; + import { animatedRate, pin } from "$lib/store"; - onMount(() => { - $animatedRate = 0; - $pin = undefined; - localStorage.clear(); - sessionStorage.clear(); - }); + onMount(() => { + $animatedRate = 0; + $pin = undefined; + localStorage.clear(); + sessionStorage.clear(); + }); - export let form; + export let form; </script> <RegisterOrLogin pageID="signIn" bind:form /> diff --git a/src/routes/(app)/logout/+page.server.js b/src/routes/(app)/logout/+page.server.js index 2dabc9d4..60750dfa 100644 --- a/src/routes/(app)/logout/+page.server.js +++ b/src/routes/(app)/logout/+page.server.js @@ -1,12 +1,12 @@ -import { redirect } from '@sveltejs/kit'; +import { redirect } from "@sveltejs/kit"; const opts = { - expires: new Date(0), - path: '/' + expires: new Date(0), + path: "/", }; export async function load({ cookies }) { - cookies.set('username', '', opts); - cookies.set('token', '', opts); - throw redirect(307, '/login'); + cookies.set("username", "", opts); + cookies.set("token", "", opts); + throw redirect(307, "/login"); } diff --git a/src/routes/(app)/otpsecret/+server.js b/src/routes/(app)/otpsecret/+server.js index 7d5c1ad9..63d06849 100644 --- a/src/routes/(app)/otpsecret/+server.js +++ b/src/routes/(app)/otpsecret/+server.js @@ -1,15 +1,17 @@ -import { auth, post } from '$lib/utils'; -import Qr from 'qrcode-base64'; -import { error } from '@sveltejs/kit'; +import { auth, post } from "$lib/utils"; +import Qr from "qrcode-base64"; +import { error } from "@sveltejs/kit"; export async function POST({ cookies, request }) { - try { - let body = await request.json(); - let { username, secret } = await post('/otpsecret', body, auth(cookies)); - let uri = `otpauth://totp/coinos:${username}?secret=${secret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`; - return new Response(JSON.stringify({ secret, uri, qr: Qr.drawImg(uri, { size: 600 }) })); - } catch (e) { - if (e.message.includes('pin')) throw e; - throw new error(500, 'Problem saving secret'); - } + try { + let body = await request.json(); + let { username, secret } = await post("/otpsecret", body, auth(cookies)); + let uri = `otpauth://totp/coinos:${username}?secret=${secret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`; + return new Response( + JSON.stringify({ secret, uri, qr: Qr.drawImg(uri, { size: 600 }) }) + ); + } catch (e) { + if (e.message.includes("pin")) throw e; + throw new error(500, "Problem saving secret"); + } } diff --git a/src/routes/(app)/password/+server.js b/src/routes/(app)/password/+server.js index b4a81740..26ed3858 100644 --- a/src/routes/(app)/password/+server.js +++ b/src/routes/(app)/password/+server.js @@ -1,7 +1,7 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let result = await post('/password', await request.json(), auth(cookies)); - return json(result); + let result = await post("/password", await request.json(), auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/payment/[id]/+page.server.js b/src/routes/(app)/payment/[id]/+page.server.js index 1c90ab69..5a364c65 100644 --- a/src/routes/(app)/payment/[id]/+page.server.js +++ b/src/routes/(app)/payment/[id]/+page.server.js @@ -1,6 +1,6 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export async function load({ cookies, params }) { - let payment = await get(`/payments/${params.id}`, auth(cookies)); - return { payment }; + let payment = await get(`/payments/${params.id}`, auth(cookies)); + return { payment }; } diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index 98c5a68b..7e3c6792 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -1,158 +1,173 @@ <script> - import { t } from '$lib/translations'; - import { back, copy, f, s, fiat, post, sats, success, types } from '$lib/utils'; - import { Avatar, Icon } from '$comp'; - import { format } from 'date-fns'; - import { PUBLIC_EXPLORER as expl } from '$env/static/public'; + import { t } from "$lib/translations"; + import { + back, + copy, + f, + s, + fiat, + post, + sats, + success, + types, + } from "$lib/utils"; + import { Avatar, Icon } from "$comp"; + import { format } from "date-fns"; + import { PUBLIC_EXPLORER as expl } from "$env/static/public"; - export let data; - let { user, payment: p } = data; + export let data; + let { user, payment: p } = data; - let { username } = user; - let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; - let a = Math.abs(amount); + let { username } = user; + 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!'); - }; + let print = async () => { + await post(`/payment/${id}/print`, { id }); + success("Printing!"); + }; - fee = fee || 0; + fee = fee || 0; - let direction = amount > 0 ? $t('payments.from') : $t('payments.to') - direction = direction[0].toUpperCase() + direction.substr(1, direction.length); + let direction = amount > 0 ? $t("payments.from") : $t("payments.to"); + direction = + direction[0].toUpperCase() + direction.substr(1, direction.length); </script> <a href={`/${username}/payments`}> - <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"> - <Icon icon="arrow-left" style="w-10" /> - </button> + <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"> + <Icon icon="arrow-left" style="w-10" /> + </button> </a> <div class="container mx-auto max-w-lg px-4 space-y-8 break-all text-2xl"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> - {$t(amount < 0 ? 'payments.sent' : 'payments.received')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> + {$t(amount < 0 ? "payments.sent" : "payments.received")} + </h1> - {#if p.with} - <div> - <span class="text-lg text-secondary my-auto mr-2">{direction}</span> - <a href={`/${p.with.username}`}> - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.with} size={20} /> - </div> - <div class="my-auto ml-1">{p.with.username}</div> - </div> - </a> - </div> - {/if} - {#if p.type === types.fund} - <div> - <span class="text-lg text-secondary my-auto mr-2">{direction}</span> - <a href={`/fund/${p.memo}`}> - <div class="flex"> - <div class="my-auto"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> - </div> - <div class="my-auto ml-1">{p.memo}</div> - </div> - </a> - </div> - {/if} - <div> - <span class="text-lg text-secondary">{$t('payments.amount')}</span> - <div> - {f(fiat(a, rate), currency)} - <span class="text-secondary text-lg"> - ⚡️{s(a)} - </span> - </div> - </div> + {#if p.with} + <div> + <span class="text-lg text-secondary my-auto mr-2">{direction}</span> + <a href={`/${p.with.username}`}> + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.with} size={20} /> + </div> + <div class="my-auto ml-1">{p.with.username}</div> + </div> + </a> + </div> + {/if} + {#if p.type === types.fund} + <div> + <span class="text-lg text-secondary my-auto mr-2">{direction}</span> + <a href={`/fund/${p.memo}`}> + <div class="flex"> + <div class="my-auto"> + <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + </div> + <div class="my-auto ml-1">{p.memo}</div> + </div> + </a> + </div> + {/if} + <div> + <span class="text-lg text-secondary">{$t("payments.amount")}</span> + <div> + {f(fiat(a, rate), currency)} + <span class="text-secondary text-lg"> + ⚡️{s(a)} + </span> + </div> + </div> - {#if tip} - <div> - <span class="text-lg text-secondary">{$t('invoice.tip')}</span> - <div> - {f(fiat(tip, rate), currency)} - ({Math.round((tip * 100) / Math.abs(a))}%) - <span class="text-secondary text-lg"> - ⚡️{s(tip)} - </span> - </div> - </div> + {#if tip} + <div> + <span class="text-lg text-secondary">{$t("invoice.tip")}</span> + <div> + {f(fiat(tip, rate), currency)} + ({Math.round((tip * 100) / Math.abs(a))}%) + <span class="text-secondary text-lg"> + ⚡️{s(tip)} + </span> + </div> + </div> - <div> - <span class="text-lg text-secondary">Total</span> - <div> - {f(fiat(a + (tip || 0), rate), currency)} - <span class="text-secondary text-lg">⚡️{`${s(a + (tip || 0))}`} </span> - </div> - </div> - {/if} + <div> + <span class="text-lg text-secondary">Total</span> + <div> + {f(fiat(a + (tip || 0), rate), currency)} + <span class="text-secondary text-lg">⚡️{`${s(a + (tip || 0))}`} </span> + </div> + </div> + {/if} - <div> - <span class="text-lg text-secondary">Network fee</span> - <div> - {f(fiat(fee, rate), currency)} - <span class="text-secondary text-lg">⚡️{`${s(fee)}`}</span> - </div> - </div> + <div> + <span class="text-lg text-secondary">Network fee</span> + <div> + {f(fiat(fee, rate), currency)} + <span class="text-secondary text-lg">⚡️{`${s(fee)}`}</span> + </div> + </div> - {#if ourfee > 0} - <div> - <span class="text-lg text-secondary">Platform fee</span> - <div> - {f(fiat(ourfee, rate), currency)} - <span class="text-secondary">⚡️{`${s(ourfee)}`}</span> - </div> - </div> - {/if} + {#if ourfee > 0} + <div> + <span class="text-lg text-secondary">Platform fee</span> + <div> + {f(fiat(ourfee, rate), currency)} + <span class="text-secondary">⚡️{`${s(ourfee)}`}</span> + </div> + </div> + {/if} - <div> - <span class="text-lg text-secondary">Date</span> - <div> - {format(new Date(created), 'MMMM d')}, - {format(new Date(created), 'h:mm aaa')} - </div> - </div> + <div> + <span class="text-lg text-secondary">Date</span> + <div> + {format(new Date(created), "MMMM d")}, + {format(new Date(created), "h:mm aaa")} + </div> + </div> - {#if type === 'bitcoin'} - <div> - <span class="text-lg text-secondary">Txid</span> - <div class="flex"> - <div> - <a href={`${expl}/tx/${id}`} target="_blank" rel="noreferrer">{id}</a> - </div> - <button class="flex font-bold hover:opacity-80 mb-auto my-auto" on:click={() => copy(id)} - ><Icon icon="copy" style="ml-2 w-20 my-auto" /> - </button> - </div> - </div> - {/if} + {#if type === "bitcoin"} + <div> + <span class="text-lg text-secondary">Txid</span> + <div class="flex"> + <div> + <a href={`${expl}/tx/${id}`} target="_blank" rel="noreferrer">{id}</a> + </div> + <button + class="flex font-bold hover:opacity-80 mb-auto my-auto" + on:click={() => copy(id)} + ><Icon icon="copy" style="ml-2 w-20 my-auto" /> + </button> + </div> + </div> + {/if} - {#if user.hasprinter} - <div> - <button - class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60" - on:click={print} - > - <div class="mx-auto flex"> - <Icon icon="printer" style="my-auto h-6 mr-2" /> - <div class="my-auto mt-1 text-base">{$t('payments.print')}</div> - </div> - </button> - </div> - {:else} - <div> - <a href={`/payment/${id}/plain`}> - <button class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60"> - <div class="mx-auto flex"> - <Icon icon="printer" style="my-auto h-6 mr-2" /> - <div class="my-auto mt-1 text-base">{$t('payments.print')}</div> - </div> - </button> - </a> - </div> - {/if} + {#if user.hasprinter} + <div> + <button + class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60" + on:click={print} + > + <div class="mx-auto flex"> + <Icon icon="printer" style="my-auto h-6 mr-2" /> + <div class="my-auto mt-1 text-base">{$t("payments.print")}</div> + </div> + </button> + </div> + {:else} + <div> + <a href={`/payment/${id}/plain`}> + <button + class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60" + > + <div class="mx-auto flex"> + <Icon icon="printer" style="my-auto h-6 mr-2" /> + <div class="my-auto mt-1 text-base">{$t("payments.print")}</div> + </div> + </button> + </a> + </div> + {/if} </div> diff --git a/src/routes/(app)/payment/[id]/plain/+page.server.js b/src/routes/(app)/payment/[id]/plain/+page.server.js index 1c90ab69..5a364c65 100644 --- a/src/routes/(app)/payment/[id]/plain/+page.server.js +++ b/src/routes/(app)/payment/[id]/plain/+page.server.js @@ -1,6 +1,6 @@ -import { auth, get } from '$lib/utils'; +import { auth, get } from "$lib/utils"; export async function load({ cookies, params }) { - let payment = await get(`/payments/${params.id}`, auth(cookies)); - return { payment }; + let payment = await get(`/payments/${params.id}`, auth(cookies)); + return { payment }; } diff --git a/src/routes/(app)/payment/[id]/plain/+page.svelte b/src/routes/(app)/payment/[id]/plain/+page.svelte index 5ffd1656..27e8d0ed 100644 --- a/src/routes/(app)/payment/[id]/plain/+page.svelte +++ b/src/routes/(app)/payment/[id]/plain/+page.svelte @@ -1,77 +1,87 @@ <script> - import { goto } from '$app/navigation'; - import { onMount } from 'svelte'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { back, copy, f, s, fiat, post, sats, success, types } from '$lib/utils'; - import { Avatar, Icon } from '$comp'; - import { format } from 'date-fns'; - import { PUBLIC_EXPLORER as expl } from '$env/static/public'; + import { goto } from "$app/navigation"; + import { onMount } from "svelte"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { + back, + copy, + f, + s, + fiat, + post, + sats, + success, + types, + } from "$lib/utils"; + import { Avatar, Icon } from "$comp"; + import { format } from "date-fns"; + import { PUBLIC_EXPLORER as expl } from "$env/static/public"; - export let data; - let { user, payment: p } = data; + export let data; + let { user, payment: p } = data; - let { username } = user; - let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; - let a = Math.abs(amount); + let { username } = user; + let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; + let a = Math.abs(amount); - onMount(() => { - if (browser) { - let main = document.querySelector('main'); - main.style.paddingBottom = '0'; - window.onfocus = () => goto(`/payment/${id}`); - window.print(); - } - }); + onMount(() => { + if (browser) { + let main = document.querySelector("main"); + main.style.paddingBottom = "0"; + window.onfocus = () => goto(`/payment/${id}`); + window.print(); + } + }); - fee = fee || 0; + fee = fee || 0; </script> <div class="space-y-5"> - <div> - <span class="text-lg text-secondary">Amount</span> - <div> - {f(fiat(a, rate), currency)} - </div> - </div> + <div> + <span class="text-lg text-secondary">Amount</span> + <div> + {f(fiat(a, rate), currency)} + </div> + </div> - {#if tip} - <div> - <span class="text-lg text-secondary">Tip</span> - <div> - {f(fiat(tip, rate), currency)} - ({Math.round((tip * 100) / Math.abs(a))}%) - </div> - </div> + {#if tip} + <div> + <span class="text-lg text-secondary">Tip</span> + <div> + {f(fiat(tip, rate), currency)} + ({Math.round((tip * 100) / Math.abs(a))}%) + </div> + </div> - <div> - <span class="text-lg text-secondary">Total</span> - <div> - {f(fiat(a + (tip || 0), rate), currency)} - </div> - </div> - {/if} + <div> + <span class="text-lg text-secondary">Total</span> + <div> + {f(fiat(a + (tip || 0), rate), currency)} + </div> + </div> + {/if} - <div> - <span class="text-lg text-secondary">Date</span> - <div> - {format(new Date(created), 'MMMM d')}, - {format(new Date(created), 'h:mm aaa')} - </div> - </div> + <div> + <span class="text-lg text-secondary">Date</span> + <div> + {format(new Date(created), "MMMM d")}, + {format(new Date(created), "h:mm aaa")} + </div> + </div> - {#if type === 'bitcoin'} - <div> - <span class="text-lg text-secondary">Txid</span> - <div class="flex"> - <div> - {id} - </div> - </div> - </div> - {/if} + {#if type === "bitcoin"} + <div> + <span class="text-lg text-secondary">Txid</span> + <div class="flex"> + <div> + {id} + </div> + </div> + </div> + {/if} </div> <a href={`/payment/${id}`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> diff --git a/src/routes/(app)/payment/[id]/print/+server.js b/src/routes/(app)/payment/[id]/print/+server.js index fde834d1..2fdfc8c1 100644 --- a/src/routes/(app)/payment/[id]/print/+server.js +++ b/src/routes/(app)/payment/[id]/print/+server.js @@ -1,7 +1,7 @@ -import { auth, post } from '$lib/utils'; -import { json } from '@sveltejs/kit'; +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))); + let body = await request.json(); + return json(post("/print", body, auth(cookies))); } diff --git a/src/routes/(app)/pin/+server.js b/src/routes/(app)/pin/+server.js index 9f63f545..76d407bf 100644 --- a/src/routes/(app)/pin/+server.js +++ b/src/routes/(app)/pin/+server.js @@ -1,7 +1,7 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let result = await post('/pin', await request.json(), auth(cookies)); - return json(result); + let result = await post("/pin", await request.json(), auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/qr/[text]/+page.server.js b/src/routes/(app)/qr/[text]/+page.server.js index 3ab0725f..5eaafe57 100644 --- a/src/routes/(app)/qr/[text]/+page.server.js +++ b/src/routes/(app)/qr/[text]/+page.server.js @@ -1,8 +1,8 @@ -import Qr from 'qrcode-base64'; +import Qr from "qrcode-base64"; export async function load({ params }) { - let { text } = params; - let src = Qr.drawImg(text, { size: 600 }); + let { text } = params; + let src = Qr.drawImg(text, { size: 600 }); - return { src, text }; + return { src, text }; } diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte index f3cd791a..d943b0b5 100644 --- a/src/routes/(app)/qr/[text]/+page.svelte +++ b/src/routes/(app)/qr/[text]/+page.svelte @@ -1,24 +1,24 @@ <script> - import { Icon, Qr } from '$comp'; + import { Icon, Qr } from "$comp"; import { back } from "$lib/utils"; - export let data; - let { src, text } = data; + export let data; + let { src, text } = data; </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-4xl mx-auto mt-10 space-y-5"> - <div class="flex justify-center items-center"> - <div class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5"> + <div class="flex justify-center items-center"> + <div + class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5" + > <Qr {src} /> <div class="text-center w-full text-2xl py-10 break-all"> {text} </div> - </div> - </div> + </div> + </div> </div> diff --git a/src/routes/(app)/redeem/[redeemcode]/+page.server.js b/src/routes/(app)/redeem/[redeemcode]/+page.server.js index 4f685d68..f227f22e 100644 --- a/src/routes/(app)/redeem/[redeemcode]/+page.server.js +++ b/src/routes/(app)/redeem/[redeemcode]/+page.server.js @@ -1,17 +1,17 @@ -import { error, redirect } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { error, redirect } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export async function load({ cookies, locals, params }) { - let { user } = locals; + let { user } = locals; - if (user) { - try { - await post('/redeem', params, auth(cookies)); - } catch (e) { - throw redirect(307, '/'); - } - throw redirect(307, `/${user.username}/payments`); - } + if (user) { + try { + await post("/redeem", params, auth(cookies)); + } catch (e) { + throw redirect(307, "/"); + } + throw redirect(307, `/${user.username}/payments`); + } - throw redirect(307, '/login'); + throw redirect(307, "/login"); } diff --git a/src/routes/(app)/redeem/[redeemcode]/+page.svelte b/src/routes/(app)/redeem/[redeemcode]/+page.svelte index a4609307..b2fcf2f4 100644 --- a/src/routes/(app)/redeem/[redeemcode]/+page.svelte +++ b/src/routes/(app)/redeem/[redeemcode]/+page.svelte @@ -1,4 +1,4 @@ <script> - import { loginRedirect } from '$lib/store'; - $loginRedirect = undefined; + import { loginRedirect } from "$lib/store"; + $loginRedirect = undefined; </script> diff --git a/src/routes/(app)/register/+page.server.js b/src/routes/(app)/register/+page.server.js index 45344907..e986c73d 100644 --- a/src/routes/(app)/register/+page.server.js +++ b/src/routes/(app)/register/+page.server.js @@ -1,41 +1,42 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { fd, post, login } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { fd, post, login } from "$lib/utils"; export const load = async ({ parent }) => { - let { user } = await parent(); - if (user) throw redirect(307, `/${user.username}`); + let { user } = await parent(); + if (user) throw redirect(307, `/${user.username}`); - let index = Math.floor(Math.random() * 64) + 1; + let index = Math.floor(Math.random() * 64) + 1; - return { index }; + return { index }; }; export const actions = { - default: async ({ cookies, request }) => { - let ip = request.headers.get('cf-connecting-ip'); + default: async ({ cookies, request }) => { + let ip = request.headers.get("cf-connecting-ip"); - let form = await fd(request); - let { profile, username, password, cipher, salt, pubkey, loginRedirect } = form; - let user = { profile, username, password, cipher, salt, pubkey }; - let error; + let form = await fd(request); + let { profile, username, password, cipher, salt, pubkey, loginRedirect } = + form; + let user = { profile, username, password, cipher, salt, pubkey }; + let error; - if (loginRedirect === 'undefined') loginRedirect = undefined; + if (loginRedirect === "undefined") loginRedirect = undefined; - try { - await post('/register', { user }, { 'cf-connecting-ip': ip }); - } catch (e) { - console.log(e); - if (e.message.includes('taken')) error = e.message; - } + try { + await post("/register", { user }, { "cf-connecting-ip": ip }); + } catch (e) { + console.log(e); + if (e.message.includes("taken")) error = e.message; + } - try { - await login(user, cookies, ip); - error = null; - } catch (e) { - error ||= e.message; - } + try { + await login(user, cookies, ip); + error = null; + } catch (e) { + error ||= e.message; + } - if (error) return fail(400, { error }); - throw redirect(307, loginRedirect || `/${user.username}`); - } + if (error) return fail(400, { error }); + throw redirect(307, loginRedirect || `/${user.username}`); + }, }; diff --git a/src/routes/(app)/register/+page.svelte b/src/routes/(app)/register/+page.svelte index a9f9d047..8310aed1 100644 --- a/src/routes/(app)/register/+page.svelte +++ b/src/routes/(app)/register/+page.svelte @@ -1,287 +1,311 @@ <script> - import punks from '$lib/punks'; - import { upload } from '$lib/upload'; - import { afterNavigate, invalidateAll } from '$app/navigation'; - import { applyAction, deserialize } from '$app/forms'; - import { tick } from 'svelte'; - import { fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { Pin, Icon, Spinner } from '$comp'; - import { focus, fail } from '$lib/utils'; - import { avatar, password, loginRedirect } from '$lib/store'; - import { t } from '$lib/translations'; - import { page } from '$app/stores'; - import { generate } from '$lib/nostr'; - import { - NumberDictionary, - uniqueNamesGenerator, - colors, - adjectives, - animals - } from 'unique-names-generator'; + import punks from "$lib/punks"; + import { upload } from "$lib/upload"; + import { afterNavigate, invalidateAll } from "$app/navigation"; + import { applyAction, deserialize } from "$app/forms"; + import { tick } from "svelte"; + import { fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { Pin, Icon, Spinner } from "$comp"; + import { focus, fail } from "$lib/utils"; + import { avatar, password, loginRedirect } from "$lib/store"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; + import { generate } from "$lib/nostr"; + import { + NumberDictionary, + uniqueNamesGenerator, + colors, + adjectives, + animals, + } from "unique-names-generator"; - export let form; - export let data; + export let form; + export let data; - $: data && ($avatar = undefined); - let username; - let { index } = data; + $: data && ($avatar = undefined); + let username; + let { index } = data; - let cleared; - let clear = () => { - if (!cleared) { - cleared = true; - username = ''; - $password = ''; - revealPassword = false; - } - }; + let cleared; + let clear = () => { + if (!cleared) { + cleared = true; + username = ""; + $password = ""; + revealPassword = false; + } + }; - let refresh = async () => { - username = uniqueNamesGenerator({ - dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], - length: 2, - separator: '' - }); + let refresh = async () => { + username = uniqueNamesGenerator({ + dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], + length: 2, + separator: "", + }); - $password = uniqueNamesGenerator({ - dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], - length: 2, - separator: '' - }); + $password = uniqueNamesGenerator({ + dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], + length: 2, + separator: "", + }); - revealPassword = true; - }; + revealPassword = true; + }; - afterNavigate(async () => { - try { - await invalidateAll(); - } catch (e) { - console.log(e); - } - }); + afterNavigate(async () => { + try { + await invalidateAll(); + } catch (e) { + console.log(e); + } + }); - let token, formElement; - let code = []; - let redirect; + let token, formElement; + let code = []; + let redirect; - $: need2fa = form?.message === '2fa'; - let cancel = () => (need2fa = false); - $: if (form?.message === '2fa' && form.token === token) token = ''; + $: need2fa = form?.message === "2fa"; + let cancel = () => (need2fa = false); + $: if (form?.message === "2fa" && form.token === token) token = ""; - let email, btn; + let email, btn; - $: update(form); - let update = (form) => form && ({ username, password: $password } = form); + $: update(form); + let update = (form) => form && ({ username, password: $password } = form); - $: token && token?.length === 6 && tick().then(() => btn.click()); + $: token && token?.length === 6 && tick().then(() => btn.click()); - let revealPassword; + let revealPassword; - let loading; - async function handleSubmit(e) { - loading = true; + let loading; + async function handleSubmit(e) { + loading = true; - let data = new FormData(this); - let user = Object.fromEntries(data); - await generate(user); + let data = new FormData(this); + let user = Object.fromEntries(data); + await generate(user); - for (let k in user) { - data.set(k, user[k]); - } + for (let k in user) { + data.set(k, user[k]); + } - data.set('profile', punks[index]); - if ($avatar) { - try { - let { hash } = JSON.parse( - await upload($avatar.file, $avatar.type, $avatar.progress, token) - ); + data.set("profile", punks[index]); + if ($avatar) { + try { + let { hash } = JSON.parse( + await upload($avatar.file, $avatar.type, $avatar.progress, token) + ); - data.set('profile', hash); - await fetch(`/api/public/${hash}.webp`, { cache: 'reload', mode: 'no-cors' }); - } catch (e) { - console.log('problem uploading avatar', e); - } - } + data.set("profile", hash); + await fetch(`/api/public/${hash}.webp`, { + cache: "reload", + mode: "no-cors", + }); + } catch (e) { + console.log("problem uploading avatar", e); + } + } - const response = await fetch('/register', { - method: 'POST', - body: data - }); + const response = await fetch("/register", { + method: "POST", + body: data, + }); - const result = deserialize(await response.text()); + const result = deserialize(await response.text()); - if (result.type === 'success') { - await invalidateAll(); - } + if (result.type === "success") { + await invalidateAll(); + } - applyAction(result); - loading = false; - } + applyAction(result); + loading = false; + } - let avatarInput; - $: src = `/api/public/${punks[index]}.webp`; - let decr = () => (index = index <= 0 ? 63 : index - 1); - let incr = () => (index = index >= 63 ? 0 : index + 1); - let selectAvatar = () => avatarInput.click(); + let avatarInput; + $: src = `/api/public/${punks[index]}.webp`; + let decr = () => (index = index <= 0 ? 63 : index - 1); + let incr = () => (index = index >= 63 ? 0 : index + 1); + let selectAvatar = () => avatarInput.click(); - let progress; - let handleFile = async ({ target }) => { - let type = 'profile'; - let file = target.files[0]; - if (!file) return; + let progress; + let handleFile = async ({ target }) => { + let type = "profile"; + let file = target.files[0]; + if (!file) return; - if (file.size > 10000000) form.error = 'File too large'; - $avatar = { file, type, progress }; + if (file.size > 10000000) form.error = "File too large"; + $avatar = { file, type, progress }; - var reader = new FileReader(); - reader.onload = async (e) => { - src = e.target.result; - }; + var reader = new FileReader(); + reader.onload = async (e) => { + src = e.target.result; + }; - reader.readAsDataURL(file); - }; + reader.readAsDataURL(file); + }; </script> {#if need2fa} - <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> + <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> {/if} <div class="pt-4"> - <div class="w-[243px] mx-auto"> - <a href="/"> - <Icon icon="logo" /> - </a> - </div> + <div class="w-[243px] mx-auto"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> - <div class="flex justify-center items-center"> - <div class="shadow-xl rounded-3xl p-4 pt-0 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"> - <input - type="file" - class="hidden" - bind:this={avatarInput} - on:change={(e) => handleFile(e, 'profile')} - /> + <div class="flex justify-center items-center"> + <div + class="shadow-xl rounded-3xl p-4 pt-0 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]" + > + <input + type="file" + class="hidden" + bind:this={avatarInput} + on:change={(e) => handleFile(e, "profile")} + /> - <div class="relative"> - <button class="absolute w-8 h-12 left-12 bg-white rounded top-12" on:click={decr}> - <Icon icon="chevron-left" style="w-8 " /> - </button> - <div class="relative w-32 mx-auto" on:click={selectAvatar}> - <button - class="w-32 h-32 rounded-full border-4 border-white overflow-hidden flex mx-auto relative" - > - <img - {src} - class="w-full h-full object-cover object-center overflow-hidden" - alt={username} - /> - </button> - <button - class="absolute bg-white rounded-full p-2 mx-auto right-0 bottom-0 z-10 bg-opacity-80" - > - <Icon icon="upload" style="w-8" /> - </button> - </div> - <button class="absolute w-8 h-12 right-12 bg-white rounded top-12" on:click={incr}> - <Icon icon="chevron-left" style="w-8 rotate-180" /> - </button> - </div> + <div class="relative"> + <button + class="absolute w-8 h-12 left-12 bg-white rounded top-12" + on:click={decr} + > + <Icon icon="chevron-left" style="w-8 " /> + </button> + <div class="relative w-32 mx-auto" on:click={selectAvatar}> + <button + class="w-32 h-32 rounded-full border-4 border-white overflow-hidden flex mx-auto relative" + > + <img + {src} + class="w-full h-full object-cover object-center overflow-hidden" + alt={username} + /> + </button> + <button + class="absolute bg-white rounded-full p-2 mx-auto right-0 bottom-0 z-10 bg-opacity-80" + > + <Icon icon="upload" style="w-8" /> + </button> + </div> + <button + class="absolute w-8 h-12 right-12 bg-white rounded top-12" + on:click={incr} + > + <Icon icon="chevron-left" style="w-8 rotate-180" /> + </button> + </div> - {#if form?.error} - <div class="text-red-600 text-center" in:fly> - {form.error} - </div> - {/if} + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} - <form class="space-y-5" on:submit|preventDefault={handleSubmit} method="POST"> - <input - type="hidden" - name="loginRedirect" - value={$loginRedirect || $page.url.searchParams.get('redirect')} - /> - <input type="hidden" name="token" value={token} /> + <form + class="space-y-5" + on:submit|preventDefault={handleSubmit} + method="POST" + > + <input + type="hidden" + name="loginRedirect" + value={$loginRedirect || $page.url.searchParams.get("redirect")} + /> + <input type="hidden" name="token" value={token} /> - <div class="relative"> - <label for="username" class="font-semibold">{$t('login.username')}</label> - <input - use:focus - tabindex="1" - name="username" - type="text" - required - class="bg-primary" - bind:value={username} - on:focus={clear} - autocapitalize="none" - /> - <button type="button" on:click={refresh} class="absolute right-0 top-4" tabindex="5"> - <Icon icon="random" style="w-10 m-4" /> - </button> - </div> + <div class="relative"> + <label for="username" class="font-semibold" + >{$t("login.username")}</label + > + <input + use:focus + tabindex="1" + name="username" + type="text" + required + class="bg-primary" + bind:value={username} + on:focus={clear} + autocapitalize="none" + /> + <button + type="button" + on:click={refresh} + class="absolute right-0 top-4" + tabindex="5" + > + <Icon icon="random" style="w-10 m-4" /> + </button> + </div> - <div class="relative"> - <label for="password" class="block font-semibold">{$t('login.password')}</label> - {#if revealPassword} - <input - tabindex="2" - name="password" - type="text" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - /> - {:else} - <input - tabindex="2" - name="password" - type="password" - required - class="bg-primary" - bind:value={$password} - autocapitalize="none" - on:focus={clear} - /> - {/if} - <button - type="button" - tabindex="4" - on:click={() => (revealPassword = !revealPassword)} - class="absolute right-0 top-4" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} style="m-6" /> - </button> - </div> + <div class="relative"> + <label for="password" class="block font-semibold" + >{$t("login.password")}</label + > + {#if revealPassword} + <input + tabindex="2" + name="password" + type="text" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {:else} + <input + tabindex="2" + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + on:focus={clear} + /> + {/if} + <button + type="button" + tabindex="4" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-0 top-4" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} style="m-6" /> + </button> + </div> - <p class="text-secondary text-sm"> - {$t('login.passwordRecommendation')} - </p> + <p class="text-secondary text-sm"> + {$t("login.passwordRecommendation")} + </p> - <button - tabindex="3" - type="submit" - class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" - disabled={loading} - bind:this={btn} - > - {#if loading} - <Spinner /> - {:else} - {$t('login.register')} - {/if} - </button> - </form> + <button + tabindex="3" + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + bind:this={btn} + > + {#if loading} + <Spinner /> + {:else} + {$t("login.register")} + {/if} + </button> + </form> - <p class="text-secondary text-center font-medium"> - {$t('login.haveAccount')} - <a - href={'/login'} - class="block md:inline text-black underline underline-offset-4 hover:opacity-80" - > - {$t('login.signIn')} - </a> - </p> - </div> - </div> + <p class="text-secondary text-center font-medium"> + {$t("login.haveAccount")} + <a + href={"/login"} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {$t("login.signIn")} + </a> + </p> + </div> + </div> </div> diff --git a/src/routes/(app)/scan/+page.svelte b/src/routes/(app)/scan/+page.svelte index 8c5ea0a5..033eb28b 100644 --- a/src/routes/(app)/scan/+page.svelte +++ b/src/routes/(app)/scan/+page.svelte @@ -1,44 +1,51 @@ <script> - import { Icon } from '$comp'; - import QrScanner from 'qr-scanner'; - import { onMount, onDestroy } from 'svelte'; - import { back } from '$lib/utils'; - import { goto } from '$app/navigation'; + import { Icon } from "$comp"; + import QrScanner from "qr-scanner"; + import { onMount, onDestroy } from "svelte"; + import { back } from "$lib/utils"; + import { goto } from "$app/navigation"; - let scanner, vid; - onMount(() => { - scanner = new QrScanner(vid, ({ data }) => scanner.stop() || goto(`/send/${encodeURI(data)}`), { - highlightScanRegion: true, - highlightCodeOutline: true - }); - scanner.start(); - }); + let scanner, vid; + onMount(() => { + scanner = new QrScanner( + vid, + ({ data }) => scanner.stop() || goto(`/send/${encodeURI(data)}`), + { + highlightScanRegion: true, + highlightCodeOutline: true, + } + ); + scanner.start(); + }); - onDestroy(() => scanner?.stop()); + onDestroy(() => scanner?.stop()); </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> <div class="flex w-full mb-4 px-4"> - <div class="bg-black mx-auto rounded-3xl"> - <video - bind:this={vid} - class="border-4 rounded-3xl border-black md:max-w-[600px] !max-h-[80vh] !min-w-[300px]" - /> - </div> + <div class="bg-black mx-auto rounded-3xl"> + <video + bind:this={vid} + class="border-4 rounded-3xl border-black md:max-w-[600px] !max-h-[80vh] !min-w-[300px]" + /> + </div> </div> <div class="flex w-full mb-4"> - <button class="mx-auto border rounded-full px-6 py-2 font-bold hover:opacity-80" on:click={back}> - Cancel - </button> + <button + class="mx-auto border rounded-full px-6 py-2 font-bold hover:opacity-80" + on:click={back} + > + Cancel + </button> </div> <style> - video { - transition: transform 0.3s ease-out; - transform: scale(1); - } + video { + transition: transform 0.3s ease-out; + transform: scale(1); + } </style> diff --git a/src/routes/(app)/send/+layout.svelte b/src/routes/(app)/send/+layout.svelte index 47c11f1c..1a587edf 100644 --- a/src/routes/(app)/send/+layout.svelte +++ b/src/routes/(app)/send/+layout.svelte @@ -1,16 +1,16 @@ <script> - import { goto } from '$app/navigation'; - import { Pin } from '$comp'; - import { pin } from '$lib/store'; + import { goto } from "$app/navigation"; + import { Pin } from "$comp"; + import { pin } from "$lib/store"; - export let data; + export let data; - let { user } = data; - let cancel = () => goto(`/${user.username}`); + let { user } = data; + let cancel = () => goto(`/${user.username}`); </script> {#if user.haspin && (!$pin || $pin?.length !== 6)} - <Pin bind:value={$pin} {cancel} /> + <Pin bind:value={$pin} {cancel} /> {/if} <slot /> diff --git a/src/routes/(app)/send/[...text]/+page.server.js b/src/routes/(app)/send/[...text]/+page.server.js index 3ca50bca..9a76f21d 100644 --- a/src/routes/(app)/send/[...text]/+page.server.js +++ b/src/routes/(app)/send/[...text]/+page.server.js @@ -1,100 +1,100 @@ -import { validate } from 'bitcoin-address-validation'; -import bip21 from 'bip21'; -import { auth, get, post } from '$lib/utils'; -import { fail, redirect } from '@sveltejs/kit'; -import { PUBLIC_DOMAIN } from '$env/static/public'; +import { validate } from "bitcoin-address-validation"; +import bip21 from "bip21"; +import { auth, get, post } from "$lib/utils"; +import { fail, redirect } from "@sveltejs/kit"; +import { PUBLIC_DOMAIN } from "$env/static/public"; let parse = async (t, host) => { - if (!t) return; + if (!t) return; - if (t.startsWith('http')) throw redirect(307, t); - if (t.startsWith(host)) throw redirect(307, `http://${t}`); + if (t.startsWith("http")) throw redirect(307, t); + if (t.startsWith(host)) throw redirect(307, `http://${t}`); - let amount, user, id; + let amount, user, id; - t = t.trim(); - t.toLowerCase().startsWith('bitcoin:') && - ({ - address: t, - options: { amount } - } = bip21.decode(t)); + t = t.trim(); + t.toLowerCase().startsWith("bitcoin:") && + ({ + address: t, + options: { amount }, + } = bip21.decode(t)); - if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split('@')[0]; - if (t.includes('@') && t.includes('.')) { - try { - t = await get(`/encode?address=${t}`); - } catch (e) {} - } + if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0]; + if (t.includes("@") && t.includes(".")) { + try { + t = await get(`/encode?address=${t}`); + } catch (e) {} + } - if (t.endsWith('@classic')) { - ({ uuid: id } = await get(`/invoice/classic/${t.replace('@classic', '')}`)); - if (id) throw redirect(307, `/send/invoice/${id}`); - } + if (t.endsWith("@classic")) { + ({ uuid: id } = await get(`/invoice/classic/${t.replace("@classic", "")}`)); + if (id) throw redirect(307, `/send/invoice/${id}`); + } - if (t.includes('/fund')) throw redirect(307, t.substring(t.indexOf('/fund'))); + if (t.includes("/fund")) throw redirect(307, t.substring(t.indexOf("/fund"))); - if (t.toLowerCase().startsWith('lnurl')) throw redirect(307, `/ln/${t}`); - if (t.includes(':')) t = t.split(':')[1]; + if (t.toLowerCase().startsWith("lnurl")) throw redirect(307, `/ln/${t}`); + if (t.includes(":")) t = t.split(":")[1]; - // lightning - if (t.toLowerCase().startsWith('ln')) { - try { - ({ id } = await get(`/invoice/${t}`)); - } catch (e) { - throw redirect(307, `/send/lightning/${t}`); - } - } + // lightning + if (t.toLowerCase().startsWith("ln")) { + try { + ({ id } = await get(`/invoice/${t}`)); + } catch (e) { + throw redirect(307, `/send/lightning/${t}`); + } + } - // bitcoin - if (validate(t)) { - try { - ({ id, user } = await get(`/invoice/${t}`)); - } catch (e) { - let r = `/send/bitcoin/${t}`; - if (amount) r += '/' + amount; - throw redirect(307, r); - } + // bitcoin + if (validate(t)) { + try { + ({ id, user } = await get(`/invoice/${t}`)); + } catch (e) { + let r = `/send/bitcoin/${t}`; + if (amount) r += "/" + amount; + throw redirect(307, r); + } - if (user) throw redirect(307, `/send/${user.username}`); - else if (id) throw redirect(307, `/send/${id}`); - } + if (user) throw redirect(307, `/send/${user.username}`); + else if (id) throw redirect(307, `/send/${id}`); + } - // user - try { - user = await get(`/users/${t.split('/')[0]}`); - if (user.anon) user = null; - } catch (e) {} + // user + try { + user = await get(`/users/${t.split("/")[0]}`); + if (user.anon) user = null; + } catch (e) {} - if (user) throw redirect(307, `/send/user/${t}`); + if (user) throw redirect(307, `/send/user/${t}`); - let fund; - try { - fund = await get(`/fund/${t}`); - } catch (e) {} + let fund; + try { + fund = await get(`/fund/${t}`); + } catch (e) {} - if (fund) throw redirect(307, `/send/fund/${t}`); + if (fund) throw redirect(307, `/send/fund/${t}`); - // invoice - let invoice; - try { - invoice = await get(`/invoice/${t}`); - } catch (e) {} + // invoice + let invoice; + try { + invoice = await get(`/invoice/${t}`); + } catch (e) {} - if (invoice) throw redirect(307, `/send/invoice/${invoice.hash}`); + if (invoice) throw redirect(307, `/send/invoice/${invoice.hash}`); }; export async function load({ cookies, params, request, url }) { - let { text } = params; - await parse(text, url.host); - let contacts = await get('/contacts', auth(cookies)); - return { contacts }; + let { text } = params; + await parse(text, url.host); + let contacts = await get("/contacts", auth(cookies)); + return { contacts }; } export const actions = { - default: async ({ request, url }) => { - const form = await request.formData(); - let t = form.get('text'); - await parse(t, url.host); - return fail(400, { error: true }); - } + default: async ({ request, url }) => { + const form = await request.formData(); + let t = form.get("text"); + await parse(t, url.host); + return fail(400, { error: true }); + }, }; diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index 74afd846..67d4afaa 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -1,125 +1,129 @@ <script> - import { page } from '$app/stores'; - import { fly } from 'svelte/transition'; - import { enhance } from '$app/forms'; - import { tick } from 'svelte'; - import { browser } from '$app/environment'; - import { t } from '$lib/translations'; - import { AppHeader, Avatar, Icon, Spinner } from '$comp'; - import { back, fail } from '$lib/utils'; + import { page } from "$app/stores"; + import { fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import { tick } from "svelte"; + import { browser } from "$app/environment"; + import { t } from "$lib/translations"; + import { AppHeader, Avatar, Icon, Spinner } from "$comp"; + import { back, fail } from "$lib/utils"; - export let data; - export let form; - data.subject = data.user; + export let data; + export let form; + data.subject = data.user; - let { contacts } = data; - let w; + let { contacts } = data; + let w; - let el, textarea, text; - let placeholder = $t('user.send.placeholder'); + let el, textarea, text; + let placeholder = $t("user.send.placeholder"); - $: $page && setTimeout(() => w > 800 && textarea?.focus(), 0); - let keypress = (e) => e.key === 'Enter' && (e.preventDefault() || el.click()); + $: $page && setTimeout(() => w > 800 && textarea?.focus(), 0); + let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click()); - let paste = async () => { - text = await navigator.clipboard.readText(); - }; + let paste = async () => { + text = await navigator.clipboard.readText(); + }; </script> <svelte:window bind:innerWidth={w} /> <AppHeader {data} /> <div class="container px-4 max-w-lg mx-auto space-y-5 mt-20"> - <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> - {$t('payments.send')} - </h1> + <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> + {$t("payments.send")} + </h1> - <form method="POST" use:enhance> - {#if form?.error} - <div class="mb-5"> - <div class="text-red-600"> - {$t('error.unrecognizedInput')} - </div> - </div> - {/if} + <form method="POST" use:enhance> + {#if form?.error} + <div class="mb-5"> + <div class="text-red-600"> + {$t("error.unrecognizedInput")} + </div> + </div> + {/if} - <div class="mb-2"> - <input type="hidden" name="text" bind:value={text} /> + <div class="mb-2"> + <input type="hidden" name="text" bind:value={text} /> - <textarea - {placeholder} - on:keypress={keypress} - class="w-full p-4 border rounded-xl h-48" - bind:value={text} - bind:this={textarea} - /> - </div> + <textarea + {placeholder} + on:keypress={keypress} + class="w-full p-4 border rounded-xl h-48" + bind:value={text} + bind:this={textarea} + /> + </div> - <div class="flex justify-end"> - {#if text?.length} - <a href={`/qr/${encodeURIComponent(text)}`}> - <button - type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" - > - <Icon icon="qr" style="mr-2 w-6 my-auto invert" /> - <div class="my-auto">{$t('user.send.makeQR')}</div> - </button> - </a> - {/if} + <div class="flex justify-end"> + {#if text?.length} + <a href={`/qr/${encodeURIComponent(text)}`}> + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + > + <Icon icon="qr" style="mr-2 w-6 my-auto invert" /> + <div class="my-auto">{$t("user.send.makeQR")}</div> + </button> + </a> + {/if} - <a href="/scan"> - <button - type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" - > - <Icon icon="camera" style="mr-2 w-6 my-auto" /> - <div class="my-auto">{$t('user.send.scan')}</div> - </button> - </a> + <a href="/scan"> + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + > + <Icon icon="camera" style="mr-2 w-6 my-auto" /> + <div class="my-auto">{$t("user.send.scan")}</div> + </button> + </a> - <button - type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" - on:click={paste} - > - <Icon icon="paste" style="mr-2 w-6 my-auto" /> - <div class="my-auto">{$t('user.send.paste')}</div> - </button> + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + on:click={paste} + > + <Icon icon="paste" style="mr-2 w-6 my-auto" /> + <div class="my-auto">{$t("user.send.paste")}</div> + </button> - <button - bind:this={el} - type="submit" - class="{!text - ? 'opacity-50' - : 'opacity-100 hover:opacity-80'} bg-black text-white border rounded-full px-6 py-2 font-bold" - > - {$t('user.send.next')} - </button> - </div> - </form> + <button + bind:this={el} + type="submit" + class="{!text + ? 'opacity-50' + : 'opacity-100 hover:opacity-80'} bg-black text-white border rounded-full px-6 py-2 font-bold" + > + {$t("user.send.next")} + </button> + </div> + </form> - {#if contacts.length} - <div class="space-y-5"> - <h1 class="px-3 md:px-0 text-xl font-semibold mt-10">{$t('user.send.contacts')}</h1> - <div> - {#each contacts as c} - <a href={`/send/${c.username}`}> - <div class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl"> - <div class="flex"> - <div> - <div class="flex"> - <Avatar user={c} size={20} disabled={true} /> - <div class="my-auto text-left"> - <p class="ml-1 text-lg break-words">{c.username}</p> - </div> - </div> - </div> - </div> - </div> - </a> - {/each} - </div> - </div> - {/if} + {#if contacts.length} + <div class="space-y-5"> + <h1 class="px-3 md:px-0 text-xl font-semibold mt-10"> + {$t("user.send.contacts")} + </h1> + <div> + {#each contacts as c} + <a href={`/send/${c.username}`}> + <div + class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl" + > + <div class="flex"> + <div> + <div class="flex"> + <Avatar user={c} size={20} disabled={true} /> + <div class="my-auto text-left"> + <p class="ml-1 text-lg break-words">{c.username}</p> + </div> + </div> + </div> + </div> + </div> + </a> + {/each} + </div> + </div> + {/if} </div> diff --git a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js index 92a754ae..5cb86aec 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js @@ -1,33 +1,32 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { auth, post, fd } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { auth, post, fd } from "$lib/utils"; let seen = {}; export const actions = { - default: async ({ cookies, request }) => { - try { - let { address, amount, confirmed, feeRate, subtract, pin, stale, ts } = await await fd( - request - ); - if (seen[ts]) return fail(400, { message: 'Duplicate form submission' }); + default: async ({ cookies, request }) => { + try { + let { address, amount, confirmed, feeRate, subtract, pin, stale, ts } = + await await fd(request); + if (seen[ts]) return fail(400, { message: "Duplicate form submission" }); - let estimate = await post( - '/bitcoin/fee', - { address, amount, feeRate, subtract }, - auth(cookies) - ); + let estimate = await post( + "/bitcoin/fee", + { address, amount, feeRate, subtract }, + auth(cookies) + ); - let { tx } = estimate; + let { tx } = estimate; - if (stale || !confirmed) return fail(400, estimate); + if (stale || !confirmed) return fail(400, estimate); - seen[ts] = 1; - setTimeout(() => delete seen[ts], 30000); + seen[ts] = 1; + setTimeout(() => delete seen[ts], 30000); - await post('/bitcoin/send', { pin, subtract, tx }, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + await post("/bitcoin/send", { pin, subtract, tx }, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte index 342eeebe..da11a22c 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte @@ -1,142 +1,159 @@ <script> - import { tick } from 'svelte'; - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - 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 } from '$lib/store'; + import { tick } from "svelte"; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + 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 } from "$lib/store"; - export let data; - export let form; + 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; + 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]; + $: rate = rates[currency]; - let toggle = () => (loading = true); - let toggleAdjusting = () => (adjusting = !adjusting); + let toggle = () => (loading = true); + let toggleAdjusting = () => (adjusting = !adjusting); - $: update(form); - let update = () => { - loading = false; - if (form?.message?.includes('pin')) $pin = undefined; - if (form) ({ min, max, feeRate, fee, ourfee, subtract } = form); - if (!ourfee) ourfee = 0; - if (feeRate) confirmed = true; - stale = false; - }; + $: update(form); + let update = () => { + loading = false; + if (form?.message?.includes("pin")) $pin = undefined; + if (form) ({ min, max, feeRate, fee, ourfee, subtract } = form); + if (!ourfee) ourfee = 0; + if (feeRate) confirmed = true; + stale = false; + }; - let setMax = () => { - fiat = false; - amount = balance; - subtract = true; - }; + let setMax = () => { + fiat = false; + amount = balance; + subtract = true; + }; - $: if (amount + fee > balance) subtract = true; + $: if (amount + fee > balance) subtract = true; - let timeout; - let handle = (e) => { - stale = true; - feeRate = e.target.value; - clearTimeout(timeout); - timeout = setTimeout(() => { - tick().then(() => submit.click()); - }, 200); - }; + let timeout; + let handle = (e) => { + stale = true; + feeRate = e.target.value; + clearTimeout(timeout); + timeout = setTimeout(() => { + tick().then(() => submit.click()); + }, 200); + }; </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto space-y-5 text-center"> - <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t('payments.send')}</h1> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> - <div class="text-lg text-secondary break-all">{address}</div> + <div class="text-lg text-secondary break-all">{address}</div> - {#if confirmed} - <div class="text-center"> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(toFiat(amount, rate), currency)} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1">⚡️{s(amount)}</h3> - </div> + {#if confirmed} + <div class="text-center"> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(toFiat(amount, rate), currency)} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">⚡️{s(amount)}</h3> + </div> - <div class="text-center"> - <h2 class="text-secondary mb-5 text-lg">with fee</h2> + <div class="text-center"> + <h2 class="text-secondary mb-5 text-lg">with fee</h2> - <div class="relative"> - <h2 class="text-xl font-semibold"> - {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} - ><Icon icon="settings" style="opacity-30" /></button - > - </div> - </div> - {:else} - <Numpad bind:amount bind:fiat {currency} {submit} bind:rate /> - {/if} + <div class="relative"> + <h2 class="text-xl font-semibold"> + {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} + ><Icon icon="settings" style="opacity-30" /></button + > + </div> + </div> + {:else} + <Numpad bind:amount bind:fiat {currency} {submit} bind:rate /> + {/if} - <form method="POST" use:enhance on:submit={toggle}> - <input name="ts" value={Date.now()} type="hidden" /> - <input name="address" value={address} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - <input name="confirmed" value={confirmed} type="hidden" /> - <input name="feeRate" value={feeRate} type="hidden" /> - <input name="stale" value={stale} type="hidden" /> - <input name="subtract" value={subtract} type="hidden" /> + <form method="POST" use:enhance on:submit={toggle}> + <input name="ts" value={Date.now()} type="hidden" /> + <input name="address" value={address} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <input name="confirmed" value={confirmed} type="hidden" /> + <input name="feeRate" value={feeRate} type="hidden" /> + <input name="stale" value={stale} type="hidden" /> + <input name="subtract" value={subtract} type="hidden" /> - {#if confirmed && adjusting} - <div class="relative mt-10 mb-20"> - <div class="absolute top-5 left-0 text-secondary">Slower (~40 blocks)</div> - <div class="absolute top-5 right-0 text-secondary">Faster (~1 block)</div> - <Slider bind:value={feeRate} {handle} {min} {max} /> - </div> - <div class="flex justify-center gap-2 mb-10"> - <div class="my-auto"> - <Toggle id="subtract" checked={subtract} bind:value={subtract} /> - </div> - <label for="subtract" class="text-secondary text-lg my-auto mb-2" - >Take fee from amount</label - > - </div> - {/if} + {#if confirmed && adjusting} + <div class="relative mt-10 mb-20"> + <div class="absolute top-5 left-0 text-secondary"> + Slower (~40 blocks) + </div> + <div class="absolute top-5 right-0 text-secondary"> + Faster (~1 block) + </div> + <Slider bind:value={feeRate} {handle} {min} {max} /> + </div> + <div class="flex justify-center gap-2 mb-10"> + <div class="my-auto"> + <Toggle id="subtract" checked={subtract} bind:value={subtract} /> + </div> + <label for="subtract" class="text-secondary text-lg my-auto mb-2" + >Take fee from amount</label + > + </div> + {/if} - <div class="flex justify-center gap-2"> - {#if !confirmed} - <button - type="button" - class="hover:opacity-80 bg-black text-white rounded-2xl py-3 px-4 mt-2 border font-bold" - on:click|preventDefault={setMax} - on:keydown={setMax}>Max ⚡️{s(balance)}</button - > - {/if} + <div class="flex justify-center gap-2"> + {#if !confirmed} + <button + type="button" + class="hover:opacity-80 bg-black text-white rounded-2xl py-3 px-4 mt-2 border font-bold" + on:click|preventDefault={setMax} + on:keydown={setMax}>Max ⚡️{s(balance)}</button + > + {/if} - <button - bind:this={submit} - type="submit" - class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-24" - disabled={loading} - > - {#if confirmed} - {$t('payments.send')} - {:else} - {$t('payments.next')} - {/if} - </button> - </div> - </form> + <button + bind:this={submit} + type="submit" + class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-24" + disabled={loading} + > + {#if confirmed} + {$t("payments.send")} + {:else} + {$t("payments.next")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js b/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js index 2cb3c4a9..3318a60b 100644 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js @@ -1,17 +1,17 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { auth, fd, post } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { auth, fd, post } from "$lib/utils"; export const actions = { - default: async ({ cookies, request }) => { - let body = await fd(request); - let p; + default: async ({ cookies, request }) => { + let body = await fd(request); + let p; - try { - p = await post('/payments', body, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + try { + p = await post("/payments", body, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - if (p) throw redirect(307, `/fund/${body.name}`); - } + if (p) throw redirect(307, `/fund/${body.name}`); + }, }; diff --git a/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte b/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte index 6aecf8f3..886e93d2 100644 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte @@ -1,58 +1,58 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { back } from '$lib/utils'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { back } from "$lib/utils"; + import { pin } from "$lib/store"; - export let data; - export let form; + export let data; + export let form; - let { amount, name } = $page.params; - let { rates } = data; - let { currency } = data.user; - let loading; + let { amount, name } = $page.params; + let { rates } = data; + let { currency } = data.user; + let loading; - $: rate = rates[currency]; + $: rate = rates[currency]; - let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message?.includes('pin')) $pin = undefined; - loading = false; - }; + let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message?.includes("pin")) $pin = undefined; + loading = false; + }; </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount {currency} bind:rate /> + <Numpad bind:amount {currency} bind:rate /> - <form method="POST" use:enhance on:submit={submit}> - <input name="name" value={name} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="name" value={name} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> </div> diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.js b/src/routes/(app)/send/invoice/[id]/+page.server.js index 4cbbb03f..aa284328 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.js +++ b/src/routes/(app)/send/invoice/[id]/+page.server.js @@ -1,37 +1,48 @@ -import { error, fail, redirect } from '@sveltejs/kit'; -import { fd, auth, get, post } from '$lib/utils'; +import { error, fail, redirect } from "@sveltejs/kit"; +import { fd, auth, get, post } from "$lib/utils"; export async function load({ params: { id }, parent, url }) { - let { user } = await parent(); + let { user } = await parent(); - let { - amount, - address, - currency, - hash, - rate, - prompt, - tip, - text, - user: recipient - } = await get(`/invoice/${id}`); + let { + amount, + address, + currency, + hash, + rate, + prompt, + tip, + text, + user: recipient, + } = await get(`/invoice/${id}`); - 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, currency, hash, tip, rate, payreq: text, recipient }; + 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, + currency, + hash, + tip, + rate, + payreq: text, + recipient, + }; } export const actions = { - default: async ({ cookies, params: { id }, request }) => { - try { - let body = await fd(request); - body.hash = id; + default: async ({ cookies, params: { id }, request }) => { + try { + let body = await fd(request); + body.hash = id; - await post('/payments', body, auth(cookies)); - } catch (e) { - return fail(400, { message: e.message }); - } + await post("/payments", body, auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index 02ab9d41..d29f1c96 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -1,154 +1,168 @@ <script> - import { onDestroy, onMount } from 'svelte'; - import { t } from '$lib/translations'; - import { goto, invalidateAll } from '$app/navigation'; - import { pin } from '$lib/store'; - import { enhance } from '$app/forms'; - import { Avatar, Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { post, back, f, fiat as toFiat, s, sats } from '$lib/utils'; - export let data; - export let form; + import { onDestroy, onMount } from "svelte"; + import { t } from "$lib/translations"; + import { goto, invalidateAll } from "$app/navigation"; + import { pin } from "$lib/store"; + import { enhance } from "$app/forms"; + import { Avatar, Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { post, back, f, fiat as toFiat, s, sats } from "$lib/utils"; + export let data; + export let form; - let { address, hash, rate, payreq, recipient, tip, rates, user } = data; - let { currency } = user; + let { address, hash, rate, payreq, recipient, tip, rates, user } = data; + let { currency } = user; - let amount = form?.amount || data.amount; - let a, - af, - amountFiat = amount * (rate / sats), - fiat = !amount; + let amount = form?.amount || data.amount; + let a, + af, + amountFiat = amount * (rate / sats), + fiat = !amount; - let setAmount = () => { - amount = a; - amountFiat = af; - }; + let setAmount = () => { + amount = a; + amountFiat = af; + }; - let loading; - let submit = () => (loading = true); + let loading; + let submit = () => (loading = true); - let external = async () => { - let invoice = { type: 'lightning', amount, currency }; - let { id } = await post(`/${recipient.username}/invoice`, { - invoice, - user: { username: recipient.username } - }); + let external = async () => { + let invoice = { type: "lightning", amount, currency }; + let { id } = await post(`/${recipient.username}/invoice`, { + invoice, + user: { username: recipient.username }, + }); - goto(`/${recipient.username}/invoice/${id}?options=true`, { invalidateAll: true }); - }; + goto(`/${recipient.username}/invoice/${id}?options=true`, { + invalidateAll: true, + }); + }; - $: rate = data.rate * (rates[user.currency] / rates[data.currency]); + $: rate = data.rate * (rates[user.currency] / rates[data.currency]); - $: update(form); - let update = () => { - if (form?.message.includes('pin')) $pin = undefined; - loading = false; - }; + $: update(form); + let update = () => { + if (form?.message.includes("pin")) $pin = undefined; + loading = false; + }; - // onMount(async () => { - // if (browser && window.NDEFReader) { - // try { - // let ndef = new NDEFReader(); - // await ndef.scan(); - // - // ndef.addEventListener('readingerror', (e) => { - // console.log('nfc error', e); - // }); - // - // ndef.addEventListener('reading', ({ message, serialNumber }) => { - // console.log(message, serialNumber); - // }); - // } catch (e) { - // console.log('NFC error', e); - // } - // } - // }); + // onMount(async () => { + // if (browser && window.NDEFReader) { + // try { + // let ndef = new NDEFReader(); + // await ndef.scan(); + // + // ndef.addEventListener('readingerror', (e) => { + // console.log('nfc error', e); + // }); + // + // ndef.addEventListener('reading', ({ message, serialNumber }) => { + // console.log(message, serialNumber); + // }); + // } catch (e) { + // console.log('NFC error', e); + // } + // } + // }); </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - {#if amount} - <div class="text-center mb-8"> - <h1 class="text-xl md:text-2xl text-secondary mb-2"> - {$t('payments.send')} - </h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(toFiat(amount, rate), currency)} - {#if tip} - <span class="text-lg"> - + {f(toFiat(tip, rate), currency)} - </span> - {/if} - </h2> - <h3 class="text-secondary md:text-lg mb-6 mt-1"> - ⚡️{s(amount)} + {#if amount} + <div class="text-center mb-8"> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.send")} + </h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(toFiat(amount, rate), currency)} + {#if tip} + <span class="text-lg"> + + {f(toFiat(tip, rate), currency)} + </span> + {/if} + </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> + ⚡️{s(amount)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> - <h1 class="text-xl md:text-2xl text-secondary mb-2">{$t('payments.to')}</h1> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.to")} + </h1> - <div class="flex p-1 gap-2 justify-center"> - {#if recipient.username.includes('@classic')} - <img - src="/images/classic.png" - class="w-24 border-4 border-transparent mr-1" - alt="Bitcoin" - /> - {:else} - <Avatar user={recipient} size={'20'} /> - {/if} - <p class="text-4xl break-words my-auto">{recipient.username.replace('@classic', '')}</p> - </div> - </div> - {:else} - <Numpad bind:amount={a} bind:amountFiat={af} {currency} bind:fiat bind:rate /> - {/if} + <div class="flex p-1 gap-2 justify-center"> + {#if recipient.username.includes("@classic")} + <img + src="/images/classic.png" + class="w-24 border-4 border-transparent mr-1" + alt="Bitcoin" + /> + {:else} + <Avatar user={recipient} size={"20"} /> + {/if} + <p class="text-4xl break-words my-auto"> + {recipient.username.replace("@classic", "")} + </p> + </div> + </div> + {:else} + <Numpad + bind:amount={a} + bind:amountFiat={af} + {currency} + bind:fiat + bind:rate + /> + {/if} - <form method="POST" use:enhance on:submit={submit}> - <input name="address" value={address} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="payreq" value={payreq} type="hidden" /> - <input name="username" value={recipient.username} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="address" value={address} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="payreq" value={payreq} type="hidden" /> + <input name="username" value={recipient.username} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> - {#if amount} - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - {:else} - <button - type="button" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - on:click={setAmount} - > - {$t('payments.next')} - </button> - {/if} - </div> - </form> - <div class="flex my-20"> - <button class="mx-auto" on:click={external}>{$t('payments.moreOptions')}</button> - </div> + <div class="flex w-full"> + {#if amount} + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + {:else} + <button + type="button" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + on:click={setAmount} + > + {$t("payments.next")} + </button> + {/if} + </div> + </form> + <div class="flex my-20"> + <button class="mx-auto" on:click={external} + >{$t("payments.moreOptions")}</button + > + </div> </div> diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.server.js b/src/routes/(app)/send/lightning/[payreq]/+page.server.js index d358bbca..d9e791e1 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.server.js +++ b/src/routes/(app)/send/lightning/[payreq]/+page.server.js @@ -1,24 +1,24 @@ -import { fail, redirect } from '@sveltejs/kit'; -import { fd, auth, post } from '$lib/utils'; +import { fail, redirect } from "@sveltejs/kit"; +import { fd, auth, post } from "$lib/utils"; export async function load({ params }) { - return post('/parse', params); + return post("/parse", params); } export const actions = { - setAmount: async ({ cookies, request }) => fd(request), + setAmount: async ({ cookies, request }) => fd(request), - send: async ({ cookies, request }) => { - try { - let body = await fd(request); + send: async ({ cookies, request }) => { + try { + let body = await fd(request); - await post('/payments', body, auth(cookies)); - } catch (e) { - if (e.message.includes('unusable')) - e.message = 'Failed to route payment, try sending a lower amount'; - return fail(400, { message: e.message }); - } + await post("/payments", body, auth(cookies)); + } catch (e) { + if (e.message.includes("unusable")) + e.message = "Failed to route payment, try sending a lower amount"; + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte index e232ef05..185597c7 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte +++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte @@ -1,105 +1,120 @@ <script> - import { t } from '$lib/translations'; - import { enhance } from '$app/forms'; - import { Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { back, s } from '$lib/utils'; - import { pin } from '$lib/store'; + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import { Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { back, s } from "$lib/utils"; + import { pin } from "$lib/store"; - export let data; - export let form; + export let data; + export let form; - let { payreq } = $page.params; - let { alias, rates } = data; + let { payreq } = $page.params; + let { alias, rates } = data; - let { currency } = data.user; - let a; - let maxfee = 500; + let { currency } = data.user; + let a; + let maxfee = 500; - $: amount = form?.amount || data.amount; - $: rate = rates[currency]; + $: amount = form?.amount || data.amount; + $: rate = rates[currency]; - let loading; - let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message?.includes('pin')) $pin = undefined; - loading = false; - }; + let loading; + let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message?.includes("pin")) $pin = undefined; + loading = false; + }; - let show; - let toggle = () => (show = !show); + let show; + let toggle = () => (show = !show); </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" /> + <Icon icon="arrow-left" style="w-10" /> </button> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - {#if amount} - <div class="text-center mb-8"> - <h1 class="text-xl md:text-2xl text-secondary mb-2">{$t('payments.send')}</h1> - <p class="text-6xl break-words mb-4"> - ⚡️{s(amount)} - </p> - <h1 class="text-xl md:text-2xl text-secondary mb-2">{$t('payments.to')}</h1> - <p class="text-4xl break-words">{alias}</p> - </div> + {#if amount} + <div class="text-center mb-8"> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.send")} + </h1> + <p class="text-6xl break-words mb-4"> + ⚡️{s(amount)} + </p> + <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {$t("payments.to")} + </h1> + <p class="text-4xl break-words">{alias}</p> + </div> - <form method="POST" use:enhance on:submit={submit} action="?/send" class="space-y-5"> - <input name="payreq" value={payreq} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - <input name="maxfee" value={maxfee} type="hidden" /> + <form + method="POST" + use:enhance + on:submit={submit} + action="?/send" + class="space-y-5" + > + <input name="payreq" value={payreq} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <input name="maxfee" value={maxfee} type="hidden" /> - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - disabled={loading} - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - </div> - </form> + <div class="flex w-full"> + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> - {#if show} - <div class="relative w-96 mx-auto"> - <label for="maxfee">Max fee</label> - <input name="maxfee" bind:value={maxfee} /> + {#if show} + <div class="relative w-96 mx-auto"> + <label for="maxfee">Max fee</label> + <input name="maxfee" bind:value={maxfee} /> - <div - class="absolute right-[2px] top-[25px] text-gray-600 rounded-r-2xl p-4 h-[54px] my-auto border-l " - > - ⚡️ - </div> - </div> - {:else} - <div class="flex w-full mt-5"> - <button class="mx-auto" on:click={toggle}>Settings</button> - </div> - {/if} - {: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} {rate} /> - <button - type="submit" - class="bg-black text-white rounded-xl h-[48px] flex w-full justify-center items-center font-semibold + <div + class="absolute right-[2px] top-[25px] text-gray-600 rounded-r-2xl p-4 h-[54px] my-auto border-l" + > + ⚡️ + </div> + </div> + {:else} + <div class="flex w-full mt-5"> + <button class="mx-auto" on:click={toggle}>Settings</button> + </div> + {/if} + {: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} {rate} /> + <button + type="submit" + class="bg-black text-white rounded-xl h-[48px] flex w-full justify-center items-center font-semibold opacity-100 hover:opacity-80" - > - Next - </button> - </form> - {/if} + > + Next + </button> + </form> + {/if} </div> 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 a01cdcfc..1f3a0420 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js @@ -1,46 +1,47 @@ -import { error, fail, redirect } from '@sveltejs/kit'; -import { types, sats, fd, auth, get, post } from '$lib/utils'; +import { error, fail, redirect } from "@sveltejs/kit"; +import { types, sats, fd, auth, get, post } from "$lib/utils"; export async function load({ cookies, params, parent }) { - let subject = await get(`/users/${params.username}`); + let subject = await get(`/users/${params.username}`); - let { rates, user } = await parent(); - let { username } = params; - let [amount, currency] = params.amount.split('/'); + 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' }); + if (subject.username === user?.username) + throw error(500, { message: "Cannot send to self" }); - 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 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) - ); + let { hash } = await post( + "/invoice", + { + invoice: { amount, prompt: false, type: types.lightning }, + user: { username }, + }, + auth(cookies) + ); - throw redirect(307, `/${username}/invoice/${hash}`); - } + throw redirect(307, `/${username}/invoice/${hash}`); + } - return { amount, rate, subject }; + return { amount, rate, subject }; } export const actions = { - default: async ({ cookies, request }) => { - try { - let body = await fd(request); - await post('/payments', body, auth(cookies)); - } catch (e) { - console.log(e); - return fail(400, { message: e.message }); - } + default: async ({ cookies, request }) => { + try { + let body = await fd(request); + await post("/payments", body, auth(cookies)); + } catch (e) { + console.log(e); + return fail(400, { message: e.message }); + } - throw redirect(307, '/sent'); - } + throw redirect(307, "/sent"); + }, }; diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte index 32a13939..cb458323 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte @@ -1,103 +1,103 @@ <script> - import { t } from '$lib/translations'; - import { goto } from '$app/navigation'; - import { pin } from '$lib/store'; - import { enhance } from '$app/forms'; - import { AppHeader, Avatar, Icon, Numpad, Spinner } from '$comp'; - import { page } from '$app/stores'; - import { f, post, s, sat, sats } from '$lib/utils'; + import { t } from "$lib/translations"; + import { goto } from "$app/navigation"; + import { pin } from "$lib/store"; + import { enhance } from "$app/forms"; + import { AppHeader, Avatar, Icon, Numpad, Spinner } from "$comp"; + import { page } from "$app/stores"; + import { f, post, s, sat, sats } from "$lib/utils"; - export let data; - export let form; + export let data; + export let form; - let { subject, rate, rates, user } = data; - let currency = user.currency || subject.currency || 'USD'; - let next; + let { subject, rate, rates, user } = data; + let currency = user.currency || subject.currency || "USD"; + let next; - let amount = form?.amount || data.amount; - let a, - af, - fiat = !amount, - hash, - amountFiat = 0; + let amount = form?.amount || data.amount; + let a, + af, + fiat = !amount, + hash, + amountFiat = 0; - $: r = rate || rates[user.currency || subject.currency]; + $: r = rate || rates[user.currency || subject.currency]; - let setAmount = async () => { - amount = a; - amountFiat = parseFloat(af).toFixed(2); - rate = fiat ? (sats * amountFiat) / amount : r; - ({ hash } = await post(`/${subject.username}/invoice`, { - invoice: { - amount, - hash: 'internal', - rate: rates[subject.currency], - prompt: subject.prompt, - type: 'internal' - }, - user: subject - })); + let setAmount = async () => { + amount = a; + amountFiat = parseFloat(af).toFixed(2); + rate = fiat ? (sats * amountFiat) / amount : r; + ({ hash } = await post(`/${subject.username}/invoice`, { + invoice: { + amount, + hash: "internal", + rate: rates[subject.currency], + prompt: subject.prompt, + type: "internal", + }, + user: subject, + })); - goto(`/${subject.username}/invoice/${hash}`); - }; + goto(`/${subject.username}/invoice/${hash}`); + }; - let loading; - let submit = () => (loading = true); + let loading; + let submit = () => (loading = true); - $: update(form); - let update = () => { - if (form?.message?.includes('pin')) $pin = undefined; - loading = false; - }; + $: update(form); + let update = () => { + if (form?.message?.includes("pin")) $pin = undefined; + loading = false; + }; </script> <AppHeader {data} /> {#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> + <div class="text-red-600 text-center"> + {form.message} + </div> {/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 - bind:rate={r} - bind:submit={next} - /> + <Numpad + bind:amount={a} + bind:amountFiat={af} + {currency} + bind:fiat + bind:rate={r} + bind:submit={next} + /> - <form method="POST" use:enhance on:submit={submit}> - <input name="amount" value={amount} type="hidden" /> - <input name="username" value={subject.username} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - <input name="hash" value={hash} type="hidden" /> + <form method="POST" use:enhance on:submit={submit}> + <input name="amount" value={amount} type="hidden" /> + <input name="username" value={subject.username} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <input name="hash" value={hash} type="hidden" /> - <div class="flex w-full"> - {#if amount} - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t('payments.send')} - {/if} - </button> - {:else} - <button - type="button" - bind:this={next} - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - on:click={setAmount} - on:keydown={setAmount} - > - Next - </button> - {/if} - </div> - </form> + <div class="flex w-full"> + {#if amount} + <button + type="submit" + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + > + {#if loading} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + {:else} + <button + type="button" + bind:this={next} + class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + on:click={setAmount} + on:keydown={setAmount} + > + Next + </button> + {/if} + </div> + </form> </div> diff --git a/src/routes/(app)/sent/+page.server.js b/src/routes/(app)/sent/+page.server.js index 381ff32f..62a4d9b4 100644 --- a/src/routes/(app)/sent/+page.server.js +++ b/src/routes/(app)/sent/+page.server.js @@ -1,6 +1,6 @@ -import { get, auth } from '$lib/utils'; +import { get, auth } from "$lib/utils"; export let load = async ({ cookies }) => { - let { payments } = await get('/payments', auth(cookies)); - return { payments }; + let { payments } = await get("/payments", auth(cookies)); + return { payments }; }; diff --git a/src/routes/(app)/sent/+page.svelte b/src/routes/(app)/sent/+page.svelte index dbd18c2e..400e8904 100644 --- a/src/routes/(app)/sent/+page.svelte +++ b/src/routes/(app)/sent/+page.svelte @@ -1,47 +1,52 @@ <script> - import { fiat, f, s, sat, sats } from '$lib/utils'; - import { scale } from 'svelte/transition'; - import { Icon } from '$comp'; - import { toast } from '@zerodevx/svelte-toast'; - import { t } from '$lib/translations'; + import { fiat, f, s, sat, sats } from "$lib/utils"; + import { scale } from "svelte/transition"; + import { Icon } from "$comp"; + import { toast } from "@zerodevx/svelte-toast"; + import { t } from "$lib/translations"; - export let data; - let { payments, user } = data; - let { currency, username } = user; + export let data; + let { payments, user } = data; + let { currency, username } = user; - let payment = payments[0]; - let { amount, rate, tip } = payment; - amount = Math.abs(amount); + let payment = payments[0]; + let { amount, rate, tip } = payment; + amount = Math.abs(amount); - toast.pop(0); + toast.pop(0); </script> <div class="text-center mt-20 md:mt-0"> - <div class="flex w-full max-w-[200px] mx-auto py-20" in:scale={{ start: 0.5 }}> - <Icon icon="check" style="mx-auto" /> - </div> - <h1 class="text-3xl md:text-4xl font-bold mb-6">{$t('payments.sent')}!</h1> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(fiat(amount, 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(amount)} + <div + class="flex w-full max-w-[200px] mx-auto py-20" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6">{$t("payments.sent")}!</h1> + <h2 class="text-2xl md:text-3xl font-semibold"> + {f(fiat(amount, 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(amount)} - {#if tip} - <span class="text-lg"> - + ⚡️{s(tip)} - </span> - {/if} - </h3> + {#if tip} + <span class="text-lg"> + + ⚡️{s(tip)} + </span> + {/if} + </h3> </div> <a href={`/${username}/payments`}> - <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> + <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> +<div class="fixed bottom-10 w-full text-center"> + {$t("payments.tapAnywhere")} +</div> diff --git a/src/routes/(app)/support/+page.svelte b/src/routes/(app)/support/+page.svelte index b2f82e89..b31b4389 100644 --- a/src/routes/(app)/support/+page.svelte +++ b/src/routes/(app)/support/+page.svelte @@ -1,122 +1,138 @@ <script> - import { onDestroy } from 'svelte'; - import { fail, success, post } from '$lib/utils'; - import { scale } from 'svelte/transition'; - import { AppHeader, Icon } from '$comp'; - import { t } from '$lib/translations'; + import { onDestroy } from "svelte"; + import { fail, success, post } from "$lib/utils"; + import { scale } from "svelte/transition"; + import { AppHeader, Icon } from "$comp"; + import { t } from "$lib/translations"; - export let data; + export let data; - let { user } = data; - $: if (user) { - data.subject = { ...user }; - } + let { user } = data; + $: if (user) { + data.subject = { ...user }; + } - let email; - let message; - let sent; - let recaptchaSiteKey = '6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM'; + let email; + let message; + let sent; + let recaptchaSiteKey = "6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM"; - let submit = (e) => { - e.preventDefault(); - grecaptcha.ready(() => { - grecaptcha.execute(recaptchaSiteKey, { action: 'submit' }).then((token) => - post('/support', { username: user?.username, email, message, token }) - .then(() => (sent = true)) - .catch(() => fail('problem submitting')) - ); - }); - }; + let submit = (e) => { + e.preventDefault(); + grecaptcha.ready(() => { + 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); - } + 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(); - } - }); + 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> - <script - src="https://www.google.com/recaptcha/api.js?render=6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM" - ></script> + <script + src="https://www.google.com/recaptcha/api.js?render=6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM" + ></script> </svelte:head> {#if user} - <AppHeader {data} /> + <AppHeader {data} /> {/if} <div class="container px-4 max-w-lg mx-auto mt-20 space-y-8"> - {#if sent} - <h1 class="text-center text-3xl md:text-4xl font-semibold">Thank you!</h1> - <div class="text-center text-lg text-secondary">Someone will be in touch shortly.</div> + {#if sent} + <h1 class="text-center text-3xl md:text-4xl font-semibold">Thank you!</h1> + <div class="text-center text-lg text-secondary"> + Someone will be in touch shortly. + </div> - <div> - <a href={user ? `/${user.username}` : '/'}> - <button - class="rounded-full border py-2 px-5 font-bold hover:opacity-80 w-full bg-black text-white" - >Ok</button - > - </a> - </div> - {:else} - <h1 class="text-center text-3xl md:text-4xl font-semibold mb-8"> - {$t('user.support.header')} - </h1> + <div> + <a href={user ? `/${user.username}` : "/"}> + <button + class="rounded-full border py-2 px-5 font-bold hover:opacity-80 w-full bg-black text-white" + >Ok</button + > + </a> + </div> + {:else} + <h1 class="text-center text-3xl md:text-4xl font-semibold mb-8"> + {$t("user.support.header")} + </h1> - <p class="text-secondary text-lg"> - Fill out this form or email us directly at <a - class="underline" - href="mailto:[email protected]">[email protected]</a - > and we'll do our best to get back to you in a timely manner. - </p> + <p class="text-secondary text-lg"> + Fill out this form or email us directly at <a + class="underline" + href="mailto:[email protected]">[email protected]</a + > and we'll do our best to get back to you in a timely manner. + </p> - <form on:submit={submit}> - <div class="mb-4"> - <label for="account" class="font-semibold">{$t('user.support.accountName')}</label> - <input - class="bg-primary" - type="text" - name="account" - required - value={user?.username || ''} - /> - </div> + <form on:submit={submit}> + <div class="mb-4"> + <label for="account" class="font-semibold" + >{$t("user.support.accountName")}</label + > + <input + class="bg-primary" + type="text" + name="account" + required + value={user?.username || ""} + /> + </div> - <div class="mb-4"> - <label for="email" class="font-semibold">{$t('user.support.email')}</label> - <input class="bg-primary" type="email" name="email" bind:value={email} required /> - </div> + <div class="mb-4"> + <label for="email" class="font-semibold" + >{$t("user.support.email")}</label + > + <input + class="bg-primary" + type="email" + name="email" + bind:value={email} + required + /> + </div> - <div class="mb-4"> - <label for="message" class="font-semibold">{$t('user.support.message')}</label> - <textarea - rows={5} - class="bg-primary" - type="text" - name="message" - bind:value={message} - required - /> - </div> + <div class="mb-4"> + <label for="message" class="font-semibold" + >{$t("user.support.message")}</label + > + <textarea + rows={5} + class="bg-primary" + type="text" + name="message" + bind:value={message} + required + /> + </div> - <button - type="submit" - disabled={!email || !message} - class="g-recaptcha {!email || !message - ? 'opacity-50' - : 'opacity-100'} bg-black text-white font-bold rounded-xl py-3 w-full mx-auto {email && - message - ? 'hover:opacity-80' - : ''}">{$t('user.support.send')}</button - > - </form> - {/if} + <button + type="submit" + disabled={!email || !message} + class="g-recaptcha {!email || !message + ? 'opacity-50' + : 'opacity-100'} bg-black text-white font-bold rounded-xl py-3 w-full mx-auto {email && + message + ? 'hover:opacity-80' + : ''}">{$t("user.support.send")}</button + > + </form> + {/if} </div> diff --git a/src/routes/(app)/support/+server.js b/src/routes/(app)/support/+server.js index 6b1778a8..e9a16cfc 100644 --- a/src/routes/(app)/support/+server.js +++ b/src/routes/(app)/support/+server.js @@ -1,9 +1,9 @@ -import { json } from '@sveltejs/kit'; -import { auth, post } from '$lib/utils'; +import { json } from "@sveltejs/kit"; +import { auth, post } from "$lib/utils"; export const prerender = false; export async function POST({ cookies, request }) { - let result = await post('/email', await request.json(), auth(cookies)); - return json(result); + let result = await post("/email", await request.json(), auth(cookies)); + return json(result); } diff --git a/src/routes/(app)/withdraw/+server.js b/src/routes/(app)/withdraw/+server.js index 7b1ff4c6..a75a72f8 100644 --- a/src/routes/(app)/withdraw/+server.js +++ b/src/routes/(app)/withdraw/+server.js @@ -1,9 +1,10 @@ -import { auth, post } from '$lib/utils'; +import { auth, post } from "$lib/utils"; export async function POST({ cookies, request }) { - let body = await request.json(); - await post('/payments', body, auth(cookies)); + let body = await request.json(); + await post("/payments", body, auth(cookies)); - return new Response(JSON.stringify('ok'), { headers: { 'content-type': 'application/json' } }); + return new Response(JSON.stringify("ok"), { + headers: { "content-type": "application/json" }, + }); } - |
