diff options
| author | Adam Soltys <[email protected]> | 2024-01-02 20:21:20 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-01-02 20:21:20 -0800 |
| commit | a20ade414740804385ba05e6028f53b670e0236d (patch) | |
| tree | 493ac4e50319121c1e35a67243a6b11560b17910 /src | |
| parent | ea8944bd05f3cdb83eab52b8abdb2351ce68bda1 (diff) | |
| parent | ae1c0add85daeae8bc44c34f9559bcd50c56d873 (diff) | |
| download | coinos-ui-a20ade414740804385ba05e6028f53b670e0236d.tar.gz coinos-ui-a20ade414740804385ba05e6028f53b670e0236d.zip | |
Merge branch 'master' into swapee
Diffstat (limited to 'src')
71 files changed, 1249 insertions, 502 deletions
diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index d7045d5f..53dd8ebf 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -147,11 +147,8 @@ {/if} <style> - .z { - z-index: 100; - } - header { + z-index: 50; view-transition-name: header; } </style> diff --git a/src/components/Lnurlp.svelte b/src/components/Lnurlp.svelte index 6db7e7d7..6785061a 100644 --- a/src/components/Lnurlp.svelte +++ b/src/components/Lnurlp.svelte @@ -5,7 +5,7 @@ import Spinner from "$comp/Spinner.svelte"; import { pin } from "$lib/store"; - export let data, form; + export let data, form, send; let { currency } = data.user; let { minSendable, maxSendable, callback, metadata, rates } = data; @@ -36,7 +36,7 @@ </div> {/if} - <Numpad bind:amount bind:rate {currency} /> + <Numpad bind:amount bind:rate {currency} bind:submit={send} /> <form action="?/pay" method="POST" use:enhance on:submit={submit}> <input name="amount" value={amount} type="hidden" /> @@ -47,6 +47,7 @@ <div class="flex w-full"> <button + bind:this={send} 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" > diff --git a/src/components/Numpad.svelte b/src/components/Numpad.svelte index 13f42379..901d11ba 100644 --- a/src/components/Numpad.svelte +++ b/src/components/Numpad.svelte @@ -1,7 +1,7 @@ <script> import Left from "$comp/Left.svelte"; import Icon from "$comp/Icon.svelte"; - import { f, s, post, warning, sat, sats } from "$lib/utils"; + import { f, s, focus, post, warning, sat, sats } from "$lib/utils"; import { t } from "$lib/translations"; export let amount, @@ -93,9 +93,7 @@ } else if (value !== "<" && parseInt(amount + value) > sats) { warning($t("user.receive.lessThan1BTCWarning")); } else { - console.log("CLICK", amount.toString(), value); amount = parseInt(amount.toString() + value); - console.log("AMOUNT", amount); } } }; @@ -163,7 +161,7 @@ }, 0); }; - let focus = (e) => { + let select = (e) => { let range = document.createRange(); range.selectNodeContents(e.target); let sel = getSelection(); @@ -171,6 +169,10 @@ sel.addRange(range); }; + let blur = () => { + if (!html) html = "0"; + }; + let keydown = (e) => e.key === "Enter" && (e.preventDefault() || submit.click()); </script> @@ -185,9 +187,11 @@ {fiat ? symbol : "⚡️"} </div> <div + use:focus contenteditable bind:innerHTML={html} - on:focus={focus} + on:focus={select} + on:blur={blur} on:input={input} on:keydown={keydown} class="outline-none my-auto" diff --git a/src/components/Popup.svelte b/src/components/Popup.svelte index d59cfcfc..df96215f 100644 --- a/src/components/Popup.svelte +++ b/src/components/Popup.svelte @@ -15,7 +15,7 @@ </script> {#if tags} - {#if tags.user} + {#if tags.user && tags.user.username} <a href={`/${tags.user.username}`}> <div class="space-y-5"> <div> diff --git a/src/components/Qr.svelte b/src/components/Qr.svelte index 8a716b14..e98497c4 100644 --- a/src/components/Qr.svelte +++ b/src/components/Qr.svelte @@ -5,7 +5,7 @@ </script> <div - class="w-80 mx-auto my-5 cursor-pointer" + class="w-80 mx-auto cursor-pointer" bind:this={qr} on:click={() => screenfull.toggle(qr)} on:keydown={() => screenfull.toggle(qr)} diff --git a/src/components/Toggle.svelte b/src/components/Toggle.svelte index 1e4cf4c4..2f92837d 100644 --- a/src/components/Toggle.svelte +++ b/src/components/Toggle.svelte @@ -12,6 +12,6 @@ class="sr-only peer" /> <div - class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-gray-300 dark:peer-focus:ring-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-gray-600" + class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-gray-300 dark:peer-focus:ring-gray-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-500" /> </label> diff --git a/src/components/index.js b/src/components/index.js deleted file mode 100644 index e69de29b..00000000 --- a/src/components/index.js +++ /dev/null diff --git a/src/lib/invoice.js b/src/lib/invoice.js index 87707c89..06a9a94e 100644 --- a/src/lib/invoice.js +++ b/src/lib/invoice.js @@ -25,13 +25,12 @@ export default async ({ cookies, request, url }) => { if (request_id) { if (url.pathname.endsWith("tip")) { - throw redirect(307, `/send/invoice/${id}`); + redirect(307, `/send/invoice/${id}`); } - throw redirect(307, `/${user.username}/request/${request_id}`); + redirect(307, `/${user.username}/request/${request_id}`); } - if (invoice.prompt) - throw redirect(307, `/${user.username}/invoice/${id}/tip`); - else throw redirect(307, `/${user.username}/invoice/${id}`); + if (invoice.prompt) redirect(307, `/${user.username}/invoice/${id}/tip`); + else redirect(307, `/${user.username}/invoice/${id}`); }; diff --git a/src/lib/samples.js b/src/lib/samples.js index c8a60c71..6624064b 100644 --- a/src/lib/samples.js +++ b/src/lib/samples.js @@ -1,3 +1,9 @@ +import { PUBLIC_DOMAIN, PUBLIC_COINOS_URL } from "$env/static/public"; + +let api = PUBLIC_DOMAIN.includes("localhost") + ? `${PUBLIC_COINOS_URL}` + : `https://${PUBLIC_DOMAIN}/api`; + export default { register: `curl "https://swapee.me/api/register" -H "content-type: application/json" -d '{ "user": { @@ -24,7 +30,7 @@ export default { -H "Authorization: Bearer $token" -d '{ "amount": 5000, - "hash": "bc1q3unh97w4rmelflrm2hvdwz37d8kray3vn4d5ca" + "address": "bc1q3unh97w4rmelflrm2hvdwz37d8kray3vn4d5ca" }'`, internal: `HASH=$(curl "https://swapee.me/api/invoice" -H "content-type: application/json" diff --git a/src/lib/utils.js b/src/lib/utils.js index c0df81ae..738413bf 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -101,8 +101,8 @@ export function reverseFormat(val, locale) { export let protectedRoutes = [/customers/, /settings/, /payments/]; -export let success = (m) => { - toast.pop(); +export let success = (m, clear = true) => { + if (clear) toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "#F5F7FA", @@ -110,16 +110,16 @@ export let success = (m) => { }); }; -export let warning = (m) => { - toast.pop(); +export let warning = (m, clear = true) => { + if (clear) toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "#FFCE22", }, }); }; -export let fail = (m) => { - toast.pop(); +export let fail = (m, clear = true) => { + if (clear) toast.pop(); toast.push(m, { theme: { "--toastBarBackground": "#E93535", @@ -186,6 +186,7 @@ export let f = (s, currency) => export let s = (s) => new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(s); + export let sat = (s) => { s = Math.abs(s); let p = Math.floor(Math.log(s) / Math.LN10 + 0.000000001); @@ -265,6 +266,7 @@ export function ss(str1, str2) { export let types = { bitcoin: "bitcoin", + liquid: "liquid", lightning: "lightning", internal: "internal", fund: "fund", @@ -280,3 +282,22 @@ export function shuffleArray(array) { [array[i], array[j]] = [array[j], array[i]]; } } + +export let closest = (a, n) => + a.reduce((prev, curr) => + Math.abs(curr - n) < Math.abs(prev - n) ? curr : prev + ); + +export let isLiquid = (text) => + text.startsWith("Az") || + text.startsWith("lq1") || + text.startsWith("VJL") || + text.startsWith("VT") || + text.startsWith("XR") || + text.startsWith("XC") || + ((text.startsWith("H") || text.startsWith("G") || text.startsWith("Q")) && + text.length === 34) || + (text.startsWith("ert1q") && text.length === 43) || + (text.startsWith("ex1q") && text.length === 42) || + text.startsWith("el1qq") || + text.startsWith("lq1qq"); diff --git a/src/locales/en.json b/src/locales/en.json index 2f3be6dc..9aaf3eca 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -272,6 +272,12 @@ } }, "payments": { + "networkFee": "Network fee", + "platformFee": "Platform fee", + "fastest": "Fastest", + "fast": "Fast", + "medium": "Medium", + "slow": "Slow", "create": "Create", "fund": "Fund", "addFunds": "Add Funds", @@ -292,6 +298,7 @@ "details": "Transaction Details", "header": "Payments", "preimage": "Proof of payment", + "maxfee": "Max routing fee", "type": "Type", "time": "Time", "sent": "Sent", diff --git a/src/routes/(app)/+layout.server.js b/src/routes/(app)/+layout.server.js index 9fcf6411..50d51584 100644 --- a/src/routes/(app)/+layout.server.js +++ b/src/routes/(app)/+layout.server.js @@ -16,7 +16,7 @@ export async function load({ cookies, request, url, params }) { } if (user?.needsMigration) { - throw redirect(307, `/migrate`); + redirect(307, `/migrate`); } if ( @@ -24,18 +24,19 @@ export async function load({ cookies, request, url, params }) { ["/login", "/register"].includes(pathname) && request.method === "GET" ) { - throw redirect(307, `/${user.username}`); + redirect(307, `/${user.username}`); } if ( protectedRoutes.find((p) => pathname.match(p)) && (!user || user.username !== params.username) ) { - throw redirect(307, "/login"); + redirect(307, "/login"); } - if (user && !user.pubkey && !pathname.includes("generate")) - throw redirect(307, `/${user.username}/generate`); + if (user && !user.pubkey && !pathname.includes("generate")) { + redirect(303, `/${user.username}/generate`); + } try { rate = await get("/rate"); diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 1c7a69f6..31c8e31c 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -8,18 +8,18 @@ request, ndef, passwordPrompt, + password, pin, } from "$lib/store"; import { page } from "$app/stores"; import { browser } from "$app/environment"; - import { goto } from "$app/navigation"; import LoadingSplash from "$comp/LoadingSplash.svelte"; import Invoice from "$comp/Invoice.svelte"; import Password from "$comp/Password.svelte"; import Request from "$comp/Request.svelte"; import { warning, protectedRoutes } from "$lib/utils"; - import { t, locale } from "$lib/translations"; - import { onNavigate } from "$app/navigation"; + import { t, locale, loading } from "$lib/translations"; + import { goto, onNavigate } from "$app/navigation"; export let data; @@ -34,7 +34,7 @@ }); }); - let { rate, user, subject, token, rates } = data; + let { generate, rate, user, subject, token, rates } = data; $: update(data); let update = (data) => { @@ -122,14 +122,16 @@ {#if subject?.profile} <meta name="og:image" content={`/api/public/${subject.profile}.webp`} /> {:else} - <meta property="og:image" content="/icons/logo.svg" /> + <meta property="og:image" content="/images/logo.webp" /> {/if} </svelte:head> <SvelteToast options={{ reversed: true, intro: { y: 192 } }} /> <main class="pb-20"> - <slot /> + {#if !$loading} + <slot /> + {/if} </main> {#if $invoice} diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte index 5b3fcfa5..ff7ef7dd 100644 --- a/src/routes/(app)/[username]/(profile)/+layout.svelte +++ b/src/routes/(app)/[username]/(profile)/+layout.svelte @@ -1,6 +1,6 @@ <script> import { hexToUint8Array } from "uint8array-extras"; - import { copy, f, s, sats } from "$lib/utils"; + import { f, post, s, sats, success, fail } from "$lib/utils"; import Icon from "$comp/Icon.svelte"; import { t } from "$lib/translations"; import { sign, send } from "$lib/nostr"; @@ -16,11 +16,9 @@ $: ({ currency, username: n, display } = subject); $: username = n.length > 60 ? n.substr(0, 6) : display || n; - $: npub = bech32m.encode( - "npub", - toWords(hexToUint8Array(subject.pubkey)), - 180 - ); + $: npub = + subject.pubkey && + bech32m.encode("npub", toWords(hexToUint8Array(subject.pubkey)), 180); $: lnaddr = `${n}@${$page.url.host}`; $: profile = `${$page.url.host}/${n}`; @@ -64,6 +62,17 @@ let showDetails; let toggleDetails = () => (showDetails = !showDetails); + + let password; + + let reset = async () => { + try { + await post(`/${subject.username}/reset`, { password }); + success("Password reset"); + } catch (e) { + fail(e.message); + } + }; </script> <div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap"> @@ -72,9 +81,9 @@ <div class="flex text-3xl font-bold text-center mx-auto justify-center gap-2" > - <div class="my-auto">{display || username}</div> + <div class="my-auto break-words w-full">{display || username}</div> <button on:click={toggleDetails}> - <Icon icon="stats" style="w-12" /> + <Icon icon="stats" style="w-12 min-w-[50px]" /> </button> </div> @@ -214,6 +223,21 @@ </a> </div> {/if} + + {#if user?.admin && user.username !== subject.username} + <form class="w-full flex" on:submit|preventDefault={reset}> + <input placeholder="Password reset" bind:value={password} /> + <button + type="submit" + class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60" + > + <div class="mx-auto flex"> + <Icon icon="clock" style="mr-2 my-auto w-8" /> + <div class="mt-1 my-auto">Reset</div> + </div> + </button> + </form> + {/if} </div> </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 ec4da865..24f2b611 100644 --- a/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js @@ -4,9 +4,9 @@ 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"); + if (!event) error(500, "Event not found"); return { event }; } catch (e) { - throw error(500, e.message); + error(500, e.message); } } diff --git a/src/routes/(app)/[username]/(profile)/request/+page.server.js b/src/routes/(app)/[username]/(profile)/request/+page.server.js index 754c5805..f22dcbb7 100644 --- a/src/routes/(app)/[username]/(profile)/request/+page.server.js +++ b/src/routes/(app)/[username]/(profile)/request/+page.server.js @@ -5,7 +5,7 @@ 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}`); + redirect(307, `/${params.username}/request/${id}`); }, delete: async ({ cookies, request }) => { diff --git a/src/routes/(app)/[username]/+layout.server.js b/src/routes/(app)/[username]/+layout.server.js index 3b25b3b5..11e6d45c 100644 --- a/src/routes/(app)/[username]/+layout.server.js +++ b/src/routes/(app)/[username]/+layout.server.js @@ -13,6 +13,6 @@ export let load = async ({ cookies, depends, params: { username } }) => { return { subject }; } catch (e) { - throw error(500, "Unable to retrieve user account data"); + error(500, "Unable to retrieve user account data"); } }; diff --git a/src/routes/(app)/[username]/address/+page.server.js b/src/routes/(app)/[username]/address/+page.server.js index 1a782f9d..f8e271f9 100644 --- a/src/routes/(app)/[username]/address/+page.server.js +++ b/src/routes/(app)/[username]/address/+page.server.js @@ -6,5 +6,5 @@ export async function load({ cookies, params, parent }) { 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}`); + 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 20b657a1..945eee30 100644 --- a/src/routes/(app)/[username]/card/[serial]/+page.server.js +++ b/src/routes/(app)/[username]/card/[serial]/+page.server.js @@ -10,7 +10,7 @@ export let load = async ({ cookies, params }) => { console.log(e); } - if (fund) throw redirect(307, `/fund/${serial}`); + if (fund) redirect(307, `/fund/${serial}`); return params; }; diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/[username]/generate/+page.svelte index 71f828dc..b9b077a3 100644 --- a/src/routes/(app)/[username]/generate/+page.svelte +++ b/src/routes/(app)/[username]/generate/+page.svelte @@ -3,6 +3,7 @@ import { onMount } from "svelte"; import { goto, invalidate } from "$app/navigation"; import Pin from "$comp/Pin.svelte"; + import Spinner from "$comp/Spinner.svelte"; import { loginRedirect, pin, password, passwordPrompt } from "$lib/store"; import { generate } from "$lib/nostr"; import { browser } from "$app/environment"; @@ -19,6 +20,7 @@ return; } + console.log("PASSWORRRRD", $password); if (!$password) $passwordPrompt = true; try { @@ -58,6 +60,12 @@ {#if loaded && user?.haspin && $pin?.length !== 6} <Pin /> {/if} -{loaded} -{JSON.stringify(user)} -{$pin} + +<div class="container px-4 text-center mx-auto"> + <h1 class="text-3xl md:text-4xl font-bold mb-6"> + Generating new account keys + </h1> + <div class="flex w-full py-5 max-w-[200px] mx-auto bg-black rounded-2xl"> + <Spinner /> + </div> +</div> diff --git a/src/routes/(app)/[username]/generate/+server.js b/src/routes/(app)/[username]/generate/+server.js index 1dca1ece..3e3a9358 100644 --- a/src/routes/(app)/[username]/generate/+server.js +++ b/src/routes/(app)/[username]/generate/+server.js @@ -6,6 +6,6 @@ export async function POST({ cookies, request }) { let body = await request.json(); return json(await post(`/user`, body, auth(cookies))); } catch (e) { - throw error(500, e.message); + error(500, e.message); } } diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.server.js b/src/routes/(app)/[username]/invoice/[id]/+layout.server.js index b9d1c43d..6f9ae4a2 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+layout.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/+layout.server.js @@ -16,24 +16,23 @@ export async function load({ depends, params, url, parent }) { !url.pathname.includes("tip") && !options ) { - throw redirect(307, `/send/invoice/${id}`); + redirect(307, `/send/invoice/${id}`); } let { amount, pending, received } = invoice; amount = parseInt(amount); let paid = - (!amount && received) || + (!amount && (pending || received)) || (amount > 0 && (pending >= amount || received >= amount)); if (paid && !url.pathname.endsWith("paid")) { - throw redirect( + redirect( 307, `/${params.username}/invoice/${id}/paid` + (options ? "?options=true" : "") ); } - let sm = Qr.drawImg(invoice.text || "", { size: 300 }); - - return { id, invoice, sm, lg: sm }; + let src = Qr.drawImg(invoice.text || "", { size: 340 }); + return { id, invoice, src }; } diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/+page.svelte index 0ff1c21c..3be9cec9 100644 --- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/+page.svelte @@ -35,7 +35,7 @@ let readingerror = (e) => console.log("nfc error", e); $: refresh(data); - let { invoice, id, user, sm, lg } = data; + let { invoice, id, user, src } = data; let { amount, hash, @@ -50,13 +50,12 @@ } = invoice; let showQr = true; - $: src = sm; let qr; let tipPercent = 0; let refresh = async (data) => { - ({ invoice, id, user, sm, lg } = data); + ({ invoice, id, user, src } = data); ({ amount, hash, @@ -109,67 +108,36 @@ } }); - $: link = type === "bitcoin" ? text : `lightning:${text}`; - $: txt = type === "bitcoin" ? hash : text; + $: link = [types.bitcoin, types.liquid].includes(type) + ? text + : `lightning:${text}`; + $: txt = [types.bitcoin, types.liquid].includes(type) ? hash : text; - let toggleType = async () => { - invoice.type = - invoice.type === types.lightning ? types.bitcoin : types.lightning; + let setType = async (type) => { + invoice.type = type; ({ id } = await post(`/${username}/invoice`, { invoice, user: { username, currency }, })); - goto(`./${id}`, { invalidateAll: true }); + goto(`./${id}`, { invalidateAll: true, noScroll: 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> - - <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> - +<div class="invoice container mx-auto max-w-xl px-4 space-y-5"> {#if showQr} <div> <a - href={invoice.type === "bitcoin" + href={[types.bitcoin, types.liquid].includes(invoice.type) ? 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> + <img + {src} + class="mx-auto z-10 shadow-inner rounded-2xl" + bind:this={qr} + alt={txt} + /> </a> </div> {/if} @@ -187,28 +155,73 @@ </div> <div> <span class="text-secondary font-normal text-lg" - >{`${sat(amount)}`}</span + >⚡️{`${s(amount)}`}</span > {#if tip} <span class="text-sm text-secondary font-normal"> - +{sat(tip)} + +⚡️{s(tip)} </span> {/if} </div> </div> {/if} - <div class="text-center break-all"> - <a href={link}> - {txt} - </a> + <div class="flex justify-center gap-2"> + <button + class="hover:bg-gray-200 my-auto flex gap-1 p-2" + class:bg-gray-200={type === types.bitcoin} + on:click={() => setType(types.bitcoin)} + > + <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> + <div class="my-auto text-lg">Bitcoin</div> + </button> + + <button + class="hover:bg-gray-200 my-auto flex gap-1 p-2" + class:bg-gray-200={type === types.liquid} + on:click={() => setType(types.liquid)} + > + <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> + <div class="my-auto text-lg">Liquid</div> + </button> + + <button + class="hover:bg-gray-200 flex gap-1 p-2" + class:bg-gray-200={type === types.lightning} + on:click={() => setType(types.lightning)} + > + <div class="bg-white rounded-full w-8 h-8 text-center flex"> + <div class="m-auto">⚡️</div> + </div> + <div class="my-auto text-lg">Lightning</div> + </button> </div> + {#if type === types.liquid} + <div class="flex justify-center"> + <div class="my-auto text-xl text-center"> + We only support <span class="text-teal-500 font-bold">L-BTC</span>, + don't deposit any other assets + </div> + </div> + {/if} + + <button + type="button" + class="flex gap-2 text-center break-all rounded-full text-white bg-black hover:opacity-80 p-4 w-full md:w-80 mx-auto text-xl justify-center" + on:click={() => copy(txt)} + > + <Icon icon="copy" style="w-8 invert my-auto" /> + <div class="my-auto font-semibold"> + {txt.substr(0, 6)} ... {txt.substr(-10)} + </div> + </button> + <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" + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80 font-semibold" > <Icon icon="mobile" style="mr-1 w-6" /> <div class="text-secondary">{$t("payments.openLink")}</div> @@ -216,15 +229,7 @@ </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="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80" + class="w-full md:w-auto flex justify-center rounded-full border py-3 px-5 hover:opacity-80 font-semibold" on:click={() => (showQr = !showQr)} > <Icon icon="qr" style="mr-1 invert" /> @@ -235,3 +240,9 @@ > </div> </div> + +<style> + .invoice { + view-transition-name: invoice; + } +</style> diff --git a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte index 9740dc92..700ac972 100644 --- a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte +++ b/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte @@ -8,21 +8,28 @@ export let data; let amount, currency, rate, received, pending, tip, user, id; - $: data && + $: update(data); + let update = (data) => { ({ amount, currency, rate, received, pending, tip, user, id } = data.invoice); + }; toast.pop(0); </script> <div class="container px-4 text-center mx-auto"> - <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} + <div + class="flex w-full py-5 max-w-[200px] mx-auto" + in:scale={{ start: 0.5 }} + > + <Icon icon="check" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6"> {$t("invoice.paymentSuccessful")} </h1> + <h2 class="text-2xl md:text-3xl font-semibold"> {f(fiat(received - tip, rate), currency)} {#if tip} @@ -43,6 +50,13 @@ {/if} {#if pending} + <div + class="flex w-full py-5 max-w-[200px] mx-auto" + in:scale={{ start: 0.5 }} + > + <Icon icon="orangeclock" style="mx-auto" /> + </div> + <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment detected</h1> <h2 class="text-2xl md:text-3xl font-semibold"> {f(fiat(pending - tip, rate), currency)} @@ -50,6 +64,7 @@ + {f(fiat(tip, rate), currency)} {/if} </h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1"> ⚡️{s(pending - tip)} {#if tip} 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 c9377e36..b81e2364 100644 --- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js +++ b/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js @@ -4,7 +4,7 @@ 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}`); + redirect(307, `/${params.username}/invoice/${invoice.id}`); } export const actions = { diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/[username]/payments/[...page]/+page.svelte index e80d7e80..3237011b 100644 --- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte +++ b/src/routes/(app)/[username]/payments/[...page]/+page.svelte @@ -121,7 +121,7 @@ }; </script> -<div class="mt-24 mb-20"> +<div class="mt-24 mb-20 payments"> <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10"> {$t("payments.header")} </h1> @@ -220,6 +220,14 @@ alt="Bitcoin" /> </div> + {:else if p.type === types.liquid} + <div class="my-auto mr-1"> + <img + src="/images/liquid.svg" + class="w-12 border-4 border-transparent" + alt="Liquid" + /> + </div> {/if} <div class="my-auto"> @@ -238,7 +246,7 @@ {format(new Date(p.created), "h:mm aaa")} </div> <div> - {format(new Date(p.created), "MMM d")} + {format(new Date(p.created), "MMM d, yyyy")} </div> </div> </div> @@ -279,4 +287,8 @@ .active * { @apply bg-black text-white border-black; } + + .payments { + view-transition-name: payments; + } </style> diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte index 49e279b9..7a27bd67 100644 --- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte +++ b/src/routes/(app)/[username]/receive/[...param]/+page.svelte @@ -28,13 +28,14 @@ <form method="POST" - class="flex justify-center items-center mt-24 mb-3 px-3" + class="flex justify-center items-center 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} /> + <input type="hidden" name="rate" value={rate} /> {#if request} <input type="hidden" name="request_id" value={request.id} /> diff --git a/src/routes/(app)/[username]/request/[id]/+page.server.js b/src/routes/(app)/[username]/request/[id]/+page.server.js index 09cce465..2712c7ca 100644 --- a/src/routes/(app)/[username]/request/[id]/+page.server.js +++ b/src/routes/(app)/[username]/request/[id]/+page.server.js @@ -5,7 +5,7 @@ export let load = async ({ cookies, depends, params, parent }) => { depends("app:invoice"); let { user } = await parent(); - if (!user) throw redirect(307, "/login"); + if (!user) redirect(307, "/login"); let { request } = await get(`/request/${params.id}`, auth(cookies)); let { invoice } = request; @@ -16,10 +16,10 @@ export let load = async ({ cookies, depends, params, parent }) => { request.recipient.username === user.username && ((received && !amount) || received >= amount) ) - throw redirect(307, `/${user.username}/invoice/${id}/paid`); + redirect(307, `/${user.username}/invoice/${id}/paid`); if (request.requester.username === user.username) - throw redirect(307, `/send/${id}`); + redirect(307, `/send/${id}`); } return { request }; diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js index 2acad2dd..b9717b82 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.js +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.server.js @@ -3,7 +3,7 @@ import { fd, auth, get, post } from "$lib/utils"; export function load({ cookies, params, url }) { if (url.pathname.endsWith("settings")) - throw redirect(307, url.pathname + "/account"); + redirect(307, url.pathname + "/account"); params.cookies = cookies.getAll(); return params; } @@ -11,7 +11,13 @@ export function load({ cookies, params, url }) { export const actions = { default: async ({ cookies, request }) => { let form = await fd(request); - if (form.prompt) form.prompt = form.prompt === "on"; + + if (form.tab === "pos") { + form.notify = form.notify === "on"; + form.nip5 = form.nip5 === "on"; + form.prompt = form.prompt === "on"; + } + let user = { ...(await get("/me", auth(cookies))), ...form }; try { diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte index baef4d8e..54a2d71c 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/+page.svelte @@ -7,7 +7,7 @@ import Icon from "$comp/Icon.svelte"; import Spinner from "$comp/Spinner.svelte"; import Pin from "$comp/Pin.svelte"; - import { t } from "$lib/translations"; + import { loading, t } from "$lib/translations"; import { fail, auth, post, sleep, warning, success } from "$lib/utils"; import { avatar, banner, password, pin } from "$lib/store"; import { upload } from "$lib/upload"; @@ -39,7 +39,7 @@ $: form?.user && ({ user } = form); - $: form?.success && success("Settings saved!"); + $: form?.success && success($t("user.settings.saved"), false); $: form?.message && fail(form.message); @@ -58,11 +58,14 @@ $: ({ comp } = tabs.find((t) => t.name === tab)); let { address, id, username } = user; - let loading; + let submitting; + + $: if (!$loading && $page.url.searchParams.get("verified")) + success($t("user.settings.verified")); async function handleSubmit() { try { - loading = true; + submitting = true; let data = new FormData(formElement); if (data.get("password")) { @@ -90,7 +93,7 @@ mode: "no-cors", }); } catch (e) { - console.log("problem uploading avatar", e); + console.log("problem upsubmitting avatar", e); } } @@ -107,7 +110,7 @@ mode: "no-cors", }); } catch (e) { - console.log("problem uploading banner", e); + console.log("problem upsubmitting banner", e); } } @@ -146,8 +149,7 @@ auth(cookies) ); - warning("Verification email sent"); - await sleep(4000); + warning($t("user.settings.verifying"), false); } catch (e) { console.log(e); } @@ -171,7 +173,7 @@ fail("Something went wrong"); } - loading = false; + submitting = false; } </script> @@ -181,11 +183,12 @@ <form method="POST" - class="mb-[154px]" + class="mb-[154px] settings" on:submit|preventDefault={handleSubmit} bind:this={formElement} > <input type="hidden" name="pin" value={$pin} /> + <input type="hidden" name="tab" value={tab} /> <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> <div class="header"> @@ -220,9 +223,9 @@ 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} + class:bg-black={submitting} > - {#if loading} + {#if submitting} <Spinner /> {:else} <div class="my-auto">{$t("user.settings.saveSettings")}</div> @@ -232,7 +235,7 @@ </form> <style> - .header { - view-transition-name: tabs; + .settings { + view-transition-name: settings; } </style> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte index 20c883e3..15ee7b0c 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_account.svelte @@ -93,6 +93,22 @@ <input type="text" name="display" bind:value={user.display} /> </div> +<div class="space-y-1 relative"> + <label for="email" class="font-bold block">{$t("user.settings.email")}</label> + <p class="text-secondary"> + {$t("user.settings.emailDescription")} + </p> + + <div class="relative"> + <input type="text" name="email" bind:value={user.email} /> + {#if user.verified} + <Icon icon="check" style="absolute right-5 top-3 w-8" /> + {:else if user.email} + <Icon icon="orangeclock" style="absolute right-5 top-3 w-8" /> + {/if} + </div> +</div> + <div> <span class="font-bold">{$t("user.settings.profileImage")}</span> diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte index 8193bc9c..539cfd5e 100644 --- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte +++ b/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte @@ -33,20 +33,14 @@ </select> </div> -<div class="space-y-1 relative"> - <label for="email" class="font-bold block">{$t("user.settings.email")}</label> - <p class="text-secondary"> - {$t("user.settings.emailDescription")} - </p> - - <div class="relative"> - <input type="text" name="email" bind:value={user.email} /> - {#if user.verified} - <Icon icon="check" style="absolute right-5 top-3 w-8" /> - {:else} - <Icon icon="orangeclock" style="absolute right-5 top-3 w-8" /> - {/if} +<div> + <div class="flex justify-between items-center"> + <span class="font-bold">{$t("user.settings.notifications")}</span> + <Toggle id="notify" bind:value={user.notify} /> </div> + <p class="text-secondary mt-1 w-9/12"> + {$t("user.settings.notificationsDesc")} + </p> </div> <div> diff --git a/src/routes/(app)/[username]/verify/[code]/+page.server.js b/src/routes/(app)/[username]/verify/[code]/+page.server.js index fda11c9e..19453d9d 100644 --- a/src/routes/(app)/[username]/verify/[code]/+page.server.js +++ b/src/routes/(app)/[username]/verify/[code]/+page.server.js @@ -1,12 +1,14 @@ import { get } from "$lib/utils"; -import { error } from "@sveltejs/kit"; +import { error, redirect } from "@sveltejs/kit"; export let load = async ({ params: { code } }) => { + let user; try { - let user = await get(`/verify/${code}`); - return { user }; + user = await get(`/verify/${code}`); } catch (e) { console.log(e); return { error: "Verification failed" }; } + + redirect(307, `/${user.username}/settings/account?verified=true`); }; diff --git a/src/routes/(app)/admin/+layout.server.js b/src/routes/(app)/admin/+layout.server.js index e49c6e40..9250a7d4 100644 --- a/src/routes/(app)/admin/+layout.server.js +++ b/src/routes/(app)/admin/+layout.server.js @@ -3,9 +3,9 @@ import { redirect } from "@sveltejs/kit"; export async function load({ cookies, parent }) { let token = cookies.get("token"); - if (!token) throw redirect(307, "/login"); + if (!token) redirect(307, "/login"); let user = await get("/me", auth(cookies)); - if (!user) throw redirect(307, "/login"); + if (!user) redirect(307, "/login"); return { user }; } diff --git a/src/routes/(app)/admin/+page.server.js b/src/routes/(app)/admin/+page.server.js index 93931aa3..96a1cd78 100644 --- a/src/routes/(app)/admin/+page.server.js +++ b/src/routes/(app)/admin/+page.server.js @@ -3,7 +3,7 @@ import { redirect } from "@sveltejs/kit"; export async function load({ cookies, parent }) { let { user } = await parent(); - if (!user) throw redirect(307, "/login"); + if (!user) redirect(307, "/login"); let users = (await get("/users", auth(cookies))).sort( (a, b) => b.balance - a.balance diff --git a/src/routes/(app)/buy/+page.server.js b/src/routes/(app)/buy/+page.server.js index 3369ec85..903fec0e 100644 --- a/src/routes/(app)/buy/+page.server.js +++ b/src/routes/(app)/buy/+page.server.js @@ -28,6 +28,6 @@ export const actions = { auth(cookies) ); - throw redirect(307, `/${form.get("username")}/payments`); + redirect(307, `/${form.get("username")}/payments`); }, }; diff --git a/src/routes/(app)/disable2fa/+server.js b/src/routes/(app)/disable2fa/+server.js index 1e264398..0afda3b9 100644 --- a/src/routes/(app)/disable2fa/+server.js +++ b/src/routes/(app)/disable2fa/+server.js @@ -8,6 +8,6 @@ export async function POST({ cookies, request }) { return json(res); } catch (e) { - throw error(500, "Problem disabling 2fa"); + error(500, "Problem disabling 2fa"); } } diff --git a/src/routes/(app)/forgot/+page.server.js b/src/routes/(app)/forgot/+page.server.js new file mode 100644 index 00000000..48411539 --- /dev/null +++ b/src/routes/(app)/forgot/+page.server.js @@ -0,0 +1,14 @@ +import { fail, redirect } from "@sveltejs/kit"; +import { fd, post, login } from "$lib/utils"; + +export const actions = { + default: async ({ request }) => { + try { + let { email } = await fd(request); + await post("/forgot", { email }); + return { success: true }; + } catch (e) { + return fail(400, { error: e.message }); + } + }, +}; diff --git a/src/routes/(app)/forgot/+page.svelte b/src/routes/(app)/forgot/+page.svelte new file mode 100644 index 00000000..378262cc --- /dev/null +++ b/src/routes/(app)/forgot/+page.svelte @@ -0,0 +1,77 @@ +<script> + import { focus } from "$lib/utils"; + import { enhance } from "$app/forms"; + import { t } from "$lib/translations"; + import Icon from "$comp/Icon.svelte"; + import Spinner from "$comp/Spinner.svelte"; + import { fly } from "svelte/transition"; + + export let form; + let email; + let loading; +</script> + +<div class="pt-10"> + <div class="w-[243px] mx-auto mb-10"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> + + <div class="flex justify-center items-center"> + <div + class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]" + > + <h1 class="text-2xl font-bold text-center">{$t("login.reset")}</h1> + + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} + + {#if form?.success} + <div class="text-secondary"> + A password reset link will be sent to your email if we have it on + file. + </div> + + <div class="flex w-full"> + <a + href="/login" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80 text-center" + > + Continue + </a> + </div> + {:else} + <form class="space-y-5" method="POST" use:enhance> + <div> + <label for="email" class="font-semibold">{$t("login.email")}</label> + <input + name="email" + type="text" + required + class="bg-primary" + bind:value={email} + use:focus + autocapitalize="none" + /> + </div> + + <button + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + {$t("login.submit")} + {/if} + </button> + </form> + {/if} + </div> + </div> +</div> diff --git a/src/routes/(app)/fund/[id]/+page.server.js b/src/routes/(app)/fund/[id]/+page.server.js index 6c5c943f..c5c8db2f 100644 --- a/src/routes/(app)/fund/[id]/+page.server.js +++ b/src/routes/(app)/fund/[id]/+page.server.js @@ -15,9 +15,9 @@ export const actions = { if (user) { await post("/redeem", params, auth(cookies)); - throw redirect(307, `/${user.username}/payments`); + redirect(307, `/${user.username}/payments`); } - throw redirect(307, "/register"); + redirect(307, "/register"); }, }; diff --git a/src/routes/(app)/fund/[id]/sweep/+page.server.js b/src/routes/(app)/fund/[id]/sweep/+page.server.js index 05fb18b6..613cf8e2 100644 --- a/src/routes/(app)/fund/[id]/sweep/+page.server.js +++ b/src/routes/(app)/fund/[id]/sweep/+page.server.js @@ -6,8 +6,8 @@ export async function load({ cookies, params, 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`); + if (!amount) redirect(307, `/fund/${id}`); + if (!user) 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]/withdraw/+page.server.js b/src/routes/(app)/fund/[id]/withdraw/+page.server.js index 6fb08f61..1da349f8 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.server.js +++ b/src/routes/(app)/fund/[id]/withdraw/+page.server.js @@ -3,7 +3,7 @@ import { fd, auth, post } from "$lib/utils"; export async function load({ parent }) { let { user } = await parent(); - if (!user) throw redirect(307, "/register"); + if (!user) redirect(307, "/register"); } export const actions = { @@ -15,6 +15,6 @@ export const actions = { return fail(400, { message: e.message }); } - throw redirect(307, `/fund/${body.id}`); + redirect(307, `/fund/${body.id}`); }, }; diff --git a/src/routes/(app)/launch/purchase/+page.server.js b/src/routes/(app)/launch/purchase/+page.server.js new file mode 100644 index 00000000..d34c1f41 --- /dev/null +++ b/src/routes/(app)/launch/purchase/+page.server.js @@ -0,0 +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"; + +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) 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) + ); + + redirect(307, `/launch/thanks/${asset}`); + }, + + lightning: async ({ cookies, request }) => { + let form = await request.formData(); + + let rates = await get("/rates"); + + let invoice = { + amount: 22000, + memo: "launch", + type: "lightning", + prompt: false, + rate: rates[form.get("currency")], + }; + + let user = { username: form.get("username") }; + + let { id } = await post("/invoice", { invoice, user }, auth(cookies)); + + redirect(307, `/${user.username}/invoice/${id}`); + }, +}; diff --git a/src/routes/(app)/launch/purchase/card/+page.server.js b/src/routes/(app)/launch/purchase/card/+page.server.js new file mode 100644 index 00000000..9fb36b71 --- /dev/null +++ b/src/routes/(app)/launch/purchase/card/+page.server.js @@ -0,0 +1,51 @@ +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"; + +export const actions = { + default: async ({ cookies, request }) => { + let form = await request.formData(); + + 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 method = "POST"; + + 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", + }); + + let { status } = await fetch(`${stripe}/charges`, { + body, + headers, + method, + }).then((r) => r.json()); + + if (status !== "succeeded") error(500, { message: "Card payment failed" }); + let asset = await createTicket(cookies, form.get("username")); + + redirect(307, `/launch/thanks/${asset}`); + }, +}; diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.js b/src/routes/(app)/ln/[lnurl]/+page.server.js index 332bc9e3..910ce5a5 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.server.js +++ b/src/routes/(app)/ln/[lnurl]/+page.server.js @@ -3,7 +3,7 @@ import { error, fail, redirect } from "@sveltejs/kit"; export async function load({ params, parent }) { let { user } = await parent(); - if (!user) throw redirect(307, "/register"); + if (!user) redirect(307, "/register"); let data; @@ -11,11 +11,11 @@ export async function load({ params, parent }) { let { lnurl } = params; data = await get(`/decode?text=${lnurl}`); } catch (e) { - throw error(500, e.message); + error(500, e.message); } if (!["payRequest", "withdrawRequest"].includes(data.tag)) - throw error(500, "We only support LNURLp and LNURLw at this time"); + error(500, "We only support LNURLp and LNURLw at this time"); return data; } @@ -37,7 +37,7 @@ export const actions = { let { pr } = await fetch(`${callback}?amount=${amount * 1000}`).then((r) => r.json() ); - throw redirect(307, `/send/lightning/${pr}`); + redirect(307, `/send/lightning/${pr}`); }, withdraw: async ({ cookies, fetch, request }) => { @@ -70,6 +70,6 @@ export const actions = { let c = callback.includes("?") ? "&" : "?"; await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); - throw redirect(307, `/ln/withdrawal/sent`); + redirect(307, `/ln/withdrawal/sent`); }, }; diff --git a/src/routes/(app)/login/+page.server.js b/src/routes/(app)/login/+page.server.js index 6876c93d..7b977024 100644 --- a/src/routes/(app)/login/+page.server.js +++ b/src/routes/(app)/login/+page.server.js @@ -3,7 +3,7 @@ import { login, fd } from "$lib/utils"; export const load = async ({ parent }) => { let { user } = await parent(); - if (user?.pubkey) throw redirect(307, `/${user.username}`); + if (user?.pubkey) redirect(307, `/${user.username}`); }; export const actions = { @@ -24,6 +24,6 @@ export const actions = { return fail(400, { error: "Login failed", message: e.message, ...form }); } - throw redirect(303, loginRedirect || `/${user.username}`); + redirect(307, loginRedirect || `/${user.username}`); }, }; diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index 647ef03c..cebbf41c 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -1,7 +1,18 @@ <script> - import RegisterOrLogin from "$comp/RegisterOrLogin.svelte"; import { onMount } from "svelte"; - import { pin } from "$lib/store"; + import { applyAction, deserialize } from "$app/forms"; + import { tick } from "svelte"; + import { fly } from "svelte/transition"; + import { enhance } from "$app/forms"; + import Pin from "$comp/Pin.svelte"; + import Icon from "$comp/Icon.svelte"; + import Spinner from "$comp/Spinner.svelte"; + import { focus, fail } from "$lib/utils"; + import { password, pin, loginRedirect } from "$lib/store"; + import { t } from "$lib/translations"; + import { page } from "$app/stores"; + import { generate } from "$lib/nostr"; + import { invalidateAll } from "$app/navigation"; onMount(() => { $pin = undefined; @@ -10,6 +21,165 @@ }); export let form; + + let token, formElement; + let code = []; + let redirect; + + $password = undefined; + + $: need2fa = form?.message === "2fa"; + let cancel = () => (need2fa = false); + $: if (form?.message === "2fa" && form.token === token) token = ""; + + let username, email, btn; + + $: update(form); + let update = (form) => form && ({ username, password: $password } = form); + + $: token && token?.length === 6 && tick().then(() => btn.click()); + + let revealPassword = false; + + let loading; + async function handleSubmit(e) { + loading = true; + + let data = new FormData(this); + let user = Object.fromEntries(data); + await generate(user); + + for (let k in user) { + data.set(k, user[k]); + } + + const response = await fetch("/login", { + method: "POST", + body: data, + }); + + const result = deserialize(await response.text()); + + if (result.type === "success") { + await invalidateAll(); + } + + applyAction(result); + loading = false; + } </script> -<RegisterOrLogin pageID="signIn" bind:form /> +{#if need2fa} + <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> +{/if} + +<div class="pt-10"> + <div class="w-[243px] mx-auto mb-10"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> + + <div class="flex justify-center items-center"> + <div + class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]" + > + <h1 class="text-2xl font-bold text-center">{$t("login.signIn")}</h1> + + {#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} /> + + <div> + <label for="username" class="font-semibold" + >{$t("login.username")}</label + > + <input + name="username" + type="text" + required + class="bg-primary" + bind:value={username} + use:focus + autocapitalize="none" + /> + </div> + + <div class="relative"> + <label for="password" class="block font-semibold" + >{$t("login.password")}</label + > + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> + </div> + + <div class="flex justify-end items-center"> + <a href="/forgot" class="underline underline-offset-4 text-black" + >{$t("login.forgotUserOrPassword")}</a + > + </div> + + <button + 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.signIn")} + {/if} + </button> + </form> + + <p class="text-secondary text-center font-medium"> + {$t("login.haveAccount")} + <a + href={"/register"} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {$t("login.register")} + </a> + </p> + </div> + </div> +</div> diff --git a/src/routes/(app)/logout/+page.server.js b/src/routes/(app)/logout/+page.server.js index 60750dfa..97bb1e73 100644 --- a/src/routes/(app)/logout/+page.server.js +++ b/src/routes/(app)/logout/+page.server.js @@ -8,5 +8,5 @@ const opts = { export async function load({ cookies }) { cookies.set("username", "", opts); cookies.set("token", "", opts); - throw redirect(307, "/login"); + redirect(307, "/login"); } diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index fd5bbae0..dbaea042 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -1,5 +1,7 @@ <script> - import { t } from "$lib/translations"; + import { browser } from "$app/environment"; + import { onMount, tick } from "svelte"; + import { t, loading } from "$lib/translations"; import { back, copy, @@ -14,15 +16,32 @@ import Avatar from "$comp/Avatar.svelte"; import Icon from "$comp/Icon.svelte"; import { format } from "date-fns"; - import { PUBLIC_EXPLORER as expl } from "$env/static/public"; + import { PUBLIC_EXPLORER, PUBLIC_LIQUID_EXPLORER } from "$env/static/public"; export let data; let { user, payment: p } = data; let { username } = user; - let { id, amount, created, rate, type, ref, tip, ourfee, fee, currency } = p; + let { + id, + hash, + amount, + created, + rate, + type, + ref, + tip, + ourfee, + fee, + currency, + } = p; let a = Math.abs(amount); + let expl = { + bitcoin: PUBLIC_EXPLORER, + liquid: PUBLIC_LIQUID_EXPLORER, + }[type]; + let print = async () => { await post(`/payment/${id}/print`, { id }); success("Printing!"); @@ -30,9 +49,18 @@ fee = fee || 0; - let direction = amount > 0 ? $t("payments.from") : $t("payments.to"); - direction = - direction[0].toUpperCase() + direction.substr(1, direction.length); + let txid, vout; + if (amount > 0) [txid, vout] = ref.split(":"); + else txid = hash; + + let direction = ""; + onMount(() => { + direction = amount > 0 ? $t("payments.from") : $t("payments.to"); + + if (direction) + direction = + direction[0].toUpperCase() + direction.substr(1, direction.length); + }); </script> <a href={`/${username}/payments`}> @@ -124,22 +152,27 @@ <div> <span class="text-lg text-secondary">Date</span> <div> - {format(new Date(created), "MMMM d")}, - {format(new Date(created), "h:mm aaa")} + {format(new Date(created), "h:mmaaa")} + {format(new Date(created), "MMM d, yyyy")} </div> </div> - {#if type === "bitcoin"} + {#if type === "bitcoin" || type === "liquid"} <div> <span class="text-lg text-secondary">Txid</span> <div class="flex"> <div> - <a href={`${expl}/tx/${id}`} target="_blank" rel="noreferrer">{id}</a> + <a + href={`${expl}/tx/${txid}${vout ? "#vout=" + vout : ""}`} + target="_blank" + rel="noreferrer">{txid}</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" /> + on:click={() => copy(txid)} + > + <Icon icon="copy" style="ml-2 w-12 my-auto" /> </button> </div> </div> diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte index 2678d91a..da164b50 100644 --- a/src/routes/(app)/qr/[text]/+page.svelte +++ b/src/routes/(app)/qr/[text]/+page.svelte @@ -10,14 +10,14 @@ <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="container px-4 max-w-4xl mx-auto 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" > <Qr {src} /> - <div class="text-center w-full text-2xl py-10 break-all"> + <div class="text-center w-full text-lg break-all"> {text} </div> </div> diff --git a/src/routes/(app)/redeem/[redeemcode]/+page.server.js b/src/routes/(app)/redeem/[redeemcode]/+page.server.js index f227f22e..25153533 100644 --- a/src/routes/(app)/redeem/[redeemcode]/+page.server.js +++ b/src/routes/(app)/redeem/[redeemcode]/+page.server.js @@ -8,10 +8,10 @@ export async function load({ cookies, locals, params }) { try { await post("/redeem", params, auth(cookies)); } catch (e) { - throw redirect(307, "/"); + redirect(307, "/"); } - throw redirect(307, `/${user.username}/payments`); + redirect(307, `/${user.username}/payments`); } - throw redirect(307, "/login"); + redirect(307, "/login"); } diff --git a/src/routes/(app)/register/+page.server.js b/src/routes/(app)/register/+page.server.js index e986c73d..318f6b98 100644 --- a/src/routes/(app)/register/+page.server.js +++ b/src/routes/(app)/register/+page.server.js @@ -3,7 +3,7 @@ import { fd, post, login } from "$lib/utils"; export const load = async ({ parent }) => { let { user } = await parent(); - if (user) throw redirect(307, `/${user.username}`); + if (user) redirect(307, `/${user.username}`); let index = Math.floor(Math.random() * 64) + 1; @@ -25,7 +25,6 @@ export const actions = { try { await post("/register", { user }, { "cf-connecting-ip": ip }); } catch (e) { - console.log(e); if (e.message.includes("taken")) error = e.message; } @@ -37,6 +36,6 @@ export const actions = { } if (error) return fail(400, { error }); - throw redirect(307, loginRedirect || `/${user.username}`); + redirect(307, loginRedirect || `/${user.username}`); }, }; diff --git a/src/routes/(app)/reset/[code]/+page.server.js b/src/routes/(app)/reset/[code]/+page.server.js new file mode 100644 index 00000000..67f2d919 --- /dev/null +++ b/src/routes/(app)/reset/[code]/+page.server.js @@ -0,0 +1,30 @@ +import { fail, redirect } from "@sveltejs/kit"; +import { fd, post, login } from "$lib/utils"; + +export const actions = { + default: async ({ cookies, params, request }) => { + let ip = request.headers.get("cf-connecting-ip"); + let error; + + let { code } = params; + let { password } = await fd(request); + + try { + let user = await post("/reset", { code, password }); + user.password = password; + + try { + await login(user, cookies, ip); + error = null; + } catch (e) { + error ||= e.message; + } + + if (error) return fail(400, { error }); + redirect(307, `/${user.username}`); + } catch (e) { + console.log(e.message); + return fail(400, { error: e.message }); + } + }, +}; diff --git a/src/routes/(app)/reset/[code]/+page.svelte b/src/routes/(app)/reset/[code]/+page.svelte new file mode 100644 index 00000000..d93e8bdf --- /dev/null +++ b/src/routes/(app)/reset/[code]/+page.svelte @@ -0,0 +1,86 @@ +<script> + import { t } from "$lib/translations"; + import Icon from "$comp/Icon.svelte"; + import Spinner from "$comp/Spinner.svelte"; + import { fly } from "svelte/transition"; + import { password } from "$lib/store"; + + export let form; + let email; + let loading; + let revealPassword; +</script> + +<div class="pt-10"> + <div class="w-[243px] mx-auto mb-10"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> + + <div class="flex justify-center items-center"> + <div + class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]" + > + <h1 class="text-2xl font-bold text-center">{$t("login.reset")}</h1> + + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> + {/if} + + <form class="space-y-5" method="POST"> + <div class="relative"> + <label for="password" class="block font-semibold" + >{$t("login.newPassword")}</label + > + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={$password} + autocapitalize="none" + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> + </div> + + <div class="text-secondary"> + A new seed phrase will be generated for your account. You'll need to + import your old seed phrase if you want to use the same nostr profile + and savings account as before. + </div> + + <button + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + {$t("login.submit")} + {/if} + </button> + </form> + </div> + </div> +</div> diff --git a/src/routes/(app)/send/[...text]/+page.server.js b/src/routes/(app)/send/[...text]/+page.server.js index 9a76f21d..73a44512 100644 --- a/src/routes/(app)/send/[...text]/+page.server.js +++ b/src/routes/(app)/send/[...text]/+page.server.js @@ -1,14 +1,14 @@ import { validate } from "bitcoin-address-validation"; import bip21 from "bip21"; -import { auth, get, post } from "$lib/utils"; +import { auth, get, isLiquid, 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.startsWith("http")) throw redirect(307, t); - if (t.startsWith(host)) throw redirect(307, `http://${t}`); + if (t.startsWith("http")) redirect(307, t); + if (t.startsWith(host)) redirect(307, `http://${t}`); let amount, user, id; @@ -28,12 +28,12 @@ let parse = async (t, host) => { if (t.endsWith("@classic")) { ({ uuid: id } = await get(`/invoice/classic/${t.replace("@classic", "")}`)); - if (id) throw redirect(307, `/send/invoice/${id}`); + if (id) redirect(307, `/send/invoice/${id}`); } - if (t.includes("/fund")) throw redirect(307, t.substring(t.indexOf("/fund"))); + if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund"))); - if (t.toLowerCase().startsWith("lnurl")) throw redirect(307, `/ln/${t}`); + if (t.toLowerCase().startsWith("lnurl")) redirect(307, `/ln/${t}`); if (t.includes(":")) t = t.split(":")[1]; // lightning @@ -41,22 +41,22 @@ let parse = async (t, host) => { try { ({ id } = await get(`/invoice/${t}`)); } catch (e) { - throw redirect(307, `/send/lightning/${t}`); + redirect(307, `/send/lightning/${t}`); } } // bitcoin - if (validate(t)) { + if (validate(t) || isLiquid(t)) { try { ({ id, user } = await get(`/invoice/${t}`)); } catch (e) { let r = `/send/bitcoin/${t}`; if (amount) r += "/" + amount; - throw redirect(307, r); + redirect(307, r); } - if (user) throw redirect(307, `/send/${user.username}`); - else if (id) throw redirect(307, `/send/${id}`); + if (user) redirect(307, `/send/${user.username}`); + else if (id) redirect(307, `/send/${id}`); } // user @@ -65,14 +65,14 @@ let parse = async (t, host) => { if (user.anon) user = null; } catch (e) {} - if (user) throw redirect(307, `/send/user/${t}`); + if (user) redirect(307, `/send/user/${t}`); let fund; try { fund = await get(`/fund/${t}`); } catch (e) {} - if (fund) throw redirect(307, `/send/fund/${t}`); + if (fund) redirect(307, `/send/fund/${t}`); // invoice let invoice; @@ -80,7 +80,7 @@ let parse = async (t, host) => { invoice = await get(`/invoice/${t}`); } catch (e) {} - if (invoice) throw redirect(307, `/send/invoice/${invoice.hash}`); + if (invoice) redirect(307, `/send/invoice/${invoice.hash}`); }; export async function load({ cookies, params, request, url }) { diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index 8548964c..343bffd5 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -58,23 +58,24 @@ /> </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="grid grid-cols-2 md:grid-cols-4 gap-2"> + <a + href={`/qr/${encodeURIComponent(text)}`} + class:invisible={!text?.length} + > + <button + type="button" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1 w-full" + > + <Icon icon="qr" style="mr-2 w-6 my-auto invert" /> + <div class="my-auto">{$t("user.send.makeQR")}</div> + </button> + </a> - <a href="/scan"> + <a href="/scan" class="w-full"> <button type="button" - class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1" + class="flex border rounded-full px-6 py-2 font-bold hover:opacity-80 mr-1 w-full" > <Icon icon="camera" style="mr-2 w-6 my-auto" /> <div class="my-auto">{$t("user.send.scan")}</div> diff --git a/src/routes/(app)/send/bitcoin/[address]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/+page.svelte new file mode 100644 index 00000000..bd54cf83 --- /dev/null +++ b/src/routes/(app)/send/bitcoin/[address]/+page.svelte @@ -0,0 +1,61 @@ +<script> + import { t } from "$lib/translations"; + import Icon from "$comp/Icon.svelte"; + import Numpad from "$comp/Numpad.svelte"; + import { page } from "$app/stores"; + import { rate } from "$lib/store"; + import { back, fail, s } from "$lib/utils"; + + export let data; + + let { rates } = data; + let { address } = $page.params; + let { balance, currency } = data.user; + + let amount = 0; + let submit, fiat; + + $: update(data); + let update = () => ($rate = data.rates[currency]); + + let setMax = () => { + fiat = false; + amount = balance; + }; +</script> + +<button + type="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-xl mx-auto space-y-5 text-center"> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> + + <div class="text-xl text-secondary break-all">{address}</div> + + <Numpad bind:amount bind:fiat {currency} {submit} bind:rate={$rate} /> + + <div class="flex justify-center gap-2"> + <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 + > + + <form action={`/send/bitcoin/${address}/${amount}`}> + <button + use:focus + 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" + > + {$t("payments.next")} + </button> + </form> + </div> +</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 deleted file mode 100644 index 5cb86aec..00000000 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js +++ /dev/null @@ -1,32 +0,0 @@ -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" }); - - let estimate = await post( - "/bitcoin/fee", - { address, amount, feeRate, subtract }, - auth(cookies) - ); - - let { tx } = estimate; - - if (stale || !confirmed) return fail(400, estimate); - - 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 }); - } - - 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 deleted file mode 100644 index 133eab91..00000000 --- a/src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte +++ /dev/null @@ -1,163 +0,0 @@ -<script> - import { tick } from "svelte"; - import { t } from "$lib/translations"; - import { enhance } from "$app/forms"; - import Toggle from "$comp/Toggle.svelte"; - import Slider from "$comp/Slider.svelte"; - import Icon from "$comp/Icon.svelte"; - import Numpad from "$comp/Numpad.svelte"; - import Spinner from "$comp/Spinner.svelte"; - 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; - - let { rates } = data; - let { address, amount } = $page.params; - let { balance, currency } = data.user; - let loading, - submit, - fiat, - confirmed, - feeRate, - min, - max, - fee, - ourfee, - stale, - subtract, - adjusting; - - $: rate = rates[currency]; - - let toggle = () => (loading = true); - let toggleAdjusting = () => (adjusting = !adjusting); - - $: 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; - }; - - $: 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); - }; -</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> - -{#if form?.message} - <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> - - <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> - - <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} - - <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} - - <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> -</div> diff --git a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.js b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.js new file mode 100644 index 00000000..6f735f32 --- /dev/null +++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.js @@ -0,0 +1,30 @@ +import { fail, redirect } from "@sveltejs/kit"; +import { auth, post, fd } from "$lib/utils"; + +let seen = {}; + +export async function load({ params: { address, amount, feeRate }, cookies }) { + try { + let { fee, fees, ourfee, hex } = await post( + "/bitcoin/fee", + { address, amount, feeRate }, + auth(cookies) + ); + + return { amount, address, fee, fees, feeRate, ourfee, hex }; + } catch (e) { + return { message: e.message }; + } +} + +export const actions = { + default: async ({ cookies, request }) => { + try { + let r = await post("/bitcoin/send", await fd(request), auth(cookies)); + } catch (e) { + return fail(400, { message: e.message }); + } + + redirect(307, "/sent"); + }, +}; diff --git a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte new file mode 100644 index 00000000..84506e6b --- /dev/null +++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte @@ -0,0 +1,146 @@ +<script> + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import Toggle from "$comp/Toggle.svelte"; + import Icon from "$comp/Icon.svelte"; + import Spinner from "$comp/Spinner.svelte"; + import { page } from "$app/stores"; + import { fiat as toFiat, f, focus, s, sat, closest } from "$lib/utils"; + import { pin } from "$lib/store"; + import { goto, invalidate } from "$app/navigation"; + import { rate } from "$lib/store"; + + export let data; + export let form; + + let { amount, address, message, fee, fees, feeRate, ourfee, rates, hex } = + data; + + $: reload(data); + let reload = () => { + ({ amount, address, message, fee, fees, feeRate, ourfee, rates, hex } = + data); + if (!fees) return; + delete fees.minimumFee; + feeRate = closest(Object.values(fees), feeRate); + if (!$rate) $rate = rates[currency]; + }; + + let { balance, currency } = data.user; + let submitting, submit, showSettings; + + let feeNames = { + fastestFee: "Fastest", + halfHourFee: "Fast", + hourFee: "Medium", + economyFee: "Slow", + }; + + let toggleSettings = () => (showSettings = !showSettings); + + $: update(form); + let update = () => { + submitting = false; + if (form?.message?.includes("pin")) $pin = undefined; + }; + + let setFee = () => goto(`/send/bitcoin/${address}/${amount}/${feeRate}`); + let goBack = () => goto(`/send/bitcoin/${address}`); +</script> + +<button + type="button" + class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" + data-sveltekit-preload-data="false" + on:click={goBack} +> + <Icon icon="arrow-left" style="w-10" /> +</button> + +<div + class="container px-4 max-w-xl mx-auto space-y-5 text-center no-transition" +> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> + + {#if form?.message || message} + <div class="mb-5"> + <div class="text-red-600">{form?.message || message}</div> + </div> + {:else} + <div class="text-xl text-secondary break-all">{address}</div> + + <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 text-lg">{$t("payments.networkFee")}</h2> + + <div class="flex flex-wrap gap-4 justify-center"> + <select + bind:value={feeRate} + on:change={setFee} + class="border text-lg bg-white p-4 rounded-2xl text-center my-auto" + > + {#each Object.keys(fees) as feeName} + <option value={fees[feeName]} + >{feeNames[feeName]} — ⚡️{fees[feeName]}/vb</option + > + {/each} + </select> + <div class="my-auto"> + <h2 class="text-xl"> + {f(toFiat(fee, $rate), currency)} + </h2> + <h3 class="text-secondary">⚡️{s(fee)}</h3> + </div> + </div> + </div> + + {#if ourfee} + <div class="text-center"> + <h2 class="text-secondary text-lg">{$t("payments.platformFee")}</h2> + + <div class="flex flex-wrap gap-4 justify-center"> + <div class="my-auto"> + <h2 class="text-xl"> + {f(toFiat(ourfee, $rate), currency)} + </h2> + <h3 class="text-secondary">⚡️{s(ourfee)}</h3> + </div> + </div> + </div> + {/if} + + <form method="POST" use:enhance on:submit={() => (submitting = true)}> + <input name="pin" value={$pin} type="hidden" /> + <input name="hex" value={hex} type="hidden" /> + <input name="rate" value={$rate} type="hidden" /> + + <div class="flex justify-center gap-2"> + <button + use:focus + 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={submitting} + > + {#if submitting} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> + {/if} +</div> + +<style> + .no-transition { + view-transition-name: fee; + } +</style> 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 3318a60b..51277e5d 100644 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js @@ -12,6 +12,6 @@ export const actions = { return fail(400, { message: e.message }); } - if (p) throw redirect(307, `/fund/${body.name}`); + if (p) redirect(307, `/fund/${body.name}`); }, }; diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.js b/src/routes/(app)/send/invoice/[id]/+page.server.js index aa284328..88017362 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.js +++ b/src/routes/(app)/send/invoice/[id]/+page.server.js @@ -17,9 +17,10 @@ export async function load({ params: { id }, parent, url }) { } = await get(`/invoice/${id}`); if (prompt && tip === null) - throw redirect(307, `/${recipient.username}/invoice/${id}/tip`); + redirect(307, `/${recipient.username}/invoice/${id}/tip`); if (recipient.username === user.username) - throw error(500, { message: "Cannot send to self" }); + error(500, { message: "Cannot send to self" }); + return { amount, address, @@ -43,6 +44,6 @@ export const actions = { return fail(400, { message: e.message }); } - throw redirect(307, "/sent"); + redirect(307, "/sent"); }, }; diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index 79367534..c2163a81 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -9,13 +9,21 @@ import Numpad from "$comp/Numpad.svelte"; import Spinner from "$comp/Spinner.svelte"; import { page } from "$app/stores"; - import { post, back, f, fiat as toFiat, s, sats } from "$lib/utils"; + import { post, back, f, fiat as toFiat, s, sats, focus } from "$lib/utils"; export let data; export let form; - let { address, hash, rate, payreq, recipient, tip, rates, user } = data; + let { address, hash, payreq, recipient, tip, rates, user } = data; let { currency } = user; + $: reload(data); + let reload = (data) => { + ({ address, hash, payreq, recipient, tip, rates, user } = data); + ({ currency } = user); + }; + + $: rate = data.rate * (rates[user.currency] / rates[data.currency]); + let amount = form?.amount || data.amount; let a, af, @@ -42,8 +50,6 @@ }); }; - $: rate = data.rate * (rates[user.currency] / rates[data.currency]); - $: update(form); let update = () => { if (form?.message.includes("pin")) $pin = undefined; @@ -80,7 +86,7 @@ </div> {/if} -<div class="container px-4 mt-20 max-w-xl mx-auto"> +<div class="container px-4 max-w-xl mx-auto"> {#if amount} <div class="text-center mb-8"> <h1 class="text-xl md:text-2xl text-secondary mb-2"> @@ -143,6 +149,7 @@ <div class="flex w-full"> {#if amount} <button + use:focus 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" > @@ -163,7 +170,7 @@ {/if} </div> </form> - <div class="flex my-20"> + <div class="flex my-10"> <button class="mx-auto" on:click={external} >{$t("payments.moreOptions")}</button > diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.server.js b/src/routes/(app)/send/lightning/[payreq]/+page.server.js index d9e791e1..89bcc3c3 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.server.js +++ b/src/routes/(app)/send/lightning/[payreq]/+page.server.js @@ -1,8 +1,8 @@ import { fail, redirect } from "@sveltejs/kit"; import { fd, auth, post } from "$lib/utils"; -export async function load({ params }) { - return post("/parse", params); +export async function load({ cookies, params }) { + return post("/parse", params, auth(cookies)); } export const actions = { @@ -19,6 +19,6 @@ export const actions = { return fail(400, { message: e.message }); } - throw redirect(307, "/sent"); + redirect(307, "/sent"); }, }; diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte index ce9649fa..57a17e01 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte +++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte @@ -5,21 +5,46 @@ import Numpad from "$comp/Numpad.svelte"; import Spinner from "$comp/Spinner.svelte"; import { page } from "$app/stores"; - import { back, s } from "$lib/utils"; - import { pin } from "$lib/store"; + import { back, fiat, f, s, focus } from "$lib/utils"; + import { rate, pin } from "$lib/store"; export let data; export let form; let { payreq } = $page.params; - let { alias, rates } = data; - - let { currency } = data.user; + let { + alias, + ourfee, + rates, + user: { currency }, + } = data; let a; - let maxfee = 500; + + $: reload(data); + let reload = (data) => { + ({ + alias, + ourfee, + rates, + user: { currency }, + } = data); + if (!$rate) $rate = rates[currency]; + }; $: amount = form?.amount || data.amount; - $: rate = rates[currency]; + $: maxfee = Math.round( + amount < 100 + ? amount * 5 + : amount < 1000 + ? amount + : amount < 10000 + ? amount * 0.5 + : amount < 100000 + ? amount * 0.1 + : amount < 1000000 + ? amount * 0.05 + : amount * 0.01 + ); let loading; let submit = () => (loading = true); @@ -37,27 +62,48 @@ <Icon icon="arrow-left" style="w-10" /> </button> -{#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> -{/if} - -<div class="container px-4 mt-20 max-w-xl mx-auto"> +<div class="container px-4 max-w-xl mx-auto text-center space-y-5"> {#if amount} - <div class="text-center mb-8"> - <h1 class="text-xl md:text-2xl text-secondary mb-2"> + {#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> + {/if} + + <div> + <h1 class="text-lg text-secondary"> {$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"> + + <h2 class="text-3xl font-semibold"> + {f(fiat(amount, $rate), currency)} + </h2> + <h3 class="text-secondary text-xl">⚡️{s(amount)}</h3> + </div> + + <div> + <div class="text-lg text-secondary"> {$t("payments.to")} - </h1> - <p class="text-4xl break-words">{alias}</p> + </div> + + <p class="text-2xl break-words font-semibold mb-8">{alias}</p> </div> + {#if ourfee} + <div class="text-center"> + <h2 class="text-secondary text-lg">{$t("payments.platformFee")}</h2> + + <div class="flex flex-wrap gap-4 justify-center"> + <div class="my-auto"> + <h2 class="text-xl"> + {f(fiat(ourfee, $rate), currency)} + </h2> + <h3 class="text-secondary">⚡️{s(ourfee)}</h3> + </div> + </div> + </div> + {/if} + <form method="POST" use:enhance @@ -68,13 +114,33 @@ <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="w-40 mx-auto"> + <label for="maxfee" class="text-lg text-secondary" + >{$t("payments.maxfee")}</label + > + + <div class="flex"> + <input + id="maxfee" + name="maxfee" + bind:value={maxfee} + class="border-r-0 rounded-r-none py-2" + /> + <div + class="text-gray-600 rounded-r-2xl p-4 my-auto rounded-l-none rounded border bg-gray-100" + > + ⚡️ + </div> + </div> + </div> <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" + class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-24 mx-auto" disabled={loading} + use:focus > {#if loading} <Spinner /> @@ -84,23 +150,6 @@ </button> </div> </form> - - {#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" @@ -109,7 +158,9 @@ use:enhance > <input type="hidden" value={a} name="amount" /> - <Numpad bind:amount={a} {currency} {rate} /> + <input name="rate" value={$rate} type="hidden" /> + + <Numpad bind:amount={a} {currency} bind:rate={$rate} /> <button type="submit" class="bg-black text-white rounded-xl h-[48px] flex w-full justify-center items-center font-semibold diff --git a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js index 1f3a0420..fe004408 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.server.js @@ -9,24 +9,30 @@ export async function load({ cookies, params, parent }) { let [amount, currency] = params.amount.split("/"); if (subject.username === user?.username) - throw error(500, { message: "Cannot send to self" }); + 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 (!rate) error(500, "Invalid currency symbol"); if (currency) amount = (amount * sats) / rate; let { hash } = await post( "/invoice", { - invoice: { amount, prompt: false, type: types.lightning }, + invoice: { + amount, + currency: currency === subject.currency ? currency : undefined, + prompt: false, + rate: currency === subject.currency ? rate : undefined, + type: types.lightning, + }, user: { username }, }, auth(cookies) ); - throw redirect(307, `/${username}/invoice/${hash}`); + redirect(307, `/${username}/invoice/${hash}`); } return { amount, rate, subject }; @@ -42,6 +48,6 @@ export const actions = { return fail(400, { message: e.message }); } - throw redirect(307, "/sent"); + 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 1ad2c458..533a7039 100644 --- a/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte +++ b/src/routes/(app)/send/user/[username]/[...amount]/+page.svelte @@ -31,6 +31,7 @@ amount = a; amountFiat = parseFloat(af).toFixed(2); rate = fiat ? (sats * amountFiat) / amount : r; + ({ hash } = await post(`/${subject.username}/invoice`, { invoice: { amount, @@ -99,8 +100,8 @@ on:click={setAmount} on:keydown={setAmount} > - Next - </button> + {$t("payments.next")}</button + > {/if} </div> </form> diff --git a/src/routes/(app)/sent/+page.svelte b/src/routes/(app)/sent/+page.svelte index e2d297de..136bd395 100644 --- a/src/routes/(app)/sent/+page.svelte +++ b/src/routes/(app)/sent/+page.svelte @@ -1,5 +1,5 @@ <script> - import { fiat, f, s, sat, sats } from "$lib/utils"; + import { fiat, f, focus, s, sat, sats } from "$lib/utils"; import { scale } from "svelte/transition"; import Icon from "$comp/Icon.svelte"; import { toast } from "@zerodevx/svelte-toast"; @@ -43,7 +43,7 @@ </h3> </div> -<a href={`/${username}/payments`}> +<a href={`/${username}/payments`} use:focus> <div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" /> </a> diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index fd405e93..055c5956 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -16,7 +16,7 @@ <meta property="og:title" content="swapee" /> <meta name="twitter:title" content="swapee" /> - <meta property="og:image" content={`${host}/images/logo.png`} /> + <meta property="og:image" content={`${host}/images/logo.webp`} /> <meta property="og:type" content="website" /> <meta property="og:description" content="An easy to use bitcoin web wallet" /> @@ -24,7 +24,7 @@ <meta name="keywords" content="swapee easy bitcoin web wallet" /> - <meta name="twitter:image" content={`${host}/images/logo.png`} /> + <meta name="twitter:image" content={`${host}/images/logo.webp`} /> <meta name="twitter:card" content="summary_large_image" /> <meta property="og:url" content={host + pathname} /> diff --git a/src/routes/start/+server.js b/src/routes/start/+server.js index e5d93497..babe7db6 100644 --- a/src/routes/start/+server.js +++ b/src/routes/start/+server.js @@ -4,8 +4,8 @@ import { redirect } from "@sveltejs/kit"; export async function GET({ cookies }) { let username = cookies.get("username"); if (username) { - throw redirect(307, `/${username}/receive`); + redirect(307, `/${username}/receive`); } - throw redirect(307, "/"); + redirect(307, "/"); } |
