aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-03 16:08:52 -0800
committerAdam Soltys <[email protected]>2026-03-03 16:08:52 -0800
commit8551df92b96ff69f3cda50184cb714ba30f3803d (patch)
treeb53b414d20d40d9bb9ab33764caa113c5996a262 /src
parent14fff3ba852b1d696f61a0da25aaf0e04ccd4158 (diff)
downloadcoinos-ui-8551df92b96ff69f3cda50184cb714ba30f3803d.tar.gz
coinos-ui-8551df92b96ff69f3cda50184cb714ba30f3803d.zip
get rid of /receive route
Diffstat (limited to 'src')
-rw-r--r--src/components/Account.svelte2
-rw-r--r--src/components/InvoiceData.svelte38
-rw-r--r--src/components/InvoiceTypes.svelte16
-rw-r--r--src/components/SetType.svelte3
-rw-r--r--src/lib/socket.ts1
-rw-r--r--src/routes/(app)/+layout.svelte5
-rw-r--r--src/routes/(app)/[username]/receive/+page.server.js16
-rw-r--r--src/routes/(app)/[username]/receive/+page.svelte187
-rw-r--r--src/routes/(app)/invoice/+page.server.ts12
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte94
-rw-r--r--src/routes/(app)/pay/[username]/[...amount]/+page.server.ts4
-rw-r--r--src/routes/(app)/qr/[text]/raw/+server.ts2
-rw-r--r--src/routes/+layout.ts2
-rw-r--r--src/routes/start/+server.ts2
14 files changed, 118 insertions, 266 deletions
diff --git a/src/components/Account.svelte b/src/components/Account.svelte
index 44b53bdf..0be08a05 100644
--- a/src/components/Account.svelte
+++ b/src/components/Account.svelte
@@ -86,7 +86,7 @@
}
};
- let receiveUrl = $derived(id === user.id ? `/${user.username}/receive` : "/invoice");
+ let receiveUrl = $derived(id === user.id ? "/invoice?lnurl=true" : "/invoice");
let displayType = $derived(
accountType === "ark"
diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte
index 617f6f42..ece30457 100644
--- a/src/components/InvoiceData.svelte
+++ b/src/components/InvoiceData.svelte
@@ -24,31 +24,34 @@
let { memo } = $derived(invoice);
let qrSrc = $derived(`/qr/${encodeURIComponent(invoice.text)}/raw`);
- let visibleSrc = $state("");
- let first = true;
+ let srcs: string[] = $state([]);
$effect(() => {
const next = qrSrc;
- if (first) {
- first = false;
- visibleSrc = next;
- return;
+ if (!srcs.length) {
+ srcs = [next];
+ } else if (next !== srcs[srcs.length - 1]) {
+ srcs = [...srcs, next];
}
- const img = new Image();
- img.onload = () => (visibleSrc = next);
- img.src = next;
});
+
+ let onLoaded = (src: string) => {
+ const idx = srcs.indexOf(src);
+ if (idx > 0) srcs = srcs.slice(idx);
+ };
</script>
{#if showQr}
- <div class="max-w-[280px] w-full mx-auto">
- <a href={link}>
- <img
- src={visibleSrc}
- class="z-10 border-4 border-white"
- alt={txt}
- />
- </a>
+ <div class="max-w-[280px] w-full mx-auto aspect-square relative">
+ {#each srcs as src, i (src)}
+ <a href={link} class="absolute inset-0" style:z-index={i}>
+ <img
+ {src}
+ alt={txt}
+ onload={srcs.length > 1 ? () => onLoaded(src) : undefined}
+ />
+ </a>
+ {/each}
</div>
{/if}
@@ -92,3 +95,4 @@
{#if memo}
<div class="text-xl text-center">{memo}</div>
{/if}
+
diff --git a/src/components/InvoiceTypes.svelte b/src/components/InvoiceTypes.svelte
index b518364d..489fb367 100644
--- a/src/components/InvoiceTypes.svelte
+++ b/src/components/InvoiceTypes.svelte
@@ -2,7 +2,6 @@
import PhLightningFill from "virtual:icons/ph/lightning-fill";
import PhXBold from "virtual:icons/ph/x-bold";
import { types } from "$lib/utils";
- import { page } from "$app/stores";
import { amountPrompt } from "$lib/store";
let {
newAmount = $bindable(),
@@ -14,6 +13,7 @@
setAmount,
setType,
activeOnly = false,
+ lnurlMode = false,
} = $props();
let { aid, address_type, type } = $derived(invoice);
@@ -60,10 +60,9 @@
<div class="flex flex-wrap gap-2">
<button
class="btn flex-nowrap !w-auto grow"
- class:bg-base-300={type === types.lightning && $page.url.pathname.includes("/receive")}
- class:text-secondary={type === types.lightning && $page.url.pathname.includes("/receive")}
- class:hidden={activeOnly &&
- (type !== types.lightning || !$page.url.pathname.includes("/receive"))}
+ class:bg-base-300={lnurlMode}
+ class:text-secondary={lnurlMode}
+ class:hidden={activeOnly && !lnurlMode}
onclick={() => {
if (activeOnly) toggleType();
else {
@@ -97,10 +96,9 @@
<button
class="btn flex-nowrap !w-auto grow"
- class:bg-base-300={type === types.lightning && !$page.url.pathname.includes("/receive")}
- class:text-secondary={type === types.lightning && !$page.url.pathname.includes("/receive")}
- class:hidden={activeOnly &&
- (type !== types.lightning || $page.url.pathname.includes("/receive"))}
+ class:bg-base-300={type === types.lightning && !lnurlMode}
+ class:text-secondary={type === types.lightning && !lnurlMode}
+ class:hidden={activeOnly && !(type === types.lightning && !lnurlMode)}
onclick={() => {
if (activeOnly) toggleType();
else {
diff --git a/src/components/SetType.svelte b/src/components/SetType.svelte
index 99bf676f..334c49ca 100644
--- a/src/components/SetType.svelte
+++ b/src/components/SetType.svelte
@@ -13,6 +13,7 @@
setType,
toggleType,
activeOnly = false,
+ lnurlMode = false,
t,
}: any = $props();
</script>
@@ -20,7 +21,7 @@
{#if settingType}
<div class="fixed inset-0 bg-base-100/90 overflow-y-auto h-full w-full z-50">
<div class="relative mx-auto p-12 max-w-xl shadow-lg rounded-md bg-base-100 space-y-2">
- <InvoiceTypes bind:newAmount {setAmount} {invoice} {user} {hasArk} {setType} {toggleType} {t} />
+ <InvoiceTypes bind:newAmount {setAmount} {invoice} {user} {hasArk} {setType} {toggleType} {lnurlMode} {t} />
</div>
</div>
{/if}
diff --git a/src/lib/socket.ts b/src/lib/socket.ts
index 2262f388..6fbcaeb6 100644
--- a/src/lib/socket.ts
+++ b/src/lib/socket.ts
@@ -64,7 +64,6 @@ export const messages = (data) => ({
if (amount > 0 && !vaultForwarding) {
if (
pathname.includes(`/${username}`) ||
- pathname.includes("/receive") ||
pathname.includes("/invoice")
) {
if (!get(navigating) && iid) {
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index f6c496c4..6cbcad2b 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -172,6 +172,11 @@
onMount(async () => {
if (browser) {
+ if (import.meta.env.DEV && navigator.serviceWorker) {
+ const regs = await navigator.serviceWorker.getRegistrations();
+ for (const reg of regs) reg.unregister();
+ }
+
$offline = !navigator.onLine;
const goOnline = () => ($offline = false);
const goOffline = () => ($offline = true);
diff --git a/src/routes/(app)/[username]/receive/+page.server.js b/src/routes/(app)/[username]/receive/+page.server.js
deleted file mode 100644
index 2aa875dd..00000000
--- a/src/routes/(app)/[username]/receive/+page.server.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import getRates from "$lib/rates";
-import { redirect } from "@sveltejs/kit";
-
-export const load = async ({ cookies, params: { username }, parent, url }) => {
- const token = cookies.get("token");
- const rates = await getRates();
- if (!token) redirect(307, `/pay/${username}`);
- const { subject, user } = await parent();
- const aid = cookies.get("aid");
-
- if (aid && user?.id !== aid) redirect(307, "/invoice");
- return {
- rate: rates[subject.currency],
- text: `${username}@${url.host}`,
- };
-};
diff --git a/src/routes/(app)/[username]/receive/+page.svelte b/src/routes/(app)/[username]/receive/+page.svelte
deleted file mode 100644
index 10aea0ef..00000000
--- a/src/routes/(app)/[username]/receive/+page.svelte
+++ /dev/null
@@ -1,187 +0,0 @@
-<script lang="ts">
- import { run } from "svelte/legacy";
- import { btc, loc, post, copy, fail, f, get, types, sat, s, sats } from "$lib/utils";
- import { tick, onMount, onDestroy } from "svelte";
- import { browser } from "$app/environment";
- import { last, lastPayment, showQr, amountPrompt } from "$lib/store";
- import Avatar from "$comp/Avatar.svelte";
- import InvoiceData from "$comp/InvoiceData.svelte";
- import InvoiceActions from "$comp/InvoiceActions.svelte";
- import SetAmount from "$comp/SetAmount.svelte";
- import SetMemo from "$comp/SetMemo.svelte";
- import SetType from "$comp/SetType.svelte";
- import { t } from "$lib/translations";
- import { goto, invalidate } from "$app/navigation";
- import { page } from "$app/stores";
-
- let { data }: any = $props();
-
- let showOptions;
-
- let id = $state();
- let { rate, subject, user, text, hasArk } = $derived(data);
- let locale = $derived(loc(user));
-
- let { currency, username } = $derived(subject);
- let type = types.lightning;
- let hash = "";
- let qr;
-
- let aid = $derived(subject.id);
- let invoice: any = $derived({
- aid,
- type: types.lightning,
- items: [],
- rate,
- text,
- uid: subject.id,
- user: subject,
- });
- let amount = $state(),
- amountFiat,
- tip;
-
- let update = async () => {
- try {
- ({ id } = await post(`/invoice`, {
- invoice,
- user: { username, currency },
- }));
-
- let url = `/invoice/${id}`;
- if (subject?.prompt) url += "/tip";
- else url += "?options=true";
-
- goto(url, { invalidateAll: true, noScroll: true });
- } catch (e: any) {
- fail(e.message);
- }
- };
-
- let settingType = $state();
- let toggleType = () => (settingType = !settingType);
- let setType = async (type, address_type) => {
- $showQr = true;
- if (type === types.lightning && !amount && typeof newAmount === "undefined")
- goto(`/${username}/receive`, { invalidateAll: true, noScroll: true });
- else {
- if (typeof newAmount !== "undefined") invoice.amount = newAmount;
- invoice.address_type = address_type;
- invoice.type = type;
- await update();
- }
- settingType = false;
- };
-
- let setAmount = async (e) => {
- e?.preventDefault();
- e?.stopPropagation();
-
- settingAmount = false;
-
- if (typeof $amountPrompt === "undefined") $amountPrompt = true;
- amount = newAmount;
- invoice.amount = newAmount;
- invoice.tip = null;
-
- await update();
- };
-
- let newAmount = $state(0);
- let settingAmount = $state();
- let fiat = $state(true);
- let toggleAmount = () => (settingAmount = !settingAmount);
-
- let setMemo = async (e) => {
- e.preventDefault();
- e.stopPropagation();
-
- settingMemo = false;
- invoice.memo = memo;
- await update();
- };
-
- let memo = $state();
- let settingMemo = $state();
- let toggleMemo = () => (settingMemo = !settingMemo);
-
- let link = $derived([types.bitcoin, types.liquid].includes(type) ? text : `lightning:${text}`);
- let txt = $derived([types.bitcoin, types.liquid].includes(type) ? hash : text);
-
- onMount(() => {
- $lastPayment = null;
- if ($amountPrompt && !amount) toggleAmount();
- let address_type = $page.url.searchParams.get("address_type");
- if (address_type) setType(types.bitcoin, address_type);
- });
-
- $effect(() => {
- if ($lastPayment?.iid) {
- const { iid, confirmed } = $lastPayment;
- const target = confirmed ? `/invoice/${iid}/paid` : `/invoice/${iid}`;
- $lastPayment = null;
- goto(target);
- }
- });
-</script>
-
-<div class="invoice container mx-auto max-w-xl px-4 space-y-2">
- <InvoiceData
- {locale}
- {link}
- {qr}
- {txt}
- {invoice}
- {amount}
- {amountFiat}
- {currency}
- {tip}
- {rate}
- showQr={$showQr}
- t={$t}
- />
-
- <InvoiceActions
- bind:newAmount
- {setAmount}
- {toggleType}
- {setType}
- {toggleAmount}
- {toggleMemo}
- {user}
- {invoice}
- {copy}
- {link}
- {showQr}
- {txt}
- t={$t}
- />
-</div>
-
-<SetAmount
- bind:newAmount
- bind:fiat
- {currency}
- locale={loc(user)}
- {rate}
- {settingAmount}
- {setAmount}
- {toggleAmount}
- t={$t}
-/>
-
-<SetMemo bind:memo {settingMemo} {setMemo} {toggleMemo} t={$t} />
-
-<SetType
- {aid}
- {invoice}
- {user}
- {hasArk}
- {type}
- {settingType}
- {setType}
- {toggleType}
- t={$t}
- bind:newAmount
- {setAmount}
-/>
diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts
index 92cfd18d..e3d8125f 100644
--- a/src/routes/(app)/invoice/+page.server.ts
+++ b/src/routes/(app)/invoice/+page.server.ts
@@ -3,16 +3,20 @@ import type { Invoice } from "$lib/types";
import { auth, post } from "$lib/utils";
import { error, redirect } from "@sveltejs/kit";
-export const load = async ({ cookies, parent }) => {
+export const load = async ({ cookies, parent, url }) => {
const aid = cookies.get("aid");
let { subject, user } = await parent();
const rates = await getRates();
- if (aid === user.id) redirect(307, `/${user.username}/receive`);
+
+ const type = url.searchParams.get("type") || undefined;
+ const address_type = url.searchParams.get("address_type") || undefined;
let invoice: Invoice = {
aid: aid || "",
rate: rates[user?.currency || subject?.currency],
+ ...(type && { type }),
+ ...(address_type && { address_type }),
};
if (!user) user = subject;
@@ -26,7 +30,9 @@ export const load = async ({ cookies, parent }) => {
const { id } = invoice;
+ const lnurl = url.searchParams.has("lnurl");
+
if (invoice.memoPrompt && !invoice.memo) {
redirect(307, `/invoice/${id}/memo`);
- } else redirect(307, `/invoice/${id}`);
+ } else redirect(307, `/invoice/${id}${lnurl ? "?lnurl=true" : ""}`);
};
diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte
index 9dac7a7c..6405bd6f 100644
--- a/src/routes/(app)/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/+page.svelte
@@ -5,7 +5,8 @@
import { btc, loc, post, copy, f, fail, success, get, types, sat, s, sats } from "$lib/utils";
import { tick, onMount, onDestroy } from "svelte";
import { browser } from "$app/environment";
- import { last, showQr, amountPrompt } from "$lib/store";
+ import { last, lastPayment, showQr, amountPrompt } from "$lib/store";
+ import { page } from "$app/stores";
import Avatar from "$comp/Avatar.svelte";
import InvoiceData from "$comp/InvoiceData.svelte";
import InvoiceActions from "$comp/InvoiceActions.svelte";
@@ -37,6 +38,18 @@
let { username, currency } = $derived(invoice.user);
let locale = $derived(loc(user));
+ let lnurlMode = $state(false);
+ let lnAddress = $derived(`${username}@${$page.url.host}`);
+
+ $effect(() => {
+ if ($page.url.searchParams.has("lnurl")) {
+ lnurlMode = true;
+ $showQr = true;
+ } else {
+ lnurlMode = false;
+ }
+ });
+
let tipPercent = $derived(tip ? (tip / amount) * 100 : 0);
$effect(() => {
@@ -67,14 +80,14 @@
if ($amountPrompt && !amount) toggleAmount();
});
- let update = async () => {
+ let update = async (changes: any = {}) => {
try {
- ({ id } = await post(`/invoice`, {
- invoice,
+ const result = await post(`/invoice`, {
+ invoice: { ...$state.snapshot(invoice), ...changes },
user: { username, currency },
- }));
+ });
- let url = `/invoice/${id}`;
+ let url = `/invoice/${result.id}`;
if (subject?.prompt) url += "/tip";
else url += "?options=true";
@@ -88,20 +101,21 @@
let toggleType = () => (settingType = !settingType);
let setType = async (type, address_type) => {
$showQr = true;
- if (type === types.lightning && !amount && typeof newAmount === "undefined")
- goto(`/${username}/receive`, { invalidateAll: true, noScroll: true });
- else {
- if (typeof newAmount !== "undefined") invoice.amount = newAmount;
- invoice.address_type = address_type;
- invoice.type = type;
+ if (type === types.lightning && !amount && typeof newAmount === "undefined") {
+ lnurlMode = true;
+ settingType = false;
+ return;
+ }
- if (type === "ark" && invoice.aid !== invoice.uid) {
- const wallet = await getWallet();
- invoice.hash = await wallet.getAddress();
- }
+ const changes: any = { type, address_type };
+ if (typeof newAmount !== "undefined") changes.amount = newAmount;
- await update();
+ if (type === "ark" && invoice.aid !== invoice.uid) {
+ const wallet = await getWallet();
+ changes.hash = await wallet.getAddress();
}
+
+ await update(changes);
settingType = false;
};
@@ -116,10 +130,10 @@
settingAmount = false;
if (typeof $amountPrompt === "undefined") $amountPrompt = true;
- invoice.amount = newAmount;
- invoice.tip = 0;
+ const changes: any = { amount: newAmount, tip: 0 };
+ if (lnurlMode) changes.type = types.lightning;
- await update();
+ await update(changes);
};
let setMemo = async (e) => {
@@ -127,8 +141,7 @@
e.stopPropagation();
settingMemo = false;
- invoice.memo = memo;
- await update();
+ await update({ memo });
};
let settingMemo = $state();
@@ -137,9 +150,37 @@
let fiat = $state(true);
let amountFiat = $derived(parseFloat(((amount * rate) / sats).toFixed(2)));
let tipAmount = $derived(((tip * rate) / sats).toFixed(2));
- let link = $derived([types.bitcoin, types.liquid].includes(type) ? text : `lightning:${text}`);
- let txt = $derived([types.bitcoin, types.liquid].includes(type) ? hash : text);
+ let displayInvoice = $derived(
+ lnurlMode
+ ? { ...invoice, text: lnAddress, type: types.lightning, items: [] }
+ : invoice,
+ );
+
+ let link = $derived(
+ lnurlMode
+ ? `lightning:${lnAddress}`
+ : [types.bitcoin, types.liquid].includes(type)
+ ? text
+ : `lightning:${text}`,
+ );
+
+ let txt = $derived(
+ lnurlMode
+ ? lnAddress
+ : [types.bitcoin, types.liquid].includes(type)
+ ? hash
+ : text,
+ );
+
+ $effect(() => {
+ if (lnurlMode && $lastPayment?.iid) {
+ const { iid, confirmed } = $lastPayment;
+ const target = confirmed ? `/invoice/${iid}/paid` : `/invoice/${iid}`;
+ $lastPayment = null;
+ goto(target);
+ }
+ });
</script>
<div class="invoice container mx-auto max-w-xl px-4 pb-4 space-y-2">
@@ -147,7 +188,7 @@
{src}
{link}
{txt}
- {invoice}
+ invoice={displayInvoice}
{amount}
{amountFiat}
{currency}
@@ -166,7 +207,7 @@
{toggleAmount}
{toggleMemo}
{user}
- {invoice}
+ invoice={displayInvoice}
{copy}
{link}
{txt}
@@ -200,5 +241,6 @@
{settingType}
{setType}
{toggleType}
+ {lnurlMode}
t={$t}
/>
diff --git a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
index ca0f382d..75cdac63 100644
--- a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
+++ b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
@@ -25,7 +25,7 @@ export async function load({ cookies, params, parent }) {
redirect(307, `/send/ark/${serverAddr}`);
}
}
- redirect(307, `/${username}/receive`);
+ redirect(307, `/invoice`);
}
const subject = await get(`/users/${username}`);
@@ -84,7 +84,7 @@ export const actions = {
}
if (parseInt(amount) > 0 && prompt) redirect(307, `/invoice/${id}/tip`);
- else if (parseInt(amount) <= 0) redirect(307, `/${username}/receive`);
+ else if (parseInt(amount) <= 0) redirect(307, `/invoice/${id}`);
redirect(307, `/invoice/${id}`);
},
};
diff --git a/src/routes/(app)/qr/[text]/raw/+server.ts b/src/routes/(app)/qr/[text]/raw/+server.ts
index 77bb8a09..baac03e5 100644
--- a/src/routes/(app)/qr/[text]/raw/+server.ts
+++ b/src/routes/(app)/qr/[text]/raw/+server.ts
@@ -3,7 +3,7 @@ import QRCode from "qrcode";
export async function GET({ params }) {
const { text } = params;
- const buffer = await QRCode.toBuffer(text, { width: 600, type: "png" });
+ const buffer = await QRCode.toBuffer(text, { width: 600, type: "png", margin: 1 });
return new Response(buffer, {
headers: {
diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts
index 56a0cc14..b2b71c26 100644
--- a/src/routes/+layout.ts
+++ b/src/routes/+layout.ts
@@ -21,7 +21,7 @@ export const load = async ({ data }) => {
setCookie("theme", theme, 21000000);
- if (theme === "dark") setTimeout(() => themeStore.set(theme), 100);
+ if (theme === "dark") setTimeout(() => themeStore.set(theme!), 100);
}
}
diff --git a/src/routes/start/+server.ts b/src/routes/start/+server.ts
index d31d1b3d..2d1319ec 100644
--- a/src/routes/start/+server.ts
+++ b/src/routes/start/+server.ts
@@ -5,7 +5,7 @@ export async function GET({ cookies }) {
const username = cookies.get("username");
if (username) {
if (pathname) redirect(307, pathname);
- else redirect(307, `/${username}/receive`);
+ else redirect(307, `/${username}`);
}
redirect(307, "/");