aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-01-02 20:21:20 -0800
committerAdam Soltys <[email protected]>2024-01-02 20:21:20 -0800
commita20ade414740804385ba05e6028f53b670e0236d (patch)
tree493ac4e50319121c1e35a67243a6b11560b17910 /src
parentea8944bd05f3cdb83eab52b8abdb2351ce68bda1 (diff)
parentae1c0add85daeae8bc44c34f9559bcd50c56d873 (diff)
downloadcoinos-ui-a20ade414740804385ba05e6028f53b670e0236d.tar.gz
coinos-ui-a20ade414740804385ba05e6028f53b670e0236d.zip
Merge branch 'master' into swapee
Diffstat (limited to 'src')
-rw-r--r--src/components/AppHeader.svelte5
-rw-r--r--src/components/Lnurlp.svelte5
-rw-r--r--src/components/Numpad.svelte14
-rw-r--r--src/components/Popup.svelte2
-rw-r--r--src/components/Qr.svelte2
-rw-r--r--src/components/Toggle.svelte2
-rw-r--r--src/components/index.js0
-rw-r--r--src/lib/invoice.js9
-rw-r--r--src/lib/samples.js8
-rw-r--r--src/lib/utils.js33
-rw-r--r--src/locales/en.json7
-rw-r--r--src/routes/(app)/+layout.server.js11
-rw-r--r--src/routes/(app)/+layout.svelte14
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte40
-rw-r--r--src/routes/(app)/[username]/(profile)/event/[id]/+page.server.js4
-rw-r--r--src/routes/(app)/[username]/(profile)/request/+page.server.js2
-rw-r--r--src/routes/(app)/[username]/+layout.server.js2
-rw-r--r--src/routes/(app)/[username]/address/+page.server.js2
-rw-r--r--src/routes/(app)/[username]/card/[serial]/+page.server.js2
-rw-r--r--src/routes/(app)/[username]/generate/+page.svelte14
-rw-r--r--src/routes/(app)/[username]/generate/+server.js2
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/+layout.server.js11
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/+page.svelte141
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte23
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/tip/+page.server.js2
-rw-r--r--src/routes/(app)/[username]/payments/[...page]/+page.svelte16
-rw-r--r--src/routes/(app)/[username]/receive/[...param]/+page.svelte3
-rw-r--r--src/routes/(app)/[username]/request/[id]/+page.server.js6
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.server.js10
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/+page.svelte31
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_account.svelte16
-rw-r--r--src/routes/(app)/[username]/settings/[...tab]/_pos.svelte20
-rw-r--r--src/routes/(app)/[username]/verify/[code]/+page.server.js8
-rw-r--r--src/routes/(app)/admin/+layout.server.js4
-rw-r--r--src/routes/(app)/admin/+page.server.js2
-rw-r--r--src/routes/(app)/buy/+page.server.js2
-rw-r--r--src/routes/(app)/disable2fa/+server.js2
-rw-r--r--src/routes/(app)/forgot/+page.server.js14
-rw-r--r--src/routes/(app)/forgot/+page.svelte77
-rw-r--r--src/routes/(app)/fund/[id]/+page.server.js4
-rw-r--r--src/routes/(app)/fund/[id]/sweep/+page.server.js4
-rw-r--r--src/routes/(app)/fund/[id]/withdraw/+page.server.js4
-rw-r--r--src/routes/(app)/launch/purchase/+page.server.js49
-rw-r--r--src/routes/(app)/launch/purchase/card/+page.server.js51
-rw-r--r--src/routes/(app)/ln/[lnurl]/+page.server.js10
-rw-r--r--src/routes/(app)/login/+page.server.js4
-rw-r--r--src/routes/(app)/login/+page.svelte176
-rw-r--r--src/routes/(app)/logout/+page.server.js2
-rw-r--r--src/routes/(app)/payment/[id]/+page.svelte57
-rw-r--r--src/routes/(app)/qr/[text]/+page.svelte4
-rw-r--r--src/routes/(app)/redeem/[redeemcode]/+page.server.js6
-rw-r--r--src/routes/(app)/register/+page.server.js5
-rw-r--r--src/routes/(app)/reset/[code]/+page.server.js30
-rw-r--r--src/routes/(app)/reset/[code]/+page.svelte86
-rw-r--r--src/routes/(app)/send/[...text]/+page.server.js28
-rw-r--r--src/routes/(app)/send/[...text]/+page.svelte29
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+page.svelte61
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.server.js32
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[...amount]/+page.svelte163
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.js30
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte146
-rw-r--r--src/routes/(app)/send/fund/[name]/[...amount]/+page.server.js2
-rw-r--r--src/routes/(app)/send/invoice/[id]/+page.server.js7
-rw-r--r--src/routes/(app)/send/invoice/[id]/+page.svelte19
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.server.js6
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.svelte135
-rw-r--r--src/routes/(app)/send/user/[username]/[...amount]/+page.server.js16
-rw-r--r--src/routes/(app)/send/user/[username]/[...amount]/+page.svelte5
-rw-r--r--src/routes/(app)/sent/+page.svelte4
-rw-r--r--src/routes/+layout.svelte4
-rw-r--r--src/routes/start/+server.js4
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]} &mdash; ⚡️{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, "/");
}