aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-09-24 20:12:43 -0700
committerAdam Soltys <[email protected]>2024-09-24 20:12:43 -0700
commitd49d4119811dee9270f0858f3f03e5d8cd0d95ba (patch)
tree42101a3ccf47f7b7290637700b90a843476e56f7 /src/routes/(app)
parent20c0c52f8b850bfbab490735b0b5cd5d08851488 (diff)
parent9531ccbdf01b07c392b1b5cb6d8ad4680ee29d2c (diff)
downloadcoinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.tar.gz
coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.zip
Merge branch 'self-custody'
Diffstat (limited to 'src/routes/(app)')
-rw-r--r--src/routes/(app)/+layout.server.ts25
-rw-r--r--src/routes/(app)/+layout.svelte54
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte70
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.server.ts6
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte258
-rw-r--r--src/routes/(app)/[username]/+layout.server.ts11
-rw-r--r--src/routes/(app)/[username]/+layout.svelte50
-rw-r--r--src/routes/(app)/[username]/address/+page.server.ts9
-rw-r--r--src/routes/(app)/[username]/card/[serial]/+page.server.ts16
-rw-r--r--src/routes/(app)/[username]/card/[serial]/+page.svelte36
-rw-r--r--src/routes/(app)/[username]/customers/+page.svelte0
-rw-r--r--src/routes/(app)/[username]/funds/+page.server.ts0
-rw-r--r--src/routes/(app)/[username]/funds/+page.svelte52
-rw-r--r--src/routes/(app)/[username]/invoice/[id]/+layout.server.ts39
-rw-r--r--src/routes/(app)/[username]/map/+page.server.ts12
-rw-r--r--src/routes/(app)/[username]/map/+page.svelte8
-rw-r--r--src/routes/(app)/[username]/receive/+page.svelte343
-rw-r--r--src/routes/(app)/[username]/receive/[...param]/+page.server.ts20
-rw-r--r--src/routes/(app)/[username]/receive/[...param]/+page.svelte116
-rw-r--r--src/routes/(app)/[username]/reset/+server.ts10
-rw-r--r--src/routes/(app)/account/[id]/+page.server.ts25
-rw-r--r--src/routes/(app)/account/[id]/+page.svelte94
-rw-r--r--src/routes/(app)/account/cash/+page.svelte37
-rw-r--r--src/routes/(app)/account/import/+page.svelte55
-rw-r--r--src/routes/(app)/account/mints/+page.svelte1
-rw-r--r--src/routes/(app)/account/pass/+page.svelte149
-rw-r--r--src/routes/(app)/account/savings/+page.svelte31
-rw-r--r--src/routes/(app)/account/seed/+page.svelte76
-rw-r--r--src/routes/(app)/account/setup/+page.svelte34
-rw-r--r--src/routes/(app)/admin/+page.svelte2
-rw-r--r--src/routes/(app)/ecash/[id]/[...version]/+layout.svelte1
-rw-r--r--src/routes/(app)/fund/[id]/+page.server.ts2
-rw-r--r--src/routes/(app)/fund/[id]/sweep/+page.svelte2
-rw-r--r--src/routes/(app)/generate/+page.svelte (renamed from src/routes/(app)/[username]/generate/+page.svelte)2
-rw-r--r--src/routes/(app)/generate/+server.ts (renamed from src/routes/(app)/[username]/generate/+server.ts)0
-rw-r--r--src/routes/(app)/invoice/+page.server.ts28
-rw-r--r--src/routes/(app)/invoice/+page.svelte (renamed from src/routes/(app)/[username]/address/+page.svelte)0
-rw-r--r--src/routes/(app)/invoice/+server.ts (renamed from src/routes/(app)/[username]/invoice/+server.ts)0
-rw-r--r--src/routes/(app)/invoice/[id]/+layout.server.ts38
-rw-r--r--src/routes/(app)/invoice/[id]/+layout.svelte (renamed from src/routes/(app)/[username]/invoice/[id]/+layout.svelte)0
-rw-r--r--src/routes/(app)/invoice/[id]/+page.server.ts (renamed from src/routes/(app)/[username]/invoice/[id]/+page.server.ts)0
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte (renamed from src/routes/(app)/[username]/invoice/[id]/+page.svelte)149
-rw-r--r--src/routes/(app)/invoice/[id]/memo/+page.server.ts (renamed from src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts)2
-rw-r--r--src/routes/(app)/invoice/[id]/memo/+page.svelte (renamed from src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte)0
-rw-r--r--src/routes/(app)/invoice/[id]/paid/+page.svelte (renamed from src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte)2
-rw-r--r--src/routes/(app)/invoice/[id]/tip/+page.server.ts (renamed from src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts)4
-rw-r--r--src/routes/(app)/invoice/[id]/tip/+page.svelte (renamed from src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte)3
-rw-r--r--src/routes/(app)/invoice/sent/+page.svelte (renamed from src/routes/(app)/[username]/invoice/sent/+page.svelte)0
-rw-r--r--src/routes/(app)/items/[id]/+page.server.ts (renamed from src/routes/(app)/[username]/items/[id]/+page.server.ts)0
-rw-r--r--src/routes/(app)/items/[id]/+page.svelte (renamed from src/routes/(app)/[username]/items/[id]/+page.svelte)0
-rw-r--r--src/routes/(app)/items/create/+page.server.ts (renamed from src/routes/(app)/[username]/items/create/+page.server.ts)0
-rw-r--r--src/routes/(app)/items/create/+page.svelte (renamed from src/routes/(app)/[username]/items/create/+page.svelte)0
-rw-r--r--src/routes/(app)/ln/[lnurl]/+page.server.ts2
-rw-r--r--src/routes/(app)/ln/withdrawal/sent/+page.svelte2
-rw-r--r--src/routes/(app)/login/+page.server.ts29
-rw-r--r--src/routes/(app)/login/+page.svelte188
-rw-r--r--src/routes/(app)/logout/+page.server.ts1
-rw-r--r--src/routes/(app)/messages/+page.server.ts (renamed from src/routes/(app)/[username]/messages/+page.server.ts)0
-rw-r--r--src/routes/(app)/messages/+page.svelte (renamed from src/routes/(app)/[username]/messages/+page.svelte)2
-rw-r--r--src/routes/(app)/messages/[recipient]/+page.server.ts (renamed from src/routes/(app)/[username]/messages/[recipient]/+page.server.ts)0
-rw-r--r--src/routes/(app)/messages/[recipient]/+page.svelte (renamed from src/routes/(app)/[username]/messages/[recipient]/+page.svelte)0
-rw-r--r--src/routes/(app)/pay/[username]/[...amount]/+page.server.ts7
-rw-r--r--src/routes/(app)/pay/[username]/[...amount]/+page.svelte7
-rw-r--r--src/routes/(app)/payment/[id]/+page.svelte53
-rw-r--r--src/routes/(app)/payments/[...page]/+page.server.ts (renamed from src/routes/(app)/[username]/payments/[...page]/+page.server.ts)5
-rw-r--r--src/routes/(app)/payments/[...page]/+page.svelte (renamed from src/routes/(app)/[username]/payments/[...page]/+page.svelte)63
-rw-r--r--src/routes/(app)/payments/[...page]/+server.ts (renamed from src/routes/(app)/[username]/payments/[...page]/+server.ts)0
-rw-r--r--src/routes/(app)/qr/[text]/+page.svelte31
-rw-r--r--src/routes/(app)/qr/create/+page.svelte60
-rw-r--r--src/routes/(app)/receive/[...token]/+page.svelte14
-rw-r--r--src/routes/(app)/register/+page.server.ts41
-rw-r--r--src/routes/(app)/register/+page.svelte314
-rw-r--r--src/routes/(app)/scan/+page.svelte4
-rw-r--r--src/routes/(app)/send/[...text]/+page.svelte12
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+layout.server.ts23
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+page.server.ts7
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+page.svelte10
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts28
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte99
-rw-r--r--src/routes/(app)/send/ecash/+page.svelte19
-rw-r--r--src/routes/(app)/send/invoice/[id]/+page.server.ts13
-rw-r--r--src/routes/(app)/send/invoice/[id]/+page.svelte8
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.server.ts5
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.svelte17
-rw-r--r--src/routes/(app)/sent/+page.server.ts6
-rw-r--r--src/routes/(app)/sent/[id]/+page.server.ts7
-rw-r--r--src/routes/(app)/sent/[id]/+page.svelte (renamed from src/routes/(app)/sent/+page.svelte)9
-rw-r--r--src/routes/(app)/settings/[...tab]/+page.server.ts (renamed from src/routes/(app)/[username]/settings/[...tab]/+page.server.ts)0
-rw-r--r--src/routes/(app)/settings/[...tab]/+page.svelte (renamed from src/routes/(app)/[username]/settings/[...tab]/+page.svelte)12
-rw-r--r--src/routes/(app)/settings/[...tab]/_account.svelte (renamed from src/routes/(app)/[username]/settings/[...tab]/_account.svelte)0
-rw-r--r--src/routes/(app)/settings/[...tab]/_pos.svelte (renamed from src/routes/(app)/[username]/settings/[...tab]/_pos.svelte)23
-rw-r--r--src/routes/(app)/settings/[...tab]/_security.svelte (renamed from src/routes/(app)/[username]/settings/[...tab]/_security.svelte)0
-rw-r--r--src/routes/(app)/settings/[...tab]/_shopify.svelte (renamed from src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte)0
-rw-r--r--src/routes/(app)/support/+page.svelte139
-rw-r--r--src/routes/(app)/support/+server.ts9
-rw-r--r--src/routes/(app)/verify/[code]/+page.server.ts (renamed from src/routes/(app)/[username]/verify/[code]/+page.server.ts)2
-rw-r--r--src/routes/(app)/verify/[code]/+page.svelte (renamed from src/routes/(app)/[username]/verify/[code]/+page.svelte)0
97 files changed, 1385 insertions, 1758 deletions
diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts
index 655a9425..0901cb5d 100644
--- a/src/routes/(app)/+layout.server.ts
+++ b/src/routes/(app)/+layout.server.ts
@@ -1,8 +1,9 @@
-import { redirect } from "@sveltejs/kit";
- import { get, auth, protectedRoutes } from "$lib/utils";
+import { error, redirect } from "@sveltejs/kit";
+import { get, auth, protectedRoutes } from "$lib/utils";
export async function load({ cookies, request, url, params }) {
let { pathname } = url;
+ let { username } = params;
let token = cookies.get("token");
let rate,
rates = { USD: 1 };
@@ -14,6 +15,18 @@ export async function load({ cookies, request, url, params }) {
} catch (e) {}
}
+ let subject;
+ if (username) {
+ try {
+ subject = await get(`/users/${username}`);
+ } catch (e) {
+ error(500, "Unable to retrieve user account data");
+ }
+ }
+
+ if (!subject) subject = user;
+ if (!user) user = subject;
+
if (user?.needsMigration) {
redirect(307, `/migrate`);
}
@@ -26,14 +39,12 @@ export async function load({ cookies, request, url, params }) {
redirect(307, `/${user.username}`);
}
- if (
- protectedRoutes.find((p) => pathname.match(p)) &&
- (!user || user.username !== params.username)
- ) {
+ if (protectedRoutes.find((p) => pathname.match(p)) && !user) {
redirect(307, "/login");
}
if (user && !user.pubkey && !pathname.includes("generate")) {
+ console.log("user missing pubkey", user);
redirect(303, `/${user.username}/generate`);
}
@@ -44,5 +55,5 @@ export async function load({ cookies, request, url, params }) {
console.log(e);
}
- return { user, token, rate, rates };
+ return { user, token, rate, rates, subject };
}
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index 8d4e2940..db14b046 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -14,26 +14,15 @@
import { page } from "$app/stores";
import { browser } from "$app/environment";
import LoadingSplash from "$comp/LoadingSplash.svelte";
+ import AppHeader from "$comp/AppHeader.svelte";
import Invoice from "$comp/Invoice.svelte";
import Password from "$comp/Password.svelte";
- import Request from "$comp/Request.svelte";
import { getCookie, warning, protectedRoutes } from "$lib/utils";
import { t, locale, loading } from "$lib/translations";
import { goto, onNavigate } from "$app/navigation";
export let data;
- onNavigate((navigation) => {
- if (!document.startViewTransition) return;
-
- return new Promise((resolve) => {
- document.startViewTransition(async () => {
- resolve();
- await navigation.complete;
- });
- });
- });
-
let { generate, rate, user, subject, token, rates } = data;
$: update(data);
@@ -115,6 +104,7 @@
<SvelteToast options={{ reversed: true, intro: { y: 192 } }} />
<main class="pb-20">
+ <AppHeader {user} {subject} />
{#if !$loading}
<slot />
{/if}
@@ -123,43 +113,3 @@
{#if $invoice}
<Invoice {user} />
{/if}
-
-{#if $request}
- <Request {user} />
-{/if}
-
-<style>
- @keyframes fade-in {
- from {
- opacity: 0;
- }
- }
-
- @keyframes fade-out {
- to {
- opacity: 0;
- }
- }
-
- @keyframes slide-from-right {
- from {
- transform: translateX(60px);
- }
- }
-
- @keyframes slide-to-left {
- to {
- transform: translateX(-60px);
- }
- }
-
- :root::view-transition-old(root) {
- animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
- 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
- }
-
- :root::view-transition-new(root) {
- animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in,
- 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
- }
-</style>
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte
index a213e792..30a6e325 100644
--- a/src/routes/(app)/[username]/(profile)/+layout.svelte
+++ b/src/routes/(app)/[username]/(profile)/+layout.svelte
@@ -82,13 +82,12 @@
<div class="container mx-auto w-full px-4 flex flex-wrap lg:flex-nowrap">
<div class="hidden lg:block lg:w-[280px] xl:w-[360px]" />
<div
- class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-5 left-20 mx-auto mb-5"
+ class="w-full lg:w-[280px] xl:w-[360px] lg:absolute space-y-2 left-20 mx-auto"
>
- <div
- class="flex text-3xl font-bold text-center mx-auto justify-center gap-2"
- >
- <div class="my-auto break-words w-full">{display || username}</div>
- </div>
+ <button type="button" on:click={toggleDetails} class="flex text-3xl font-bold text-center mx-auto justify-center gap-2">
+ <div class="my-auto break-words">{display || username}</div>
+ <Icon icon="qr" style="w-8 invert my-auto" />
+ </button>
{#if subject.address}
<div
@@ -112,68 +111,51 @@
<span class="text-secondary">{$t("user.followers")}</span></a
>
</div>
-
- <button
- type="button"
- on:click={toggleDetails}
- class="flex text-base text-secondary mx-auto"
- >
- <div class="mt-1">
- {$t("user.moreDetails")}
- </div>
- <div
- class:-rotate-90={showDetails}
- class:rotate-180={!showDetails}
- class="my-auto"
- >
- <Icon icon="chevron-left" style="w-6 mr-2" />
- </div>
- </button>
</div>
{#if showDetails}
- <div class="text-xl space-y-5">
+ <div class="space-y-5 pb-20">
<div>
- <div class="font-bold">{$t("user.profilePage")}</div>
+ <div class="text-secondary">{$t("user.lightningAddress")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {profile}
+ <div class="break-all grow text-xl">
+ {lnaddr}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(profile)}
+ <button class="my-auto" on:click={() => copy(lnaddr)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a
- href={`/qr/${encodeURIComponent(
- `${$page.url.protocol}//${profile}`
- )}`}
- class="my-auto"
- >
+ <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.lightningAddress")}</div>
+ <div class="text-secondary">{$t("user.url")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
- {lnaddr}
+ <div class="break-all grow text-xl">
+ {profile}
</div>
<div class="flex mb-auto gap-1">
- <button class="my-auto" on:click={() => copy(lnaddr)}
+ <button class="my-auto" on:click={() => copy(profile)}
><Icon icon="copy" style="max-w-max w-8 min-w-[32px]" /></button
>
- <a href={`/qr/${encodeURIComponent(lnaddr)}`} class="my-auto">
+ <a
+ href={`/qr/${encodeURIComponent(
+ `${$page.url.protocol}//${profile}`
+ )}`}
+ class="my-auto"
+ >
<Icon icon="qr" style="invert max-w-max min-w-[32px]" />
</a>
</div>
</div>
</div>
<div>
- <div class="font-bold">{$t("user.nostrPubkey")}</div>
+ <div class="text-secondary">{$t("user.nostrPubkey")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{npub}
</div>
<div class="flex my-auto gap-1">
@@ -188,9 +170,9 @@
</div>
{#if user.id === subject.id}
<div>
- <div class="font-bold">{$t("user.nwc")}</div>
+ <div class="text-secondary">{$t("user.nwc")}</div>
<div class="flex gap-4">
- <div class="break-all grow text-secondary">
+ <div class="break-all grow text-xl">
{nwc}
</div>
<div class="flex my-auto gap-1">
@@ -237,7 +219,7 @@
{#if user && user.username !== subject.username && subject.pubkey}
<a
- href={`/${user.username}/messages/${subject.username}`}
+ href={`/messages/${subject.username}`}
class="contents"
>
<button
diff --git a/src/routes/(app)/[username]/(profile)/+page.server.ts b/src/routes/(app)/[username]/(profile)/+page.server.ts
index 59eb44d5..ed4ee58c 100644
--- a/src/routes/(app)/[username]/(profile)/+page.server.ts
+++ b/src/routes/(app)/[username]/(profile)/+page.server.ts
@@ -1,11 +1,15 @@
import { auth, get } from "$lib/utils";
export async function load({ cookies, depends, parent }) {
+ depends("app:payments");
depends("app:items");
let { subject } = await parent();
+
+ let accounts = await get("/accounts", auth(cookies));
+
let items = await get(`/${subject.id}/items`, auth(cookies));
items.map((i) => (i.quantity = 0));
- return { items };
+ return { accounts, items };
}
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index 32e6807a..399a33e9 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -1,161 +1,54 @@
<script>
- import { browser } from "$app/environment";
- import { goto, invalidate } from "$app/navigation";
- import { btc, f, sat, post } from "$lib/utils";
+ import { btc, f, sat } from "$lib/utils";
import Icon from "$comp/Icon.svelte";
+ import Account from "$comp/Account.svelte";
import Balance from "$comp/Balance.svelte";
- import DeleteItem from "$comp/DeleteItem.svelte";
+ import Items from "$comp/Items.svelte";
import { t } from "$lib/translations";
- import { fade } from "svelte/transition";
- import { cubicIn } from "svelte/easing";
- import { flip } from "svelte/animate";
import { installPrompt } from "$lib/store";
- import {
- dndzone,
- SHADOW_ITEM_MARKER_PROPERTY_NAME,
- SOURCES,
- TRIGGERS,
- } from "svelte-dnd-action";
-
export let data;
- let flipDurationMs = 200;
- let dropTargetStyle = {};
-
- let { subject, user, items, rates } = data;
+ let { accounts, subject, user, items, rates } = data;
$: ({ currency } = subject);
$: rate = rates[currency];
- $: refresh(data);
- let refresh = (d) => ({ items, subject, user, rates } = d);
- let dragDisabled =
- subject?.id !== user?.id || (browser && window.innerWidth < 768);
-
- let deleting;
$: total = items.reduce((a, b) => a + b.price * b.quantity, 0);
- let del = (item) => {
- deleting = item;
- };
-
- function handleConsider(e) {
- const {
- items: newItems,
- info: { source, trigger },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.KEYBOARD && trigger === TRIGGERS.DRAG_STOPPED)
- reset();
- }
-
- async function handleFinalize(e) {
- const {
- items: newItems,
- info: { source },
- } = e.detail;
- items = newItems;
- if (source === SOURCES.POINTER) reset();
-
- if (subject?.id === user?.id) {
- await post("/api/items/sort", { items });
- invalidate("app:items");
- }
- }
-
- function startDrag(e) {
- e.preventDefault();
- dragDisabled = false;
- }
-
- function handleKeyDown(e) {
- if ((e.key === "Enter" || e.key === " ") && dragDisabled)
- dragDisabled = false;
- }
-
- $: if (total > 0 && browser) {
- let payButton = document.querySelector("#payButton");
- if (payButton) payButton.addEventListener("click", checkout);
- }
-
- let checkout = async () => {
- let invoice = {
- type: "lightning",
- amount: btc(total, rate),
- currency,
- items: items.filter((i) => i.quantity > 0),
- prompt: subject.prompt,
- memoPrompt: subject.memoPrompt,
- rate,
- };
-
- let { id } = await post(`/${subject.username}/invoice`, {
- invoice,
- user: { username: subject.username },
- });
-
- if (invoice.memoPrompt) {
- return goto(`/${subject.username}/invoice/${id}/memo`);
- }
- goto(`/${subject.username}/invoice/${id}`);
- };
- let reset = () =>
- (dragDisabled =
- user?.username !== subject?.username || window.innerWidth < 768);
+ let refresh = (d) => ({ accounts, items, subject, user, rates } = d);
+ $: refresh(data);
let install = async () => {
if (!$installPrompt) return;
await $installPrompt.prompt();
$installPrompt = null;
};
-</script>
-
-<svelte:window on:resize={reset} />
-{#if deleting}
- <DeleteItem bind:item={deleting} />
-{/if}
+ if (user) user.savings = 0;
+</script>
-<div class="space-y-5">
+<div class="space-y-2">
{#if user?.id === subject.id}
- <div class="flex justify-center lg:justify-start">
- <Balance {user} {rate} />
- </div>
-
- {#if !user.balance}
- <div class="mb-8">
- <p class="text-secondary text-lg">
- {$t("user.welcome")}
- </p>
- </div>
- {/if}
-
- <div class="flex flex-wrap gap-3 justify-center w-full text-xl">
- <a href={`/send`} class="contents grow">
- <button
- type="button"
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
- >
- <div class="mx-auto flex gap-2">
- <Icon icon="send" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.send")}</div>
- </div>
- </button>
- </a>
+ {#each accounts as account}
+ <Account {user} {rate} {account} />
+ {/each}
- <a href={`/${user.username}/receive`} class="contents">
+ <div class="flex justify-center gap-2">
+ <a href={`/account/savings`} class="contents">
<button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex w-full justify-center"
>
<div class="mx-auto flex gap-2">
- <Icon icon="receive" style="w-8" />
- <div class="my-auto">{$t("user.dashboard.receive")}</div>
+ <Icon icon="plus" style="w-8 mx-auto" />
+ <div class="my-auto text-xl whitespace-nowrap">
+ {$t("accounts.addAccount")}
+ </div>
</div>
</button>
</a>
{#if $installPrompt}
<button
- class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden"
+ class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white xl:hidden w-full justify-center"
on:click={install}
>
<div class="mx-auto flex gap-2">
@@ -169,115 +62,8 @@
</div>
{/if}
- {#if items.length}
- <h2 class="text-2xl font-bold">Select items</h2>
- {/if}
- <div
- class="grid sm:grid-cols-2 gap-4 pb-20"
- use:dndzone={{ items, flipDurationMs, dropTargetStyle, dragDisabled }}
- on:consider={handleConsider}
- on:finalize={handleFinalize}
- >
- {#each items as i (i.id)}
- <button
- type="button"
- class="w-full text-center cursor-pointer hover:opacity-80 mb-auto"
- on:click={() => i.quantity++}
- >
- <div class="relative w-full h-64 overflow-hidden rounded-xl">
- {#if i[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
- <div
- in:fade={{ duration: 200, easing: cubicIn }}
- class="custom-shadow-item"
- />
- {:else if i.image}
- <img
- src={`/api/public/${i.image}.webp`}
- alt={i.name}
- class="mx-auto object-cover h-full w-full"
- />
- {:else}
- <div
- class="bg-gradient-to-r from-primary to-gradient mb-4 cursor-pointer hover:opacity-80 w-full h-full"
- alt="Banner"
- />
- {/if}
- {#if user?.username === subject.username}
- <div class="flex gap-2 justify-center absolute top-2 right-2">
- <button type="button" on:click|stopPropagation={() => {}}>
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="edit" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- on:click|stopPropagation={() => del(i)}
- >
- <Icon icon="trash" style="w-8 mx-auto invert" />
- </button>
- <button
- type="button"
- on:click|stopPropagation={() => {}}
- class="sm:hidden"
- on:mousedown={startDrag}
- on:touchstart={startDrag}
- on:keydown={handleKeyDown}
- >
- <a href={`/${user.username}/items/${i.id}`}>
- <button
- class="bg-black rounded-full w-12 h-12 bg-opacity-40 hover:bg-opacity-100"
- >
- <Icon icon="move" style="w-8 mx-auto invert" />
- </button>
- </a>
- </button>
- </div>
- {/if}
- </div>
- <div class="bg-white rounded-xl py-2">
- <div class="flex text-left gap-2">
- <div class="overflow-hidden space-y-1">
- <div class="text-2xl break-words">{i.name}</div>
- <div class="flex gap-2">
- <div class="font-semibold text-lg">
- {f(i.price, currency)}
- </div>
- <div class="text-secondary my-auto">
- {sat(btc(i.price, rate))}
- </div>
- </div>
- </div>
- <div class="flex gap-1 ml-auto mb-auto justify-around tabular-nums">
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() =>
- i.quantity > 0 && i.quantity--}
- >
- <Icon icon="minus" style="w-10 min-w-10" />
- </button>
- {/if}
- <div class="text-2xl my-auto text-center">
- {#if i.quantity}{i.quantity}{:else}&mdash;{/if}
- </div>
- {#if i.quantity}
- <button
- type="button"
- on:click|stopPropagation={() => i.quantity++}
- >
- <Icon icon="plus" style="w-10 min-w-10" />
- </button>
- {/if}
- </div>
- </div>
- </div>
- </button>
- {/each}
- </div>
+ <Items {items} {subject} {user} {total} {currency} {rate} />
+
<div
class="fixed inset-x-0 mx-auto flex bottom-16"
class:static={!items.length}
diff --git a/src/routes/(app)/[username]/+layout.server.ts b/src/routes/(app)/[username]/+layout.server.ts
index 11e6d45c..9a520036 100644
--- a/src/routes/(app)/[username]/+layout.server.ts
+++ b/src/routes/(app)/[username]/+layout.server.ts
@@ -1,17 +1,24 @@
import { get } from "$lib/utils";
import { error } from "@sveltejs/kit";
-export let load = async ({ cookies, depends, params: { username } }) => {
+export let load = async ({
+ cookies,
+ depends,
+ params: { username },
+ parent,
+}) => {
depends("app:user");
try {
let subject = await get(`/users/${username}`);
+ let { user } = await parent();
+ if (!user) user = subject;
let expires = new Date();
expires.setSeconds(expires.getSeconds() + 380 * 24 * 60 * 60);
cookies.set("username", username, { path: "/", expires });
- return { subject };
+ return { subject, user };
} catch (e) {
error(500, "Unable to retrieve user account data");
}
diff --git a/src/routes/(app)/[username]/+layout.svelte b/src/routes/(app)/[username]/+layout.svelte
index dd29e3aa..ce152831 100644
--- a/src/routes/(app)/[username]/+layout.svelte
+++ b/src/routes/(app)/[username]/+layout.svelte
@@ -1,57 +1,9 @@
<script>
- import AppHeader from "$comp/AppHeader.svelte";
- import { onNavigate } from "$app/navigation";
export let data;
-
- onNavigate((navigation) => {
- if (!document.startViewTransition) return;
-
- return new Promise((resolve) => {
- document.startViewTransition(async () => {
- resolve();
- await navigation.complete;
- });
- });
- });
</script>
-<AppHeader {data} />
<div class="content">
<slot />
</div>
-<style>
- @keyframes fade-in {
- from {
- opacity: 0;
- }
- }
-
- @keyframes fade-out {
- to {
- opacity: 0;
- }
- }
-
- @keyframes slide-from-right {
- from {
- transform: translateX(60px);
- }
- }
-
- @keyframes slide-to-left {
- to {
- transform: translateX(-60px);
- }
- }
-
- :root::view-transition-old(root) {
- animation: 280ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
- 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
- }
-
- :root::view-transition-new(root) {
- animation: 280ms cubic-bezier(0, 0, 0.2, 1) 80ms both fade-in,
- 180ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
- }
-</style>
+<slot name="fixed" />
diff --git a/src/routes/(app)/[username]/address/+page.server.ts b/src/routes/(app)/[username]/address/+page.server.ts
deleted file mode 100644
index 2f7841ab..00000000
--- a/src/routes/(app)/[username]/address/+page.server.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { auth, post } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export async function load({ cookies, parent }) {
- let user = (await parent()).subject;
- let invoice = { type: "bitcoin" };
- let { id } = await post(`/invoice`, { invoice, user }, auth(cookies));
- redirect(307, `/${user.username}/invoice/${id}`);
-}
diff --git a/src/routes/(app)/[username]/card/[serial]/+page.server.ts b/src/routes/(app)/[username]/card/[serial]/+page.server.ts
deleted file mode 100644
index 945eee30..00000000
--- a/src/routes/(app)/[username]/card/[serial]/+page.server.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { auth, get } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export let load = async ({ cookies, params }) => {
- let { serial } = params;
- let fund;
- try {
- fund = await get(`/fund/${serial}`, auth(cookies));
- } catch (e) {
- console.log(e);
- }
-
- if (fund) redirect(307, `/fund/${serial}`);
-
- return params;
-};
diff --git a/src/routes/(app)/[username]/card/[serial]/+page.svelte b/src/routes/(app)/[username]/card/[serial]/+page.svelte
deleted file mode 100644
index 1d11ebee..00000000
--- a/src/routes/(app)/[username]/card/[serial]/+page.svelte
+++ /dev/null
@@ -1,36 +0,0 @@
-<script>
- import Card from "$comp/Card.svelte";
- import Icon from "$comp/Icon.svelte";
- export let data;
- let { serial } = data;
- let balance = 0;
-</script>
-
-<div class="space-y-5">
- <div class="relative flex justify-center gap-2">
- <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {serial}</h1>
- </div>
- <div class="flex justify-center">
- <a href={`/send/pot/${serial}`}>
- <button
- class="rounded-full border py-3 px-6 font-bold hover:opacity-80 flex w-60 bg-black text-white justify-center gap-2"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- class="invert text-black peer-placeholder-shown:text-gray-300 h-6 w-6 my-auto"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- >
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"
- />
- </svg>
- <div class="my-auto">Add Funds</div>
- </button>
- </a>
- </div>
-</div>
diff --git a/src/routes/(app)/[username]/customers/+page.svelte b/src/routes/(app)/[username]/customers/+page.svelte
deleted file mode 100644
index e69de29b..00000000
--- a/src/routes/(app)/[username]/customers/+page.svelte
+++ /dev/null
diff --git a/src/routes/(app)/[username]/funds/+page.server.ts b/src/routes/(app)/[username]/funds/+page.server.ts
deleted file mode 100644
index e69de29b..00000000
--- a/src/routes/(app)/[username]/funds/+page.server.ts
+++ /dev/null
diff --git a/src/routes/(app)/[username]/funds/+page.svelte b/src/routes/(app)/[username]/funds/+page.svelte
deleted file mode 100644
index 25686e86..00000000
--- a/src/routes/(app)/[username]/funds/+page.svelte
+++ /dev/null
@@ -1,52 +0,0 @@
-<script>
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import { t } from "$lib/translations";
-
- export let data;
-
- let { funds } = data;
-</script>
-
-<div class="mt-24 mb-20">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
- Funds
- </h1>
-
- <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
- <div class="text-base">
- <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4">
- <div class="whitespace-nowrap my-auto">
- <div class="text-secondary" />
- </div>
-
- <div class="flex my-auto">
- <div class="text-secondary flex">
- <div class="my-auto mr-1">
- <img src="/images/moneypot.png" class="w-12" alt="Pot" />
- </div>
-
- <div class="my-auto">Pot</div>
- </div>
- </div>
-
- <div class="text-secondary text-right text-sm my-auto">
- <div />
- </div>
- </div>
- </div>
-
- <button
- class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto"
- >
- <Icon icon="plus" style="opacity-50 mr-2 my-auto" />
- <div class="my-auto">{$t("payments.create")}</div>
- </button>
- </div>
-</div>
-
-<style>
- .active * {
- @apply bg-black text-white border-black;
- }
-</style>
diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts b/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts
deleted file mode 100644
index 41554c9f..00000000
--- a/src/routes/(app)/[username]/invoice/[id]/+layout.server.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import Qr from "qrcode-base64";
-import { get } from "$lib/utils";
-import { redirect } from "@sveltejs/kit";
-
-export async function load({ depends, params, url, parent }) {
- depends("app:invoice");
-
- let { subject, user } = await parent();
- let { id } = params;
- let invoice = await get(`/invoice/${id}`);
- let options = !!url.searchParams.get("options");
-
- if (
- user &&
- invoice.uid !== user.id &&
- !(url.pathname.includes("tip") || url.pathname.includes("memo")) &&
- !options
- ) {
- redirect(307, `/send/invoice/${id}`);
- }
-
- let { amount, pending, received } = invoice;
- amount = parseInt(amount);
-
- let paid =
- (!amount && (pending || received)) ||
- (amount > 0 && (pending >= amount || received >= amount));
- if (paid && !url.pathname.endsWith("paid")) {
- redirect(
- 307,
- `/${params.username}/invoice/${id}/paid` +
- (options ? "?options=true" : "")
- );
- }
-
- let src = Qr.drawImg(invoice.text || "", { size: 340 });
-
- return { id, invoice, subject, src };
-}
diff --git a/src/routes/(app)/[username]/map/+page.server.ts b/src/routes/(app)/[username]/map/+page.server.ts
deleted file mode 100644
index 616d28f6..00000000
--- a/src/routes/(app)/[username]/map/+page.server.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { get } from "$lib/utils";
-
-// export const prerender = true;
-export let load = async () => {
- let data = { locations: [] };
-
- try {
- data = await get("/locations");
- } catch (e) {}
-
- return data;
-};
diff --git a/src/routes/(app)/[username]/map/+page.svelte b/src/routes/(app)/[username]/map/+page.svelte
deleted file mode 100644
index bfa115e4..00000000
--- a/src/routes/(app)/[username]/map/+page.svelte
+++ /dev/null
@@ -1,8 +0,0 @@
-<script>
- import LocationsMap from "$comp/LocationsMap.svelte";
- export let data;
-
- let { locations } = data;
-</script>
-
-<LocationsMap {locations} />
diff --git a/src/routes/(app)/[username]/receive/+page.svelte b/src/routes/(app)/[username]/receive/+page.svelte
new file mode 100644
index 00000000..11587211
--- /dev/null
+++ b/src/routes/(app)/[username]/receive/+page.svelte
@@ -0,0 +1,343 @@
+<script>
+ import { send } from "$lib/socket";
+ import {
+ btc,
+ post,
+ back,
+ copy,
+ f,
+ get,
+ types,
+ sat,
+ reverseFormat,
+ s,
+ sats,
+ } from "$lib/utils";
+ import { tick, onMount, onDestroy } from "svelte";
+ import { browser } from "$app/environment";
+ import { last, showQr, amountPrompt } from "$lib/store";
+ import Avatar from "$comp/Avatar.svelte";
+ import Icon from "$comp/Icon.svelte";
+ import Heart from "$comp/Heart.svelte";
+ import Image from "$comp/Image.svelte";
+ import Numpad from "$comp/Numpad.svelte";
+ import { t } from "$lib/translations";
+ import { goto, invalidate } from "$app/navigation";
+ import Toggle from "$comp/Toggle.svelte";
+
+ export let data;
+
+ let showOptions;
+
+ let reading = async ({ message, serialNumber }) => {
+ let name = serialNumber.replace(/:/g, "-");
+ let result = await post(`/fund/${name}/withdraw`, { amount, hash, name });
+ };
+
+ let readingerror = (e) => console.log("nfc error", e);
+
+ $: refresh(data);
+ let { invoice, id, subject, user, src } = data;
+ let {
+ aid,
+ amount,
+ hash,
+ type,
+ rate,
+ text,
+ tip,
+ user: { username, currency },
+ } = invoice;
+
+ let qr;
+ let tipPercent = 0;
+
+ let refresh = async (data) => {
+ ({ invoice, id, user, src } = data);
+ ({
+ aid,
+ amount,
+ hash,
+ type,
+ rate,
+ text,
+ tip,
+ user: { username, currency },
+ } = invoice);
+
+ tipPercent = (tip / amount) * 100;
+ };
+
+ $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2));
+ $: tipAmount = ((tip * rate) / sats).toFixed(2);
+
+ let subbed;
+
+ onMount(async () => {
+ if (browser) {
+ last.subscribe((v) => {
+ if (!v || subbed) return;
+ subbed = true;
+ send("subscribe", invoice);
+ });
+
+ if ($amountPrompt && tip === undefined) toggleAmount();
+ }
+ });
+
+ $: link = [types.bitcoin, types.liquid].includes(type)
+ ? text
+ : `lightning:${text}`;
+ $: txt = [types.bitcoin, types.liquid].includes(type) ? hash : text;
+
+ let setType = async (type) => {
+ invoice.type = type;
+ ({ id } = await post(`/invoice`, {
+ invoice,
+ user: { username, currency },
+ }));
+
+ goto(`./${id}?options=true`, { invalidateAll: true, noScroll: true });
+ };
+
+ let setAmount = async () => {
+ if (typeof $amountPrompt === "undefined") $amountPrompt = true;
+ settingAmount = false;
+ amount = newAmount;
+ invoice.amount = newAmount;
+ invoice.tip = 0;
+ ({ id } = await post(`/invoice`, {
+ invoice,
+ user: { username, currency },
+ }));
+
+ let url = `./${id}`;
+ if (subject?.prompt) url += "/tip";
+ else url += "?options=true";
+
+ goto(url, { invalidateAll: true, noScroll: true });
+ };
+
+ let newAmount;
+ let settingAmount;
+ let toggleAmount = () => (settingAmount = !settingAmount);
+ let fiat;
+ let submit;
+</script>
+
+<div class="invoice container mx-auto max-w-xl px-4 space-y-2">
+ {#if $showQr}
+ <div>
+ <a
+ href={[types.bitcoin, types.liquid].includes(invoice.type)
+ ? invoice.text
+ : "lightning:" + invoice.text}
+ >
+ <img
+ {src}
+ class="mx-auto z-10 max-w-[360px]"
+ bind:this={qr}
+ alt={txt}
+ />
+ </a>
+ </div>
+ {:else}
+ <div class="break-all pb-5 text-center text-secondary text-xl">
+ {txt}
+ </div>
+ {/if}
+
+ {#each invoice.items as i}
+ <div class="grid grid-cols-12 text-xl">
+ <div class="col-span-1 my-auto">{i.quantity}</div>
+ <div class="mr-auto grow col-span-7 my-auto">
+ {i.name}
+ </div>
+ <div class="col-span-2 font-semibold text-right my-auto">
+ {f(i.price * i.quantity, currency)}
+ </div>
+ <div class="col-span-2 text-secondary text-right text-lg my-auto">
+ {sat(btc(i.price * i.quantity, rate))}
+ </div>
+ </div>
+ {/each}
+
+ {#if amount > 0}
+ <div class="text-center font-bold text-2xl">
+ <div>
+ {f(amountFiat, currency)}
+
+ {#if tip}
+ <span class="text-sm">
+ +{f(tip * (rate / sats), currency)}
+ </span>
+ {/if}
+ </div>
+ <div>
+ <span class="text-secondary font-normal text-xl"
+ >⚡️{`${s(amount)}`}</span
+ >
+
+ {#if tip}
+ <span class="text-sm text-secondary font-normal">
+ +⚡️{s(tip)}
+ </span>
+ {/if}
+ </div>
+ </div>
+ {/if}
+
+ <div class="text-secondary space-y-2 text-xl pt-2">
+ <button
+ type="button"
+ class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2"
+ on:click={() => copy(txt)}
+ >
+ <Icon icon="copy" style="w-8 my-auto" />
+ <div class="my-auto">
+ {$t("payments.copy")}
+ </div>
+ </button>
+
+ {#if user?.id === invoice?.user?.id}
+ <button
+ class="w-full flex justify-center rounded-2xl border py-5 px-6 hover:opacity-80"
+ on:click={toggleAmount}
+ >
+ <Icon icon="edit" style="mr-1 w-8" />
+ <div class="my-auto">{$t("payments.setAmount")}</div>
+ </button>
+ {:else}
+ <a href={link} class="w-full">
+ <button
+ class="w-full flex justify-center rounded-2xl border py-5 px-6 hover:opacity-80"
+ >
+ <Icon icon="mobile" style="mr-1 w-8" />
+ <div class="my-auto">{$t("payments.openLink")}</div>
+ </button>
+ </a>
+ {/if}
+
+ <div class="w-full flex justify-center gap-2 flex-wrap">
+ <button
+ class="w-full flex justify-center rounded-2xl border py-5 px-6 hover:opacity-80"
+ on:click={() => ($showQr = !$showQr)}
+ >
+ {#if $showQr}
+ <Icon icon="text" style="w-6 mr-2 my-auto" />
+ {:else}
+ <Icon icon="qr" style="w-8 mr-1 invert my-auto" />
+ {/if}
+ <div class="my-auto">
+ {$showQr ? $t("payments.showText") : $t("payments.showQr")}
+ </div></button
+ >
+ </div>
+ </div>
+
+ {#if aid === user.id}
+ <div class="grid grid-cols-2 justify-around text-secondary">
+ <button
+ class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.bitcoin}
+ class:text-black={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-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.liquid}
+ class:text-black={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-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.lightning}
+ class:text-black={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>
+
+ {#if user?.id === invoice?.user?.id}
+ <a href={`/receive`} class="block contents">
+ <button
+ class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ >
+ <img src="/images/cash.png" class="w-8 my-auto" />
+ <div class="my-auto text-lg">Ecash</div>
+ </button>
+ </a>
+ {/if}
+ </div>
+
+ {#if type === types.liquid}
+ <div class="flex justify-center">
+ <div class="my-auto text-xl text-center text-secondary">
+ {$t("payments.onlyLbtc")}
+ <span class="text-teal-500 font-bold">L-BTC</span>,
+ {$t("payments.dontDeposit")}
+ </div>
+ </div>
+ {/if}
+ {/if}
+</div>
+
+{#if settingAmount}
+ <div
+ class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
+ >
+ <div
+ class="relative mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5 top-10"
+ >
+ <form on:submit|preventDefault={setAmount}>
+ <Numpad
+ bind:amount={newAmount}
+ bind:currency
+ bind:rate
+ bind:fiat
+ bind:submit
+ />
+ <div class="w-full flex flex-wrap gap-2">
+ <button
+ bind:this={submit}
+ type="submit"
+ on:click={setAmount}
+ class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto bg-black text-white w-full"
+ >
+ <div class="my-auto">Ok</div>
+ </button>
+ <button
+ type="button"
+ class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto w-full"
+ on:click={toggleAmount}
+ on:keydown={toggleAmount}
+ >
+ <div class="my-auto">Cancel</div>
+ </button>
+ </div>
+ </form>
+
+ <div class="flex justify-center gap-3">
+ <div class="text-secondary">{$t("payments.amountPrompt")}</div>
+ <Toggle id="notify" bind:value={$amountPrompt} />
+ </div>
+ </div>
+ </div>
+{/if}
+
+<style>
+ .invoice {
+ view-transition-name: invoice;
+ }
+</style>
diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.server.ts b/src/routes/(app)/[username]/receive/[...param]/+page.server.ts
deleted file mode 100644
index 502678a9..00000000
--- a/src/routes/(app)/[username]/receive/[...param]/+page.server.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { redirect } from "@sveltejs/kit";
-import { auth, post } from "$lib/utils";
-import type { Invoice } from "$lib/types";
-
-export let load = async ({ cookies, parent }) => {
- let {subject, user, rates } = await parent();
-
- let invoice: Invoice = {
- type: "lightning",
- rate: rates[user?.currency || subject?.currency],
- };
-
- if(!user) user = subject;
- invoice = await post("/invoice", { invoice, user }, auth(cookies));
- let { id } = invoice;
-
- if (invoice.memoPrompt && !invoice.memo) {
- redirect(307, `/${user.username}/invoice/${id}/memo`);
- } else redirect(307, `/${user.username}/invoice/${id}`);
-};
diff --git a/src/routes/(app)/[username]/receive/[...param]/+page.svelte b/src/routes/(app)/[username]/receive/[...param]/+page.svelte
deleted file mode 100644
index f9267105..00000000
--- a/src/routes/(app)/[username]/receive/[...param]/+page.svelte
+++ /dev/null
@@ -1,116 +0,0 @@
-<script>
- import { page } from "$app/stores";
- import { fly } from "svelte/transition";
- import { enhance } from "$app/forms";
- import { tick } from "svelte";
- import { browser } from "$app/environment";
- import { t } from "$lib/translations";
- import AppHeader from "$comp/AppHeader.svelte";
- import Avatar from "$comp/Avatar.svelte";
- import Icon from "$comp/Icon.svelte";
- import Spinner from "$comp/Spinner.svelte";
- import { back, fail, focus } from "$lib/utils";
-
- export let data;
- export let form;
- data.subject = data.user;
-
- let { contacts } = data;
- let w;
-
- let el, text;
-
- let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click());
-
- let paste = async () => {
- text = await navigator.clipboard.readText();
- };
-</script>
-
-<svelte:window bind:innerWidth={w} />
-
-<AppHeader {data} />
-<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
- {$t("payments.send")}
- </h1>
-
- <form method="POST" use:enhance class="space-y-2 text-xl">
- {#if form?.error}
- <div class="mb-5">
- <div class="text-red-600">
- {$t("error.unrecognizedInput")}
- </div>
- </div>
- {/if}
-
- <div class="mb-2">
- <textarea
- use:focus
- name="text"
- placeholder={$t("user.send.placeholder")}
- on:keypress={keypress}
- class="w-full p-4 border rounded-xl h-48 text-xl"
- bind:value={text}
- autocapitalize="none"
- />
- </div>
-
- <div class="flex gap-2">
- <a href="/scan" class="w-full">
- <button
- type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
- >
- <Icon icon="camera" style="mr-2 w-6 my-auto" />
- <div class="my-auto">{$t("user.send.scan")}</div>
- </button>
- </a>
-
- <button
- type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
- on:click={paste}
- >
- <Icon icon="paste" style="mr-2 w-6 my-auto" />
- <div class="my-auto">{$t("user.send.paste")}</div>
- </button>
- </div>
-
- <button
- bind:this={el}
- type="submit"
- class="bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold"
- >
- {$t("user.send.next")}
- </button>
- </form>
-
- {#if contacts.length}
- <div class="space-y-5">
- <h1 class="px-3 md:px-0 text-2xl font-semibold mt-10">
- {$t("user.send.contacts")}
- </h1>
- <div>
- {#each contacts as c}
- <a href={`/pay/${c.username}`}>
- <div
- class="border-b p-2 last:border-b-0 hover:bg-gray-100 rounded-2xl"
- >
- <div class="flex">
- <div>
- <div class="flex">
- <Avatar user={c} size={20} disabled={true} />
- <div class="my-auto text-left">
- <p class="ml-1 text-lg break-words">{c.username}</p>
- </div>
- </div>
- </div>
- </div>
- </div>
- </a>
- {/each}
- </div>
- </div>
- {/if}
-</div>
diff --git a/src/routes/(app)/[username]/reset/+server.ts b/src/routes/(app)/[username]/reset/+server.ts
deleted file mode 100644
index f7dba02c..00000000
--- a/src/routes/(app)/[username]/reset/+server.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { json } from "@sveltejs/kit";
-import { auth, post } from "$lib/utils";
-
-export async function POST({ cookies, request, params }) {
- let { username } = params;
- let r = await request.json();
- let { password } = r;
- let result = await post("/reset", { password, username }, auth(cookies));
- return json(result);
-}
diff --git a/src/routes/(app)/account/[id]/+page.server.ts b/src/routes/(app)/account/[id]/+page.server.ts
new file mode 100644
index 00000000..35b5517d
--- /dev/null
+++ b/src/routes/(app)/account/[id]/+page.server.ts
@@ -0,0 +1,25 @@
+import { error, redirect } from "@sveltejs/kit";
+import { auth, get, post, fd } from "$lib/utils";
+
+
+export let load = async ({ cookies, params }) => {
+ let account = await get(`/account/${params.id}`, auth(cookies));
+ return { account };
+}
+
+export const actions = {
+ default: async ({ cookies, params, request }) => {
+ let { id } = params;
+ let body = await fd(request);
+ let username = cookies.get("username");
+
+ try {
+ await post(`/account/${id}`, body, auth(cookies));
+ } catch (e: any) {
+ console.log(e);
+ error(400, { message: e.message });
+ }
+
+ redirect(303, `/${username}`);
+ },
+};
diff --git a/src/routes/(app)/account/[id]/+page.svelte b/src/routes/(app)/account/[id]/+page.svelte
new file mode 100644
index 00000000..76473a89
--- /dev/null
+++ b/src/routes/(app)/account/[id]/+page.svelte
@@ -0,0 +1,94 @@
+<script>
+ import Mnemonic from "$comp/Mnemonic.svelte";
+ import WalletPass from "$comp/WalletPass.svelte";
+ import { goto } from "$app/navigation";
+ import { copy, fail, focus, post } from "$lib/utils";
+ import { enhance } from "$app/forms";
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+ import { decrypt } from "nostr-tools/nip49";
+ import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ export let data;
+ let { account, user } = data;
+ let { id, name, seed } = account;
+ let mnemonic, password;
+ let passwordPrompt;
+ let cancel = () => (passwordPrompt = false);
+ let toggle = () => (passwordPrompt = !passwordPrompt);
+
+ let submit = async () => {
+ toggle();
+ let entropy = await decrypt(seed, password);
+ mnemonic = entropyToMnemonic(entropy, wordlist);
+ };
+
+ let del = async () => {
+ try {
+ await post("/api/account/delete", { id });
+ goto(`/${user.username}`);
+ } catch (e) {
+ fail(e.message);
+ }
+ };
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">Account settings</h1>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
+ <form class="space-y-5" method="POST">
+ <!-- <div> -->
+ <!-- <label for="display" class="font-bold mb-1 block">Account name</label> -->
+ <!-- <input use:focus type="text" name="name" bind:value={name} /> -->
+ <!-- </div> -->
+ <!-- -->
+ <div class="space-y-2">
+ <!-- <button -->
+ <!-- type="submit" -->
+ <!-- class="rounded-2xl border py-5 font-bold mx-auto bg-black text-white px-6 w-full" -->
+ <!-- >{$t("accounts.save")}</button -->
+ <!-- > -->
+ {#if mnemonic}
+ <Mnemonic {mnemonic} />
+
+ <button
+ on:click={() => copy(mnemonic)}
+ type="button"
+ class="flex gap-2 rounded-2xl border py-5 mx-auto px-6 w-full justify-center"
+ >
+ <Icon icon="copy" style="w-8 my-auto" />
+ <div class="my-auto">{$t("accounts.copy")}</div></button
+ >
+ {:else if seed}
+ <button
+ on:click={toggle}
+ type="button"
+ class="flex gap-2 rounded-2xl border py-5 mx-auto px-6 w-full justify-center"
+ >
+ <Icon icon="eye" style="w-8 my-auto" />
+ <div class="my-auto">{$t("accounts.revealMnemonic")}</div></button
+ >
+ {/if}
+
+ {#if seed}
+ <button
+ on:click={del}
+ type="button"
+ class="flex gap-2 rounded-2xl border py-5 mx-auto px-6 w-full justify-center"
+ >
+ <Icon icon="trash" style="w-8 my-auto" />
+ <div class="my-auto">{$t("accounts.deleteAccount")}</div></button
+ >
+ {/if}
+ </div>
+
+ <div />
+ </form>
+ </div>
+</div>
+
+{#if passwordPrompt}
+ <WalletPass bind:password bind:cancel bind:submit />
+{/if}
diff --git a/src/routes/(app)/account/cash/+page.svelte b/src/routes/(app)/account/cash/+page.svelte
new file mode 100644
index 00000000..b0e2e757
--- /dev/null
+++ b/src/routes/(app)/account/cash/+page.svelte
@@ -0,0 +1,37 @@
+<script>
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">Choose your custodian</h1>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
+ <div class="space-y-5">
+ <a href="/account/cash" class="block">
+ <div class="shadow p-8 rounded-xl flex gap-4 hover:bg-primary">
+ <img src="/icons/logo-symbol.svg" class="w-20" />
+ <div>
+ <div class="text-xl">Coinos</div>
+ <div class="text-secondary">https://mint.coinos.io</div>
+ </div>
+ </div>
+ </a>
+ <a href="/account/cash" class="block">
+ <div class="shadow p-8 rounded-xl flex gap-4 hover:bg-primary">
+ <img src="/images/minibits.png" class="w-20" />
+ <div>
+ <div class="text-xl">Minibits</div>
+ <div class="text-secondary">https://mint.minibits.cash/bitcoin</div>
+ </div>
+ </div>
+ </a>
+ </div>
+ <!-- <a href="/account/mints" class="block"> -->
+ <!-- <div class="p-4 border rounded-xl flex justify-center gap-2"> -->
+ <!-- <Icon icon="plus" style="w-8 my-auto" /> -->
+ <!-- <div class="text-xl my-auto">More</div> -->
+ <!-- </div> -->
+ <!-- </a> -->
+ </div>
+</div>
diff --git a/src/routes/(app)/account/import/+page.svelte b/src/routes/(app)/account/import/+page.svelte
new file mode 100644
index 00000000..a1d6549b
--- /dev/null
+++ b/src/routes/(app)/account/import/+page.svelte
@@ -0,0 +1,55 @@
+<script>
+ import { goto } from "$app/navigation";
+ import { mnemonic } from "$lib/store";
+ import { fail, focus, versions } from "$lib/utils";
+ import { t } from "$lib/translations";
+ import { browser } from "$app/environment";
+ import Icon from "$comp/Icon.svelte";
+ import { validateMnemonic } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ let el, pasted, text;
+
+ let paste = async () => {
+ text = await navigator.clipboard.readText();
+ pasted = true;
+ };
+
+ let submit = () => {
+ text = text.replace(/,/g, " ").trim();
+ let valid = validateMnemonic(text, wordlist);
+ if (valid) {
+ $mnemonic = text;
+ goto("/account/pass");
+ } else {
+ fail("Failed to validate seed phrase");
+ }
+ };
+
+ $: if (browser && pasted && text) form.submit() && (pasted = false);
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">{$t("accounts.import")}</h1>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
+ <form on:submit|preventDefault={submit} class="space-y-2">
+ <textarea
+ use:focus
+ name="text"
+ placeholder={$t("accounts.enterSeed")}
+ class="w-full p-4 border rounded-xl h-48 text-xl"
+ bind:value={text}
+ autocapitalize="none"
+ />
+ <button
+ bind:this={el}
+ type="submit"
+ class="flex bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold gap-2 justify-center"
+ >
+ <Icon icon="send" style="w-8 my-auto invert" />
+ <div class="my-auto">{$t("user.send.next")}</div>
+ </button>
+ </form>
+ </div>
+</div>
diff --git a/src/routes/(app)/account/mints/+page.svelte b/src/routes/(app)/account/mints/+page.svelte
new file mode 100644
index 00000000..1080269e
--- /dev/null
+++ b/src/routes/(app)/account/mints/+page.svelte
@@ -0,0 +1 @@
+mints
diff --git a/src/routes/(app)/account/pass/+page.svelte b/src/routes/(app)/account/pass/+page.svelte
new file mode 100644
index 00000000..77298cc4
--- /dev/null
+++ b/src/routes/(app)/account/pass/+page.svelte
@@ -0,0 +1,149 @@
+<script lang="ts">
+ import { onMount } from "svelte";
+ import { t } from "$lib/translations";
+ import { mnemonic } from "$lib/store";
+ import Icon from "$comp/Icon.svelte";
+ import { encrypt } from "nostr-tools/nip49";
+ import {
+ mnemonicToSeed,
+ mnemonicToEntropy,
+ entropyToMnemonic,
+ } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+ import { focus, versions, fail, post } from "$lib/utils";
+ import { goto } from "$app/navigation";
+ import { HDKey } from "@scure/bip32";
+
+ export let data;
+
+ let { user } = data;
+
+ let confirm, password, revealPassword, revealConfirm;
+ let type = "bitcoin";
+ let name = $t("accounts.savings");
+
+ onMount(() => {
+ if (!$mnemonic) goto("/account/savings");
+ });
+
+ let submit = async () => {
+ if (!password || password !== confirm) {
+ fail($t("accounts.passwordMismatch"));
+ return;
+ }
+
+ try {
+ let seed = await encrypt(
+ mnemonicToEntropy($mnemonic, wordlist),
+ password
+ );
+ let master = HDKey.fromMasterSeed(
+ await mnemonicToSeed($mnemonic, password),
+ versions
+ );
+ let child = master.derive("m/84'/0'/0'");
+ let pubkey = child.publicExtendedKey;
+ let fingerprint = child.fingerprint.toString(16).padStart(8, "0");
+ await post("/api/accounts", { fingerprint, pubkey, name, seed, type });
+ goto(`/${user.username}`);
+ } catch (e) {
+ console.log(e);
+ fail(e.message);
+ }
+
+ $mnemonic = "";
+ };
+</script>
+
+<div class="space-y-5">
+ <div>
+ <h1 class="text-center text-3xl font-semibold">
+ {$t("accounts.accountPassword")}
+ </h1>
+ </div>
+
+ <form
+ on:submit|preventDefault={submit}
+ class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"
+ >
+ <p class="text-secondary mb-1">
+ {$t("accounts.passwordWarning")}
+ </p>
+
+ <div class="relative">
+ {#if revealPassword}
+ <input
+ name="password"
+ type="text"
+ required
+ bind:value={password}
+ autocapitalize="none"
+ />
+ {:else}
+ <input
+ use:focus
+ name="password"
+ type="password"
+ placeholder={$t("accounts.password")}
+ required
+ bind:value={password}
+ autocapitalize="none"
+ />
+ {/if}
+ <button
+ tabindex="-1"
+ type="button"
+ on:click={() => (revealPassword = !revealPassword)}
+ class="absolute right-5 -translate-y-1/2 top-1/2"
+ >
+ <Icon icon={revealPassword ? "eye" : "eye-off"} />
+ </button>
+ </div>
+
+ <div class="relative">
+ {#if revealConfirm}
+ <input
+ name="password"
+ type="text"
+ placeholder="Confirm password"
+ required
+ bind:value={confirm}
+ autocapitalize="none"
+ />
+ {:else}
+ <input
+ type="password"
+ placeholder={$t("accounts.confirmPassword")}
+ required
+ bind:value={confirm}
+ autocapitalize="none"
+ />
+ {/if}
+ <button
+ type="button"
+ tabindex="-1"
+ on:click={() => (revealConfirm = !revealConfirm)}
+ class="absolute right-5 -translate-y-1/2 top-1/2"
+ >
+ <Icon icon={revealConfirm ? "eye" : "eye-off"} />
+ </button>
+ </div>
+
+ <div class="flex gap-2">
+ <a href={`/account/seed`} class="contents">
+ <button
+ type="button"
+ class="border grow px-5 py-6 text-xl rounded-2xl"
+ >
+ {$t("accounts.back")}
+ </button>
+ </a>
+ <button
+ type="submit"
+ class="border grow px-5 py-6 text-xl rounded-2xl"
+ >
+ {$t("accounts.next")}
+ </button>
+ </div>
+ </form>
+</div>
diff --git a/src/routes/(app)/account/savings/+page.svelte b/src/routes/(app)/account/savings/+page.svelte
new file mode 100644
index 00000000..f9c6305c
--- /dev/null
+++ b/src/routes/(app)/account/savings/+page.svelte
@@ -0,0 +1,31 @@
+<script>
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">{$t("accounts.accountSetup")}</h1>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
+ <div class="space-y-5">
+ <a href="/account/seed" class="block">
+ <div class="shadow p-8 rounded-xl flex gap-4 hover:bg-primary">
+ <img src="/icons/random.svg" class="w-20" />
+ <div>
+ <div class="text-xl">{$t("accounts.create")}</div>
+ <div class="text-secondary">{$t("accounts.generateSeed")}</div>
+ </div>
+ </div>
+ </a>
+ <a href="/account/import" class="block">
+ <div class="shadow p-8 rounded-xl flex gap-4 hover:bg-primary">
+ <img src="/images/keys.png" class="w-20" />
+ <div>
+ <div class="text-xl">{$t("accounts.import")}</div>
+ <div class="text-secondary">{$t("accounts.importSeed")}</div>
+ </div>
+ </div>
+ </a>
+ </div>
+ </div>
+</div>
diff --git a/src/routes/(app)/account/seed/+page.svelte b/src/routes/(app)/account/seed/+page.svelte
new file mode 100644
index 00000000..ec5ab1cf
--- /dev/null
+++ b/src/routes/(app)/account/seed/+page.svelte
@@ -0,0 +1,76 @@
+<script>
+ import Mnemonic from "$comp/Mnemonic.svelte";
+ import { browser } from "$app/environment";
+ import { tick, onMount } from "svelte";
+ import { copy } from "$lib/utils";
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+ import { mnemonic } from "$lib/store";
+ import { generateMnemonic } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
+
+ export let data;
+ let { user } = data;
+
+ onMount(async () => {
+ if (!browser) return;
+ if (!$mnemonic) await generate();
+ });
+
+ let generate = async () => {
+ $mnemonic = await generateMnemonic(wordlist);
+ };
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">
+ {$t("accounts.seedPhrase")}
+ </h1>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ {#if $mnemonic}
+ <Mnemonic mnemonic={$mnemonic} />
+ {/if}
+
+ <div>
+ <button
+ type="button"
+ class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2"
+ on:click={generate}
+ >
+ <Icon icon="random" style="w-8 my-auto" />
+ <div class="my-auto">{$t("accounts.regenerate")}</div>
+ </button>
+
+ <button
+ type="button"
+ class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2"
+ on:click={() => copy($mnemonic)}
+ >
+ <Icon icon="copy" style="w-8 my-auto" />
+ <div class="my-auto">
+ {$t("payments.copy")}
+ </div>
+ </button>
+ </div>
+
+ <div class="flex gap-2">
+ <a href={`/account/savings`} class="contents">
+ <button
+ type="button"
+ class="border w-full py-5 px-6 text-xl rounded-2xl"
+ >
+ {$t("accounts.back")}
+ </button>
+ </a>
+ <a href={`/account/pass`} class="contents">
+ <button
+ type="button"
+ class="border w-full py-5 px-6 text-xl rounded-2xl"
+ >
+ {$t("accounts.next")}
+ </button>
+ </a>
+ </div>
+ </div>
+</div>
diff --git a/src/routes/(app)/account/setup/+page.svelte b/src/routes/(app)/account/setup/+page.svelte
new file mode 100644
index 00000000..f5a5ac09
--- /dev/null
+++ b/src/routes/(app)/account/setup/+page.svelte
@@ -0,0 +1,34 @@
+<script>
+ import { t } from "$lib/translations";
+</script>
+
+<div class="space-y-5">
+ <h1 class="text-center text-3xl font-semibold">Choose account type</h1>
+
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <a href="/account/cash" class="block">
+ <div class="shadow p-8 rounded-xl flex gap-4 hover:bg-primary">
+ <img src="/images/cash.png" class="w-20 my-auto" />
+ <div>
+ <div class="text-xl">Cash</div>
+ <div class="text-secondary">
+ Your balance is held with a custodian. Payments are fast and
+ free.
+ </div>
+ </div>
+ </div>
+ </a>
+ <a href="/account/savings" class="block">
+ <div class="shadow p-8 rounded-xl flex gap-4 hover:bg-primary">
+ <img src="/images/keys.png" class="w-20 my-auto" />
+ <div>
+ <div class="text-xl">Savings</div>
+ <div class="text-secondary">
+ You hold the keys to the account and are responsible for
+ safeguarding them.
+ </div>
+ </div>
+ </div>
+ </a>
+ </div>
+</div>
diff --git a/src/routes/(app)/admin/+page.svelte b/src/routes/(app)/admin/+page.svelte
index c61a2e45..214bf5b3 100644
--- a/src/routes/(app)/admin/+page.svelte
+++ b/src/routes/(app)/admin/+page.svelte
@@ -1,7 +1,7 @@
<script>
import { post } from "$lib/utils";
export let data;
- let reset = (username) => post(`/${username}/reset`, { password });
+ let reset = (username) => post(`/reset`, { password });
let { users } = data;
let password;
</script>
diff --git a/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte b/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte
index 679fbe76..89de4b58 100644
--- a/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte
+++ b/src/routes/(app)/ecash/[id]/[...version]/+layout.svelte
@@ -1,5 +1,4 @@
<script>
- import AppHeader from "$comp/AppHeader.svelte";
import { copy, sats, f, s } from "$lib/utils";
import { t } from "$lib/translations";
import Icon from "$comp/Icon.svelte";
diff --git a/src/routes/(app)/fund/[id]/+page.server.ts b/src/routes/(app)/fund/[id]/+page.server.ts
index e553b1b4..6fd3af68 100644
--- a/src/routes/(app)/fund/[id]/+page.server.ts
+++ b/src/routes/(app)/fund/[id]/+page.server.ts
@@ -15,7 +15,7 @@ export const actions = {
if (user) {
await post("/redeem", params, auth(cookies));
- redirect(307, `/${user.username}/payments`);
+ redirect(307, `/payments`);
}
redirect(307, "/register");
diff --git a/src/routes/(app)/fund/[id]/sweep/+page.svelte b/src/routes/(app)/fund/[id]/sweep/+page.svelte
index 4a48edf8..e142f00d 100644
--- a/src/routes/(app)/fund/[id]/sweep/+page.svelte
+++ b/src/routes/(app)/fund/[id]/sweep/+page.svelte
@@ -32,7 +32,7 @@
</h3>
</div>
-<a href={`/${user.username}/payments`}>
+<a href={`/payments`}>
<div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" />
</a>
diff --git a/src/routes/(app)/[username]/generate/+page.svelte b/src/routes/(app)/generate/+page.svelte
index 767b7545..49d75dc6 100644
--- a/src/routes/(app)/[username]/generate/+page.svelte
+++ b/src/routes/(app)/generate/+page.svelte
@@ -28,7 +28,7 @@
try {
user.pin = $pin;
- await post(`/${user.username}/generate`, user);
+ await post(`/generate`, user);
goto($loginRedirect || `/${user.username}`, { invalidateAll: true });
} catch (e) {
$pin = "";
diff --git a/src/routes/(app)/[username]/generate/+server.ts b/src/routes/(app)/generate/+server.ts
index 5a442982..5a442982 100644
--- a/src/routes/(app)/[username]/generate/+server.ts
+++ b/src/routes/(app)/generate/+server.ts
diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts
new file mode 100644
index 00000000..b8a2fe06
--- /dev/null
+++ b/src/routes/(app)/invoice/+page.server.ts
@@ -0,0 +1,28 @@
+import { auth, post } from "$lib/utils";
+import { error, redirect } from "@sveltejs/kit";
+import type { Invoice } from "$lib/types";
+
+export let load = async ({ cookies, parent }) => {
+ let aid = cookies.get("aid");
+ let { subject, user, rates } = await parent();
+
+ let invoice: Invoice = {
+ aid,
+ rate: rates[user?.currency || subject?.currency],
+ };
+
+ if (!user) user = subject;
+
+ try {
+ invoice = await post("/invoice", { invoice, aid, user }, auth(cookies));
+ } catch (e) {
+ console.log(e);
+ error(500, "Failed to generate invoice");
+ }
+
+ let { id } = invoice;
+
+ if (invoice.memoPrompt && !invoice.memo) {
+ redirect(307, `/invoice/${id}/memo`);
+ } else redirect(307, `/invoice/${id}`);
+};
diff --git a/src/routes/(app)/[username]/address/+page.svelte b/src/routes/(app)/invoice/+page.svelte
index e69de29b..e69de29b 100644
--- a/src/routes/(app)/[username]/address/+page.svelte
+++ b/src/routes/(app)/invoice/+page.svelte
diff --git a/src/routes/(app)/[username]/invoice/+server.ts b/src/routes/(app)/invoice/+server.ts
index 5e3d0300..5e3d0300 100644
--- a/src/routes/(app)/[username]/invoice/+server.ts
+++ b/src/routes/(app)/invoice/+server.ts
diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts
new file mode 100644
index 00000000..55c44f36
--- /dev/null
+++ b/src/routes/(app)/invoice/[id]/+layout.server.ts
@@ -0,0 +1,38 @@
+import * as Qr from "qrcode-base64";
+import { get } from "$lib/utils";
+import { redirect } from "@sveltejs/kit";
+
+export async function load({ depends, params, url, parent }) {
+ depends("app:invoice");
+
+ let { subject, user } = await parent();
+ let { id } = params;
+ let invoice;
+
+ if (id) {
+ invoice = await get(`/invoice/${id}`);
+ let options = !!url.searchParams.get("options");
+
+ if (
+ user &&
+ invoice.uid !== user.id &&
+ !(url.pathname.includes("tip") || url.pathname.includes("memo")) &&
+ !options
+ ) {
+ redirect(307, `/send/invoice/${id}`);
+ }
+
+ let { amount, pending, received } = invoice;
+ amount = parseInt(amount);
+
+ let paid =
+ (!amount && (pending || received)) ||
+ (amount > 0 && (pending >= amount || received >= amount));
+ if (paid && !url.pathname.endsWith("paid")) {
+ redirect(307, `/invoice/${id}/paid` + (options ? "?options=true" : ""));
+ }
+ }
+
+ let src = Qr.drawImg(invoice?.text || "", { size: 500 });
+ return { id, invoice, subject, src };
+}
diff --git a/src/routes/(app)/[username]/invoice/[id]/+layout.svelte b/src/routes/(app)/invoice/[id]/+layout.svelte
index 4fa864ce..4fa864ce 100644
--- a/src/routes/(app)/[username]/invoice/[id]/+layout.svelte
+++ b/src/routes/(app)/invoice/[id]/+layout.svelte
diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.server.ts b/src/routes/(app)/invoice/[id]/+page.server.ts
index a57efa64..a57efa64 100644
--- a/src/routes/(app)/[username]/invoice/[id]/+page.server.ts
+++ b/src/routes/(app)/invoice/[id]/+page.server.ts
diff --git a/src/routes/(app)/[username]/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte
index 28bfe27b..5ef9e5e5 100644
--- a/src/routes/(app)/[username]/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/+page.svelte
@@ -28,7 +28,6 @@
export let data;
- let ndef;
let showOptions;
let reading = async ({ message, serialNumber }) => {
@@ -41,6 +40,7 @@
$: refresh(data);
let { invoice, id, subject, user, src } = data;
let {
+ aid,
amount,
hash,
type,
@@ -56,6 +56,7 @@
let refresh = async (data) => {
({ invoice, id, user, src } = data);
({
+ aid,
amount,
hash,
type,
@@ -66,21 +67,6 @@
} = invoice);
tipPercent = (tip / amount) * 100;
-
- // if (browser && window.NDEFReader) {
- // try {
- // ndef = new NDEFReader();
- // await ndef.scan();
- //
- // ndef.removeEventListener('readingerror', readingerror);
- // ndef.removeEventListener('reading', reading);
- //
- // ndef.addEventListener('readingerror', readingerror);
- // ndef.addEventListener('reading', reading);
- // } catch (e) {
- // console.log(e);
- // }
- // }
};
$: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2));
@@ -96,14 +82,7 @@
send("subscribe", invoice);
});
- if ($amountPrompt && !tip) toggleAmount();
- }
- });
-
- onDestroy(() => {
- if (ndef) {
- ndef.removeEventListener("readingerror", readingerror);
- ndef.removeEventListener("reading", reading);
+ if ($amountPrompt && tip === null) toggleAmount();
}
});
@@ -114,7 +93,7 @@
let setType = async (type) => {
invoice.type = type;
- ({ id } = await post(`/${username}/invoice`, {
+ ({ id } = await post(`/invoice`, {
invoice,
user: { username, currency },
}));
@@ -128,7 +107,7 @@
amount = newAmount;
invoice.amount = newAmount;
invoice.tip = 0;
- ({ id } = await post(`/${username}/invoice`, {
+ ({ id } = await post(`/invoice`, {
invoice,
user: { username, currency },
}));
@@ -157,14 +136,14 @@
>
<img
{src}
- class="mx-auto z-10 shadow-inner rounded-2xl"
+ class="mx-auto z-10 max-w-[360px]"
bind:this={qr}
alt={txt}
/>
</a>
</div>
{:else}
- <div class="break-all pb-5 text-lg text-center">
+ <div class="break-all pb-5 text-center text-secondary text-xl">
{txt}
</div>
{/if}
@@ -209,7 +188,7 @@
</div>
{/if}
- <div class="text-secondary space-y-2 text-xl">
+ <div class="text-secondary space-y-2 text-xl pt-2">
<button
type="button"
class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border whitespace-nowrap mb-2"
@@ -257,72 +236,72 @@
</div>
</div>
- <div class="flex justify-around text-secondary">
- <button
- class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center"
- class:bg-primary={type === types.bitcoin}
- class:text-black={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>
+ {#if aid === user.id}
+ <div class="grid grid-cols-2 justify-around text-secondary">
+ <button
+ class="hover:bg-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.bitcoin}
+ class:text-black={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-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center"
- class:bg-primary={type === types.liquid}
- class:text-black={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-primary my-auto flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.liquid}
+ class:text-black={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-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 w-full justify-center"
- class:bg-primary={type === types.lightning}
- class:text-black={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>
+ <button
+ class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ class:bg-primary={type === types.lightning}
+ class:text-black={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>
- {#if type === types.liquid}
- <div class="flex justify-center">
- <div class="my-auto text-xl text-center text-secondary">
- {$t("payments.onlyLbtc")}
- <span class="text-teal-500 font-bold">L-BTC</span>,
- {$t("payments.dontDeposit")}
- </div>
+ {#if user?.id === invoice?.user?.id}
+ <a href={`/receive`} class="block contents">
+ <button
+ class="hover:bg-primary flex gap-1 sm:gap-2 py-3 px-2 sm:px-5 justify-center"
+ >
+ <img src="/images/cash.png" class="w-8 my-auto" />
+ <div class="my-auto text-lg">Ecash</div>
+ </button>
+ </a>
+ {/if}
</div>
- {/if}
- {#if user?.id === invoice?.user?.id}
- <a href={`/receive`} class="block">
- <button
- class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border text-xl whitespace-nowrap text-secondary"
- >
- <Icon icon="voucher" style="w-8 my-auto" />
- <div class="my-auto">
- {$t("payments.redeemVoucher")}
- </div></button
- >
- </a>
+ {#if type === types.liquid}
+ <div class="flex justify-center">
+ <div class="my-auto text-xl text-center text-secondary">
+ {$t("payments.onlyLbtc")}
+ <span class="text-teal-500 font-bold">L-BTC</span>,
+ {$t("payments.dontDeposit")}
+ </div>
+ </div>
+ {/if}
{/if}
</div>
{#if settingAmount}
<div
- class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20"
+ class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50"
>
<div
- class="relative top-1/3 mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5"
+ class="relative mx-auto p-12 border w-96 shadow-lg rounded-md bg-white space-y-5 top-10"
>
- <form submit={setAmount}>
+ <form on:submit|preventDefault={setAmount}>
<Numpad
bind:amount={newAmount}
bind:currency
@@ -332,6 +311,7 @@
/>
<div class="w-full flex flex-wrap gap-2">
<button
+ bind:this={submit}
type="submit"
on:click={setAmount}
class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto bg-black text-white w-full"
@@ -339,7 +319,6 @@
<div class="my-auto">Ok</div>
</button>
<button
- bind:this={submit}
type="button"
class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto w-full"
on:click={toggleAmount}
@@ -350,8 +329,8 @@
</div>
</form>
- <div class="flex justify-between items-center">
- <span class="font-bold">{$t("payments.amountPrompt")}</span>
+ <div class="flex justify-center gap-3">
+ <div class="text-secondary">{$t("payments.amountPrompt")}</div>
<Toggle id="notify" bind:value={$amountPrompt} />
</div>
</div>
diff --git a/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts b/src/routes/(app)/invoice/[id]/memo/+page.server.ts
index 58d56018..24dec1af 100644
--- a/src/routes/(app)/[username]/invoice/[id]/memo/+page.server.ts
+++ b/src/routes/(app)/invoice/[id]/memo/+page.server.ts
@@ -4,7 +4,7 @@ import { redirect } from "@sveltejs/kit";
export async function load({ parent, params }) {
let { invoice } = await parent();
if (!invoice.amount) {
- redirect(307, `/${params.username}/invoice/${invoice.id}`);
+ redirect(307, `/invoice/${invoice.id}`);
}
}
diff --git a/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte b/src/routes/(app)/invoice/[id]/memo/+page.svelte
index 4d23ff19..4d23ff19 100644
--- a/src/routes/(app)/[username]/invoice/[id]/memo/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/memo/+page.svelte
diff --git a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte b/src/routes/(app)/invoice/[id]/paid/+page.svelte
index 700ac972..e4279e48 100644
--- a/src/routes/(app)/[username]/invoice/[id]/paid/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/paid/+page.svelte
@@ -76,7 +76,7 @@
{/if}
</div>
-<a href={`/${user.username}/receive`}>
+<a href={`/${user.username}`}>
<div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" />
</a>
diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts b/src/routes/(app)/invoice/[id]/tip/+page.server.ts
index b81e2364..48ebf970 100644
--- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.server.ts
+++ b/src/routes/(app)/invoice/[id]/tip/+page.server.ts
@@ -1,10 +1,10 @@
import invoice from "$lib/invoice";
import { redirect } from "@sveltejs/kit";
-export async function load({ parent, params }) {
+export async function load({ parent }) {
let { invoice } = await parent();
if (!invoice.amount)
- redirect(307, `/${params.username}/invoice/${invoice.id}`);
+ redirect(307, `/invoice/${invoice.id}`);
}
export const actions = {
diff --git a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte b/src/routes/(app)/invoice/[id]/tip/+page.svelte
index 35b24bf1..da2d041e 100644
--- a/src/routes/(app)/[username]/invoice/[id]/tip/+page.svelte
+++ b/src/routes/(app)/invoice/[id]/tip/+page.svelte
@@ -59,6 +59,7 @@
$: refresh(data);
let { invoice, id, user, rates } = data;
let {
+ aid,
amount,
hash,
items,
@@ -81,6 +82,7 @@
({ invoice, id } = data);
({
+ aid,
amount,
hash,
items,
@@ -111,6 +113,7 @@
<div class="container px-4 max-w-lg text-center mx-auto">
<form method="POST" use:enhance class:invisible={submitting}>
+ <input type="hidden" name="aid" value={aid} />
<input type="hidden" name="amount" value={amount} />
<input type="hidden" name="tip" value={tip} />
<input type="hidden" name="username" value={username} />
diff --git a/src/routes/(app)/[username]/invoice/sent/+page.svelte b/src/routes/(app)/invoice/sent/+page.svelte
index b95bc85e..b95bc85e 100644
--- a/src/routes/(app)/[username]/invoice/sent/+page.svelte
+++ b/src/routes/(app)/invoice/sent/+page.svelte
diff --git a/src/routes/(app)/[username]/items/[id]/+page.server.ts b/src/routes/(app)/items/[id]/+page.server.ts
index 67cebd93..67cebd93 100644
--- a/src/routes/(app)/[username]/items/[id]/+page.server.ts
+++ b/src/routes/(app)/items/[id]/+page.server.ts
diff --git a/src/routes/(app)/[username]/items/[id]/+page.svelte b/src/routes/(app)/items/[id]/+page.svelte
index 65720350..65720350 100644
--- a/src/routes/(app)/[username]/items/[id]/+page.svelte
+++ b/src/routes/(app)/items/[id]/+page.svelte
diff --git a/src/routes/(app)/[username]/items/create/+page.server.ts b/src/routes/(app)/items/create/+page.server.ts
index 2e006a3e..2e006a3e 100644
--- a/src/routes/(app)/[username]/items/create/+page.server.ts
+++ b/src/routes/(app)/items/create/+page.server.ts
diff --git a/src/routes/(app)/[username]/items/create/+page.svelte b/src/routes/(app)/items/create/+page.svelte
index b82889c9..b82889c9 100644
--- a/src/routes/(app)/[username]/items/create/+page.svelte
+++ b/src/routes/(app)/items/create/+page.svelte
diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.ts b/src/routes/(app)/ln/[lnurl]/+page.server.ts
index d1b1b86e..c078ae8e 100644
--- a/src/routes/(app)/ln/[lnurl]/+page.server.ts
+++ b/src/routes/(app)/ln/[lnurl]/+page.server.ts
@@ -70,6 +70,6 @@ export const actions = {
let c = callback.includes("?") ? "&" : "?";
await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json());
- redirect(307, `/${username}/payments`);
+ redirect(307, `/payments`);
},
};
diff --git a/src/routes/(app)/ln/withdrawal/sent/+page.svelte b/src/routes/(app)/ln/withdrawal/sent/+page.svelte
index c1a2bdb2..90814960 100644
--- a/src/routes/(app)/ln/withdrawal/sent/+page.svelte
+++ b/src/routes/(app)/ln/withdrawal/sent/+page.svelte
@@ -19,7 +19,7 @@
<h1 class="text-3xl md:text-4xl font-bold mb-6">Request Sent!</h1>
</div>
-<a href={`/${user.username}/payments`}>
+<a href={`/payments`}>
<div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" />
</a>
diff --git a/src/routes/(app)/login/+page.server.ts b/src/routes/(app)/login/+page.server.ts
deleted file mode 100644
index fd7fec8c..00000000
--- a/src/routes/(app)/login/+page.server.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { fail, redirect } from "@sveltejs/kit";
-import { login, fd } from "$lib/utils";
-
-export const load = async ({ parent }) => {
- let { user } = await parent();
- if (user?.pubkey) redirect(307, `/${user.username}`);
-};
-
-export const actions = {
- default: async ({ cookies, request }) => {
- let form = await fd(request);
- let { username, password, token, loginRedirect } = form;
- let user = { username, password, token };
-
- if (loginRedirect === "undefined") loginRedirect = undefined;
-
- try {
- await login(
- user,
- cookies,
- request.headers.get("cf-connecting-ip")
- );
- } catch (e: any) {
- return fail(400, { error: "Login failed", message: e.message, ...form });
- }
-
- redirect(307, loginRedirect || `/${user.username}`);
- },
-};
diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte
deleted file mode 100644
index 61ea23b7..00000000
--- a/src/routes/(app)/login/+page.svelte
+++ /dev/null
@@ -1,188 +0,0 @@
-<script>
- import { onMount } from "svelte";
- 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;
- localStorage.clear();
- sessionStorage.clear();
-
- let lang = $page.url.searchParams.get("lang");
- if (lang) document.cookie = `lang=${lang} ;`;
- });
-
- 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>
-
-{#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.noAccount")}
- <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.ts b/src/routes/(app)/logout/+page.server.ts
index ab5df7b9..374f244a 100644
--- a/src/routes/(app)/logout/+page.server.ts
+++ b/src/routes/(app)/logout/+page.server.ts
@@ -7,6 +7,7 @@ const opts = {
export async function load({ cookies }) {
let lang = cookies.get("lang");
+ cookies.set("aid", "", opts);
cookies.set("lang", "", opts);
cookies.set("username", "", opts);
cookies.set("token", "", opts);
diff --git a/src/routes/(app)/[username]/messages/+page.server.ts b/src/routes/(app)/messages/+page.server.ts
index 94659621..94659621 100644
--- a/src/routes/(app)/[username]/messages/+page.server.ts
+++ b/src/routes/(app)/messages/+page.server.ts
diff --git a/src/routes/(app)/[username]/messages/+page.svelte b/src/routes/(app)/messages/+page.svelte
index 4738ea2c..547a1f21 100644
--- a/src/routes/(app)/[username]/messages/+page.svelte
+++ b/src/routes/(app)/messages/+page.svelte
@@ -71,7 +71,7 @@
<div class="relative">
{#each latest as { content, pubkey, author, recipient }}
{@const user = author.id === user.id ? recipient : author}
- <a href={`/${subject.username}/messages/${user.username}`}>
+ <a href={`/messages/${user.username}`}>
<div class="flex hover:bg-gray-100 p-4 rounded-2xl">
<div class="my-auto">
<Avatar {user} size={"20"} disabled={true} />
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts b/src/routes/(app)/messages/[recipient]/+page.server.ts
index 6be9f6ec..6be9f6ec 100644
--- a/src/routes/(app)/[username]/messages/[recipient]/+page.server.ts
+++ b/src/routes/(app)/messages/[recipient]/+page.server.ts
diff --git a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte b/src/routes/(app)/messages/[recipient]/+page.svelte
index 98638389..98638389 100644
--- a/src/routes/(app)/[username]/messages/[recipient]/+page.svelte
+++ b/src/routes/(app)/messages/[recipient]/+page.svelte
diff --git a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
index e9a0ec9e..cf8143f0 100644
--- a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
+++ b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
@@ -29,7 +29,7 @@ export async function load({ cookies, params, parent }) {
auth(cookies)
);
- redirect(307, `/${username}/invoice/${id}`);
+ redirect(307, `/invoice/${id}`);
}
return { amount, rate, subject };
@@ -37,14 +37,15 @@ export async function load({ cookies, params, parent }) {
export const actions = {
default: async ({ cookies, request }) => {
+ let p;
try {
let body = await fd(request);
- await post("/payments", body, auth(cookies));
+ p = await post("/payments", body, auth(cookies));
} catch (e: any) {
console.log(e);
return fail(400, { message: e.message });
}
- redirect(307, "/sent");
+ redirect(307, `/sent/${p.id}`);
},
};
diff --git a/src/routes/(app)/pay/[username]/[...amount]/+page.svelte b/src/routes/(app)/pay/[username]/[...amount]/+page.svelte
index 1652952a..8e3abab4 100644
--- a/src/routes/(app)/pay/[username]/[...amount]/+page.svelte
+++ b/src/routes/(app)/pay/[username]/[...amount]/+page.svelte
@@ -3,7 +3,6 @@
import { goto } from "$app/navigation";
import { pin } from "$lib/store";
import { enhance } from "$app/forms";
- import AppHeader from "$comp/AppHeader.svelte";
import Avatar from "$comp/Avatar.svelte";
import Icon from "$comp/Icon.svelte";
import Numpad from "$comp/Numpad.svelte";
@@ -33,7 +32,7 @@
rate = fiat ? (sats * amountFiat) / amount : r;
let id;
- ({ id, hash } = await post(`/${subject.username}/invoice`, {
+ ({ id, hash } = await post(`/invoice`, {
invoice: {
amount,
rate: rates[subject.currency],
@@ -56,15 +55,13 @@
};
</script>
-<AppHeader {data} />
-
{#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-8">
+<div class="container px-4 mt-20 max-w-xl mx-auto space-y-2">
<Numpad
bind:amount={a}
bind:amountFiat={af}
diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte
index ea07be14..f7a36717 100644
--- a/src/routes/(app)/payment/[id]/+page.svelte
+++ b/src/routes/(app)/payment/[id]/+page.svelte
@@ -3,7 +3,6 @@
import { onMount, tick } from "svelte";
import { t, loading } from "$lib/translations";
import {
- back,
copy,
f,
s,
@@ -34,6 +33,7 @@
rate,
type,
ref,
+ path,
tip,
ourfee,
fee,
@@ -57,6 +57,7 @@
rate,
type,
ref,
+ path,
tip,
ourfee,
fee,
@@ -96,12 +97,6 @@
});
</script>
-<a href={`/${username}/payments`}>
- <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80">
- <Icon icon="arrow-left" style="w-10" />
- </button>
-</a>
-
<div class="container mx-auto max-w-lg px-4 space-y-8 break-all text-2xl">
<h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold mb-10">
{$t(amount < 0 ? "payments.sent" : "payments.received")}
@@ -225,40 +220,18 @@
</div>
{/if}
- <div class="flex gap-2">
- {#if user.admin}
- <button
- class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60"
- on:click={bump}
- >
- <div class="mx-auto flex">
- <Icon icon="clock" style="my-auto h-6 mr-2" />
- <div class="my-auto mt-1 text-base">{$t("payments.bump")}</div>
- </div>
- </button>
- {/if}
-
- {#if user.hasprinter}
- <button
- class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60"
- on:click={print}
- >
- <div class="mx-auto flex">
- <Icon icon="printer" style="my-auto h-6 mr-2" />
- <div class="my-auto mt-1 text-base">{$t("payments.print")}</div>
- </div>
- </button>
- {:else}
- <a href={`/payment/${id}/plain`}>
+ {#if path}
+ <div>
+ <span class="text-lg text-secondary">Path</span>
+ <div class="flex">
+ <div>{path}</div>
<button
- class="rounded-full border py-3 px-6 hover:opacity-80 flex w-full md:w-60"
+ class="flex font-bold hover:opacity-80 mb-auto my-auto"
+ on:click={() => copy(path)}
>
- <div class="mx-auto flex">
- <Icon icon="printer" style="my-auto h-6 mr-2" />
- <div class="my-auto mt-1 text-base">{$t("payments.print")}</div>
- </div>
+ <Icon icon="copy" style="ml-2 w-12 my-auto" />
</button>
- </a>
- {/if}
- </div>
+ </div>
+ </div>
+ {/if}
</div>
diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.server.ts b/src/routes/(app)/payments/[...page]/+page.server.ts
index 070349f8..f1818955 100644
--- a/src/routes/(app)/[username]/payments/[...page]/+page.server.ts
+++ b/src/routes/(app)/payments/[...page]/+page.server.ts
@@ -5,6 +5,7 @@ let limit = 25;
export async function load({ cookies, depends, params }) {
depends("app:payments");
+ let aid = cookies.get("aid");
let parts = params.page.split("/");
let start, end, page;
@@ -16,7 +17,9 @@ export async function load({ cookies, depends, params }) {
let offset = (page - 1) * limit;
if (!start) start = Date.now() / 1000 - 24 * 60 * 60;
- let url = `/payments?start=${start * 1000}&limit=${limit}&offset=${offset}`;
+ let url = `/payments?aid=${aid}&start=${
+ start * 1000
+ }&limit=${limit}&offset=${offset}`;
if (end) url += `&end=${end * 1000}`;
let { count, payments, incoming, outgoing } = await get(url, auth(cookies));
diff --git a/src/routes/(app)/[username]/payments/[...page]/+page.svelte b/src/routes/(app)/payments/[...page]/+page.svelte
index 1efdc2ec..bfd30f55 100644
--- a/src/routes/(app)/[username]/payments/[...page]/+page.svelte
+++ b/src/routes/(app)/payments/[...page]/+page.svelte
@@ -65,7 +65,7 @@
: $page.url.pathname;
let csv = async () => {
- let url = `/${user.username}/payments`;
+ let url = `/payments`;
if (start) url += `/${start}`;
if (end) url += `/${end}`;
@@ -78,7 +78,9 @@
platform_fee: p.ourfee,
amount_fiat: f((p.amount * p.rate) / sats, p.currency),
fee_fiat: p.fee ? f((p.fee * p.rate) / sats, p.currency) : null,
- platform_fee_fiat: p.ourfee ? f((p.ourfee * p.rate) / sats, p.currency) : null,
+ platform_fee_fiat: p.ourfee
+ ? f((p.ourfee * p.rate) / sats, p.currency)
+ : null,
tip_fiat: p.tip ? f((p.tip * p.rate) / sats, p.currency) : null,
total_fiat: f(((p.amount + (p.tip || 0)) * p.rate) / sats, p.currency),
}));
@@ -127,30 +129,28 @@
};
</script>
-<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">
+<div class="mt-24 mb-20 payments space-y-5">
+ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
{$t("payments.header")}
</h1>
- <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
- <div class="flex text-md text-secondary relative">
- <div class="mx-auto flex justify-center w-full gap-1">
- {#each presets as { start, end, title }, i}
- <a
- class:active={selection === i}
- href={`/${user.username}/payments/${getUnixTime(start) + "/"}1`}
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
+ <div class="mx-auto flex justify-center w-full gap-1">
+ {#each presets as { start, end, title }, i}
+ <a
+ class:active={selection === i}
+ href={`/payments/${getUnixTime(start) + "/"}1`}
+ >
+ <button
+ class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]"
>
- <button
- class="text-sm md:text-lg rounded-full border py-2 px-4 hover:opacity-80 min-w-[72px]"
- >
- <div class="my-auto">{title}</div>
- </button>
- </a>
- {/each}
- </div>
+ <div class="my-auto">{title}</div>
+ </button>
+ </a>
+ {/each}
</div>
- <div class="flex flex-wrap justify-center mb-8">
+ <div class="flex flex-wrap justify-center">
{#if pages.length > 1}
{#each pages as _, i}
<a
@@ -213,6 +213,8 @@
<div class="text-secondary flex">
{#if [types.ecash, types.lightning].includes(p.type)}
<div class="text-3xl">⚡️</div>
+ {:else if p.type === types.reconcile}
+ <Icon icon="balance" style="w-6 mr-2" />
{:else if p.type === types.bitcoin}
<div class="my-auto mr-1">
<img
@@ -259,16 +261,14 @@
<div class="grid grid-cols-3 w-full text-center text-lg">
{#each Object.keys(incoming) as c}
- <span class="text-base text-secondary text-left"></span>
+ <span class="text-base text-secondary text-left" />
<!-- <span class="text-base text-secondary" -->
<!-- >{$t("payments.subtotal")}</span -->
<!-- > -->
<span class="text-base text-secondary">
{#if tipsIn || tipsOut}{$t("payments.tips")}{/if}
</span>
- <span class="text-base text-secondary"
- >{$t("payments.total")}</span
- >
+ <span class="text-base text-secondary">{$t("payments.total")}</span>
{@const totalIn = incoming[c]?.fiat || 0}
{@const tipsIn = incoming[c]?.fiatTips || 0}
@@ -293,15 +293,14 @@
<span><b>{totalOut ? f(totalOut, c) : "-"}</b></span>
{/each}
</div>
-
- <button
- class="ml-auto rounded-full border py-2 px-4 w-36 hover:opacity-80 flex mx-auto"
- on:click={csv}
- >
- <Icon icon="save" style="opacity-50 mr-2 my-auto" />
- <div class="my-auto">{$t("payments.export")}</div>
- </button>
</div>
+ <button
+ class="ml-auto rounded-2xl border py-5 px-6 hover:opacity-80 flex mx-auto text-xl gap-2"
+ on:click={csv}
+ >
+ <Icon icon="save" style="my-auto" />
+ <div class="my-auto">{$t("payments.export")}</div>
+ </button>
</div>
<style>
diff --git a/src/routes/(app)/[username]/payments/[...page]/+server.ts b/src/routes/(app)/payments/[...page]/+server.ts
index a81faaf3..a81faaf3 100644
--- a/src/routes/(app)/[username]/payments/[...page]/+server.ts
+++ b/src/routes/(app)/payments/[...page]/+server.ts
diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte
index c9562582..5469c084 100644
--- a/src/routes/(app)/qr/[text]/+page.svelte
+++ b/src/routes/(app)/qr/[text]/+page.svelte
@@ -6,26 +6,17 @@
let { src, text } = data;
</script>
-<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}>
- <Icon icon="arrow-left" style="w-10" />
-</button>
-
<div class="container px-4 max-w-4xl mx-auto 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} />
+ <Qr {src} />
- <button
- type="button"
- class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border text-xl hover:bg-slate-100"
- on:click={() => copy(text)}
- >
- <div class="my-auto">
- {text}
- </div>
- </button>
- </div>
- </div>
+ <button
+ type="button"
+ class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-5 px-6 w-full mx-auto justify-center border text-xl hover:bg-slate-100"
+ on:click={() => copy(text)}
+ >
+ <Icon icon="copy" style="w-8" />
+ <div class="my-auto">
+ {text}
+ </div>
+ </button>
</div>
diff --git a/src/routes/(app)/qr/create/+page.svelte b/src/routes/(app)/qr/create/+page.svelte
new file mode 100644
index 00000000..fd99a54b
--- /dev/null
+++ b/src/routes/(app)/qr/create/+page.svelte
@@ -0,0 +1,60 @@
+<script>
+ import { goto } from "$app/navigation";
+ import { browser } from "$app/environment";
+ import { t } from "$lib/translations";
+ import Icon from "$comp/Icon.svelte";
+ let el, text, pasted;
+
+ let keypress = (e) =>
+ e.key === "Enter" ? e.preventDefault() || el.click() : (pasted = false);
+
+ let paste = async () => {
+ text = await navigator.clipboard.readText();
+ pasted = true;
+ };
+
+ let submit = () => goto(`/qr/${encodeURIComponent(text)}`);
+
+ $: if (browser && pasted && text) el.click() && (pasted = false);
+</script>
+
+<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20">
+ <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
+ Create QR
+ </h1>
+
+ <form on:submit|preventDefault={submit} class="space-y-2 text-xl">
+ <div class="mb-2">
+ <textarea
+ use:focus
+ name="text"
+ placeholder="Enter some text or a URL to put in a QR"
+ on:keypress={keypress}
+ class="w-full p-4 border rounded-xl h-32 text-xl"
+ bind:value={text}
+ on:paste={() => (pasted = true)}
+ autocapitalize="none"
+ />
+ </div>
+
+ <div class="flex gap-2">
+ <button
+ type="button"
+ class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center gap-2"
+ on:click={paste}
+ >
+ <Icon icon="paste" style="w-8 my-auto" />
+ <div class="my-auto">{$t("user.send.paste")}</div>
+ </button>
+
+ <button
+ bind:this={el}
+ type="submit"
+ class="flex bg-black text-white border rounded-2xl px-6 py-5 w-full font-bold gap-2 justify-center"
+ >
+ <Icon icon="send" style="w-8 my-auto invert" />
+ <div class="my-auto">{$t("user.dashboard.go")}</div>
+ </button>
+ </div>
+ </form>
+</div>
diff --git a/src/routes/(app)/receive/[...token]/+page.svelte b/src/routes/(app)/receive/[...token]/+page.svelte
index 2fd4b879..988c1e6c 100644
--- a/src/routes/(app)/receive/[...token]/+page.svelte
+++ b/src/routes/(app)/receive/[...token]/+page.svelte
@@ -5,7 +5,6 @@
import { tick } from "svelte";
import { browser } from "$app/environment";
import { t } from "$lib/translations";
- import AppHeader from "$comp/AppHeader.svelte";
import Avatar from "$comp/Avatar.svelte";
import Icon from "$comp/Icon.svelte";
import Spinner from "$comp/Spinner.svelte";
@@ -30,10 +29,9 @@
<svelte:window bind:innerWidth={w} />
-<AppHeader {data} />
<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20">
<h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
- {$t("payments.voucher")}
+ {$t("user.dashboard.receive")}
</h1>
<form method="POST" use:enhance class="space-y-2 text-xl">
@@ -53,7 +51,7 @@
name="text"
placeholder={$t("payments.voucherText")}
on:keypress={keypress}
- class="w-full p-4 border rounded-xl h-48 text-xl"
+ class="w-full p-4 border rounded-xl h-32 text-xl"
bind:value={text}
on:paste={() => (pasted = true)}
autocapitalize="none"
@@ -64,19 +62,19 @@
<a href="/scan" class="w-full">
<button
type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
+ class="flex border rounded-2xl px-5 py-4 hover:opacity-80 w-full justify-center gap-1"
>
- <Icon icon="camera" style="mr-2 w-6 my-auto" />
+ <Icon icon="camera" style="w-8 my-auto" />
<div class="my-auto">{$t("user.send.scan")}</div>
</button>
</a>
<button
type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center"
+ class="flex border rounded-2xl px-5 py-4 hover:opacity-80 w-full justify-center gap-1"
on:click={paste}
>
- <Icon icon="paste" style="mr-2 w-6 my-auto" />
+ <Icon icon="paste" style="w-8 my-auto" />
<div class="my-auto">{$t("user.send.paste")}</div>
</button>
</div>
diff --git a/src/routes/(app)/register/+page.server.ts b/src/routes/(app)/register/+page.server.ts
deleted file mode 100644
index 731530c4..00000000
--- a/src/routes/(app)/register/+page.server.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { fail, redirect } from "@sveltejs/kit";
-import { fd, post, login } from "$lib/utils";
-
-export const load = async ({ parent }) => {
- let { user } = await parent();
- if (user) redirect(307, `/${user.username}`);
-
- let index = Math.floor(Math.random() * 64) + 1;
-
- return { index };
-};
-
-export const actions = {
- default: async ({ cookies, request }) => {
- let ip = request.headers.get("cf-connecting-ip");
-
- let form = await fd(request);
- let { profile, username, password, cipher, salt, pubkey, loginRedirect } =
- form;
- let user = { profile, username, password, cipher, salt, pubkey };
- let error;
-
- if (loginRedirect === "undefined") loginRedirect = undefined;
-
- try {
- await post("/register", { user }, { "cf-connecting-ip": ip });
- } catch (e: any) {
- error = e.message;
- }
-
- try {
- await login(user, cookies, ip);
- error = null;
- } catch (e: any) {
- error ||= e.message;
- }
-
- if (error) return fail(400, { error });
- redirect(307, loginRedirect || `/${user.username}`);
- },
-};
diff --git a/src/routes/(app)/register/+page.svelte b/src/routes/(app)/register/+page.svelte
deleted file mode 100644
index 0a761228..00000000
--- a/src/routes/(app)/register/+page.svelte
+++ /dev/null
@@ -1,314 +0,0 @@
-<script>
- import punks from "$lib/punks";
- import { upload } from "$lib/upload";
- import { afterNavigate, invalidateAll } from "$app/navigation";
- import { applyAction, deserialize } from "$app/forms";
- import { tick } from "svelte";
- import { fly } from "svelte/transition";
- import { enhance } from "$app/forms";
-
- import Pin from "$comp/Pin.svelte";
- import Icon from "$comp/Icon.svelte";
- import Spinner from "$comp/Spinner.svelte";
-
- import { focus, fail } from "$lib/utils";
- import { avatar, password, loginRedirect } from "$lib/store";
- import { t } from "$lib/translations";
- import { page } from "$app/stores";
- import { generate } from "$lib/nostr";
- import {
- NumberDictionary,
- uniqueNamesGenerator,
- colors,
- adjectives,
- animals,
- } from "unique-names-generator";
-
- export let form;
- export let data;
-
- $: data && ($avatar = undefined);
- let username;
- let { index } = data;
-
- let cleared;
- let clear = () => {
- if (!cleared) {
- cleared = true;
- username = "";
- $password = "";
- revealPassword = false;
- }
- };
-
- let refresh = async () => {
- username = uniqueNamesGenerator({
- dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })],
- length: 2,
- separator: "",
- });
-
- $password = uniqueNamesGenerator({
- dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })],
- length: 2,
- separator: "",
- });
-
- revealPassword = true;
- };
-
- afterNavigate(async () => {
- try {
- await invalidateAll();
- } catch (e) {
- console.log(e);
- }
- });
-
- let token, formElement;
- let code = [];
- let redirect;
-
- $: need2fa = form?.message === "2fa";
- let cancel = () => (need2fa = false);
- $: if (form?.message === "2fa" && form.token === token) token = "";
-
- let email, btn;
-
- $: update(form);
- let update = (form) => form && ({ username, password: $password } = form);
-
- $: token && token?.length === 6 && tick().then(() => btn.click());
-
- let revealPassword;
-
- 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]);
- }
-
- data.set("profile", punks[index]);
- if ($avatar) {
- try {
- let { hash } = JSON.parse(
- await upload($avatar.file, $avatar.type, $avatar.progress, token)
- );
-
- data.set("profile", hash);
- await fetch(`/api/public/${hash}.webp`, {
- cache: "reload",
- mode: "no-cors",
- });
- } catch (e) {
- console.log("problem uploading avatar", e);
- }
- }
-
- const response = await fetch("/register", {
- method: "POST",
- body: data,
- });
-
- const result = deserialize(await response.text());
-
- if (result.type === "success") {
- await invalidateAll();
- }
-
- applyAction(result);
- loading = false;
- }
-
- let avatarInput;
- $: src = `/api/public/${punks[index]}.webp`;
- let decr = () => (index = index <= 0 ? 63 : index - 1);
- let incr = () => (index = index >= 63 ? 0 : index + 1);
- let selectAvatar = () => avatarInput.click();
-
- let progress;
- let handleFile = async ({ target }) => {
- let type = "profile";
- let file = target.files[0];
- if (!file) return;
-
- if (file.size > 10000000) form.error = "File too large";
- $avatar = { file, type, progress };
-
- var reader = new FileReader();
- reader.onload = async (e) => {
- src = e.target.result;
- };
-
- reader.readAsDataURL(file);
- };
-</script>
-
-{#if need2fa}
- <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} />
-{/if}
-
-<div class="pt-4">
- <div class="w-[243px] mx-auto">
- <a href="/">
- <Icon icon="logo" />
- </a>
- </div>
-
- <div class="flex justify-center items-center">
- <div
- class="shadow-xl rounded-3xl p-4 pt-0 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"
- >
- <input
- type="file"
- class="hidden"
- bind:this={avatarInput}
- on:change={(e) => handleFile(e, "profile")}
- />
-
- <div class="relative">
- <button
- class="absolute w-8 h-12 left-12 bg-white rounded top-12"
- on:click={decr}
- >
- <Icon icon="chevron-left" style="w-8 " />
- </button>
- <button
- class="block relative w-32 mx-auto"
- on:click={selectAvatar}
- >
- <button
- class="w-32 h-32 rounded-full border-4 border-white overflow-hidden flex mx-auto relative"
- >
- <img
- {src}
- class="w-full h-full object-cover object-center overflow-hidden"
- alt={username}
- />
- </button>
- <button
- class="absolute bg-white rounded-full p-2 mx-auto right-0 bottom-0 z-10 bg-opacity-80"
- >
- <Icon icon="upload" style="w-8" />
- </button>
- </button>
- <button
- class="absolute w-8 h-12 right-12 bg-white rounded top-12"
- on:click={incr}
- >
- <Icon icon="chevron-left" style="w-8 rotate-180" />
- </button>
- </div>
-
- {#if form?.error}
- <div class="text-red-600 text-center" in:fly>
- {form.error}
- </div>
- {/if}
-
- <form
- class="space-y-5"
- on:submit|preventDefault={handleSubmit}
- method="POST"
- >
- <input
- type="hidden"
- name="loginRedirect"
- value={$loginRedirect || $page.url.searchParams.get("redirect")}
- />
- <input type="hidden" name="token" value={token} />
-
- <div class="relative">
- <label for="username" class="font-semibold"
- >{$t("login.username")}</label
- >
- <input
- use:focus
- name="username"
- type="text"
- required
- class="bg-primary"
- bind:value={username}
- on:focus={clear}
- autocapitalize="none"
- />
- <button
- tabindex="-1"
- type="button"
- on:click={refresh}
- class="absolute right-0 top-4"
- >
- <Icon icon="random" style="w-10 m-4" />
- </button>
- </div>
-
- <div class="relative">
- <label for="password" class="block font-semibold"
- >{$t("login.password")}</label
- >
- {#if revealPassword}
- <input
- 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"
- on:focus={clear}
- />
- {/if}
- <button
- type="button"
- tabindex="-1"
- on:click={() => (revealPassword = !revealPassword)}
- class="absolute right-0 top-4"
- >
- <Icon icon={revealPassword ? "eye" : "eye-off"} style="m-6" />
- </button>
- </div>
-
- <p class="text-secondary text-sm">
- {$t("login.passwordRecommendation")}
- </p>
-
- <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.register")}
- {/if}
- </button>
- </form>
-
- <p class="text-secondary text-center font-medium">
- {$t("login.haveAccount")}
- <a
- href={"/login"}
- class="block md:inline text-black underline underline-offset-4 hover:opacity-80"
- >
- {$t("login.signIn")}
- </a>
- </p>
- </div>
- </div>
-</div>
diff --git a/src/routes/(app)/scan/+page.svelte b/src/routes/(app)/scan/+page.svelte
index e23b9bb8..150a66f4 100644
--- a/src/routes/(app)/scan/+page.svelte
+++ b/src/routes/(app)/scan/+page.svelte
@@ -21,10 +21,6 @@
onDestroy(() => scanner?.stop());
</script>
-<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}>
- <Icon icon="arrow-left" style="w-10" />
-</button>
-
<div class="flex w-full mb-4 px-4">
<div class="bg-black mx-auto rounded-3xl">
<video
diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte
index 780f70c1..faa4a766 100644
--- a/src/routes/(app)/send/[...text]/+page.svelte
+++ b/src/routes/(app)/send/[...text]/+page.svelte
@@ -5,7 +5,6 @@
import { tick } from "svelte";
import { browser } from "$app/environment";
import { t } from "$lib/translations";
- import AppHeader from "$comp/AppHeader.svelte";
import Avatar from "$comp/Avatar.svelte";
import Icon from "$comp/Icon.svelte";
import Spinner from "$comp/Spinner.svelte";
@@ -19,7 +18,7 @@
let el, text, pasted, w;
- let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click());
+ let keypress = (e) => e.key === "Enter" ? (e.preventDefault() || el.click()) : (pasted = false);
let paste = async () => {
text = await navigator.clipboard.readText();
@@ -31,7 +30,6 @@
<svelte:window bind:innerWidth={w} />
-<AppHeader {data} />
<div class="container px-4 max-w-lg mx-auto space-y-5 mt-20">
<h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
{$t("payments.send")}
@@ -54,7 +52,7 @@
name="text"
placeholder={$t("user.send.placeholder")}
on:keypress={keypress}
- class="w-full p-4 border rounded-xl h-48 text-xl"
+ class="w-full p-4 border rounded-xl h-32 text-xl"
bind:value={text}
on:paste={() => (pasted = true)}
autocapitalize="none"
@@ -65,7 +63,7 @@
<a href="/scan" class="w-full">
<button
type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center gap-2"
+ class="flex border rounded-2xl px-5 py-4 hover:opacity-80 w-full justify-center gap-2"
>
<Icon icon="camera" style="w-8 my-auto" />
<div class="my-auto">{$t("user.send.scan")}</div>
@@ -74,7 +72,7 @@
<button
type="button"
- class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center gap-2"
+ class="flex border rounded-2xl px-5 py-4 hover:opacity-80 w-full justify-center gap-2"
on:click={paste}
>
<Icon icon="paste" style="w-8 my-auto" />
@@ -97,7 +95,7 @@
type="button"
class="flex border rounded-2xl px-6 py-5 w-full font-bold gap-2 justify-center text-xl bg-primary"
>
- <Icon icon="voucher" style="w-8 my-auto" />
+ <img src="/images/cash.png" class="w-8" />
<div class="my-auto">{$t("payments.createEcash")}</div>
</button>
</a>
diff --git a/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts
new file mode 100644
index 00000000..a6edf8b0
--- /dev/null
+++ b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts
@@ -0,0 +1,23 @@
+import { auth, get } from "$lib/utils";
+import { redirect } from "@sveltejs/kit";
+
+export async function load({ cookies, params, parent }) {
+ let { user } = await parent();
+ let aid = cookies.get("aid");
+ let { address } = params;
+ let { balance } = await get(`/account/${aid}`, auth(cookies));
+
+ try {
+ let invoice = await get(`/invoice/${address}`);
+
+ if (invoice) {
+ let recipient = await get(`/users/${invoice.uid}`);
+ if (recipient?.id !== user.id)
+ redirect(307, `/pay/${recipient.username}`);
+ }
+ } catch (e) {
+ // invoice not found
+ }
+
+ return { balance };
+}
diff --git a/src/routes/(app)/send/bitcoin/[address]/+page.server.ts b/src/routes/(app)/send/bitcoin/[address]/+page.server.ts
new file mode 100644
index 00000000..a9b72787
--- /dev/null
+++ b/src/routes/(app)/send/bitcoin/[address]/+page.server.ts
@@ -0,0 +1,7 @@
+import { auth, get } from "$lib/utils";
+
+export async function load({ cookies }) {
+ let aid = cookies.get("aid");
+ let { balance } = await get(`/account/${aid}`, auth(cookies));
+ return { balance };
+}
diff --git a/src/routes/(app)/send/bitcoin/[address]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/+page.svelte
index 838c5c04..6b938c38 100644
--- a/src/routes/(app)/send/bitcoin/[address]/+page.svelte
+++ b/src/routes/(app)/send/bitcoin/[address]/+page.svelte
@@ -8,9 +8,9 @@
export let data;
- let { rates, user } = data;
+ let { balance, rates, user } = data;
let { address } = $page.params;
- let { balance, currency, username } = user;
+ let { currency, username } = user;
let amount = 0;
let submit, fiat;
@@ -24,12 +24,6 @@
};
</script>
-<a href="/send">
- <button type="button" class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80">
- <Icon icon="arrow-left" style="w-10" />
- </button>
-</a>
-
<div class="container 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>
diff --git a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts
index 7efe6e49..e46a9f56 100644
--- a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts
+++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts
@@ -1,15 +1,29 @@
import { fail, redirect } from "@sveltejs/kit";
-import { auth, post, fd } from "$lib/utils";
+import { auth, get, post, fd } from "$lib/utils";
export async function load({ params: { address, amount, feeRate }, cookies }) {
+ let aid = cookies.get("aid");
+
try {
- let { fee, fees, ourfee, hex } = await post(
+ let { fee, fees, inputs, ourfee, hex } = await post(
"/bitcoin/fee",
- { address, amount, feeRate },
+ { address, amount, feeRate, aid },
auth(cookies)
);
- return { amount, address, fee, fees, feeRate, ourfee, hex };
+ let account = await get(`/account/${aid}`, auth(cookies));
+
+ return {
+ account,
+ amount,
+ address,
+ fee,
+ fees,
+ feeRate,
+ ourfee,
+ hex,
+ inputs,
+ };
} catch (e: any) {
return { amount, address, feeRate, message: e.message };
}
@@ -21,13 +35,15 @@ export const actions = {
params: { address, amount, feeRate },
request,
}) => {
+ let p;
try {
- await post("/bitcoin/send", await fd(request), auth(cookies));
+ let body = await fd(request);
+ p = await post("/bitcoin/send", body, auth(cookies));
} catch (e: any) {
console.log("problem sending bitcoin", e);
return fail(400, { address, amount, feeRate, message: e.message });
}
- redirect(307, "/sent");
+ redirect(307, `/sent/${p.id}`);
},
};
diff --git a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte
index 84506e6b..ba87436f 100644
--- a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte
+++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte
@@ -1,20 +1,96 @@
<script>
+ import { tick } from "svelte";
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 WalletPass from "$comp/WalletPass.svelte";
import { page } from "$app/stores";
- import { fiat as toFiat, f, focus, s, sat, closest } from "$lib/utils";
+ import {
+ fiat as toFiat,
+ f,
+ focus,
+ s,
+ sat,
+ closest,
+ network,
+ } from "$lib/utils";
import { pin } from "$lib/store";
import { goto, invalidate } from "$app/navigation";
import { rate } from "$lib/store";
+ import { applyAction } from "$app/forms";
+ import { hex as hexUtil } from "@scure/base";
+ import * as btc from "@scure/btc-signer";
+ import { hash160 } from "@scure/btc-signer/utils";
+ import { decrypt } from "nostr-tools/nip49";
+ import { HDKey } from "@scure/bip32";
+ import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39";
+ import { wordlist } from "@scure/bip39/wordlists/english";
export let data;
export let form;
- let { amount, address, message, fee, fees, feeRate, ourfee, rates, hex } =
- data;
+ let { encode, decode } = hexUtil;
+ let passwordPrompt;
+ let password;
+ let cancel = () => (passwordPrompt = false);
+ let signed;
+
+ let togglePassword = () => (passwordPrompt = !passwordPrompt);
+ let handler = ({ cancel }) => {
+ if (account.seed && !signed) cancel(), togglePassword();
+ return async ({ result }) => {
+ if (result.type === "redirect") {
+ goto(result.location);
+ } else {
+ await applyAction(result);
+ invalidate("app:payments");
+ }
+ };
+ };
+
+ let signTx = async () => {
+ let entropy = await decrypt(account.seed, password);
+ let mnemonic = entropyToMnemonic(entropy, wordlist);
+ let seed = await mnemonicToSeed(mnemonic, password);
+ let master = HDKey.fromMasterSeed(seed, network);
+ let child = master.derive("m/84'/0'/0'");
+
+ let tx = btc.Transaction.fromRaw(decode(hex));
+
+ for (let [i, input] of tx.inputs.entries()) {
+ let { witnessUtxo, path } = inputs[i];
+ witnessUtxo.amount = BigInt(witnessUtxo.amount);
+ witnessUtxo.script = decode(witnessUtxo.script);
+ let key = child.derive(path);
+ tx.updateInput(i, { witnessUtxo });
+ tx.signIdx(key.privateKey, i);
+ }
+
+ tx.finalize();
+ hex = tx.hex;
+ console.log("HEX", hex);
+ signed = true;
+ await tick();
+ submit.click();
+ };
+
+ let toggle = () => (submitting = !submitting);
+
+ let {
+ account,
+ amount,
+ address,
+ message,
+ fee,
+ fees,
+ feeRate,
+ ourfee,
+ rates,
+ hex,
+ inputs,
+ } = data;
$: reload(data);
let reload = () => {
@@ -48,15 +124,6 @@
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"
>
@@ -115,10 +182,12 @@
</div>
{/if}
- <form method="POST" use:enhance on:submit={() => (submitting = true)}>
+ <form method="POST" use:enhance={handler}>
<input name="pin" value={$pin} type="hidden" />
<input name="hex" value={hex} type="hidden" />
<input name="rate" value={$rate} type="hidden" />
+ <input name="aid" value={account.id} type="hidden" />
+ <input name="signed" value={signed} type="hidden" />
<div class="flex justify-center gap-2">
<button
@@ -139,6 +208,10 @@
{/if}
</div>
+{#if passwordPrompt}
+ <WalletPass bind:password bind:cancel submit={signTx} />
+{/if}
+
<style>
.no-transition {
view-transition-name: fee;
diff --git a/src/routes/(app)/send/ecash/+page.svelte b/src/routes/(app)/send/ecash/+page.svelte
index 15c7c5d3..ef8e368c 100644
--- a/src/routes/(app)/send/ecash/+page.svelte
+++ b/src/routes/(app)/send/ecash/+page.svelte
@@ -29,25 +29,15 @@
let toggle = () => (submitting = !submitting);
</script>
-<a href="/send">
- <button type="button" class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80">
- <Icon icon="arrow-left" style="w-10" />
- </button>
-</a>
-
<div class="container 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.createEcash")}
- </h1>
-
<form use:enhance={handler(toggle)} method="POST">
<Numpad bind:amount bind:fiat {currency} {submit} bind:rate={$rate} />
<input type="hidden" name="amount" bind:value={amount} />
- <div class="flex justify-center gap-2">
+ <div>
<button
type="button"
- class="hover:opacity-80 bg-black text-white rounded-2xl py-3 px-4 mt-2 border font-bold"
+ class="hover:opacity-80 rounded-2xl py-3 px-4 mt-2 border font-bold w-full max-w-[340px] mx-auto"
on:click|preventDefault={setMax}
disabled={submitting}
on:keydown={setMax}>Max ⚡️{s(balance)}</button
@@ -57,12 +47,13 @@
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"
+ class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-full flex justify-center gap-2 max-w-[340px] mx-auto"
>
{#if submitting}
<Spinner />
{:else}
- {$t("payments.mint")}
+ <img src="/images/cash.png" class="w-8" />
+ <div class="my-auto">{$t("payments.mint")}</div>
{/if}
</button>
</div>
diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.ts b/src/routes/(app)/send/invoice/[id]/+page.server.ts
index ed0198cf..6c89912d 100644
--- a/src/routes/(app)/send/invoice/[id]/+page.server.ts
+++ b/src/routes/(app)/send/invoice/[id]/+page.server.ts
@@ -7,31 +7,32 @@ export async function load({ params: { id }, parent }) {
let invoice = await get(`/invoice/${id}`);
if (invoice.prompt && invoice.tip === null)
- redirect(307, `/${invoice.user.username}/invoice/${id}/tip`);
+ redirect(307, `/invoice/${id}/tip`);
if (invoice.memoPrompt && invoice.memo === null)
- redirect(307, `/${invoice.user.username}/invoice/${id}/memo`);
+ redirect(307, `/invoice/${id}/memo`);
- if (invoice.user.username === user?.username)
+ if (invoice.account === user?.id)
error(500, { message: "Cannot send to self" });
- if (!user) redirect(307, `/${invoice.user.username}/invoice/${id}`);
+ if (!user) redirect(307, `/invoice/${id}`);
return { invoice, rates, user };
}
export const actions = {
default: async ({ cookies, params: { id }, request }) => {
+ let p;
try {
let body = await fd(request);
body.hash = id;
- await post("/payments", body, auth(cookies));
+ p = await post("/payments", body, auth(cookies));
} catch (e: any) {
console.log("payment failed", id, e);
error(500, e.message);
}
- redirect(307, "/sent");
+ redirect(307, `/sent/${p.id}`);
},
};
diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte
index 81a2c521..4348e473 100644
--- a/src/routes/(app)/send/invoice/[id]/+page.svelte
+++ b/src/routes/(app)/send/invoice/[id]/+page.svelte
@@ -52,7 +52,7 @@
let toggle = () => (submitting = !submitting);
let external = async () => {
- let { id } = await post(`/${recipient.username}/invoice`, {
+ let { id } = await post(`/invoice`, {
invoice: {
...invoice,
type: "lightning",
@@ -62,7 +62,7 @@
user: { username: recipient.username },
});
- goto(`/${recipient.username}/invoice/${id}?options=true`, {
+ goto(`/invoice/${id}?options=true`, {
invalidateAll: true,
});
};
@@ -93,10 +93,6 @@
// });
</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}
diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.server.ts b/src/routes/(app)/send/lightning/[payreq]/+page.server.ts
index ddec2968..3e87fa53 100644
--- a/src/routes/(app)/send/lightning/[payreq]/+page.server.ts
+++ b/src/routes/(app)/send/lightning/[payreq]/+page.server.ts
@@ -9,16 +9,17 @@ export const actions = {
setAmount: async ({ request }) => fd(request),
send: async ({ cookies, request }) => {
+ let p;
try {
let body = await fd(request);
- await post("/payments", body, auth(cookies));
+ p = await post("/payments", body, auth(cookies));
} catch (e: any) {
if (e.message.includes("unusable"))
e.message = "Failed to route payment, try sending a lower amount";
return fail(400, { message: e.message });
}
- redirect(307, "/sent");
+ redirect(307, `/sent/${p.id}`);
},
};
diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte
index 57a17e01..74c100f8 100644
--- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte
+++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte
@@ -58,11 +58,7 @@
let toggle = () => (show = !show);
</script>
-<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}>
- <Icon icon="arrow-left" style="w-10" />
-</button>
-
-<div class="container px-4 max-w-xl mx-auto text-center space-y-5">
+<div class="container px-4 max-w-xl mx-auto text-center space-y-2">
{#if amount}
{#if form?.message}
<div class="text-red-600 text-center">
@@ -81,12 +77,9 @@
<h3 class="text-secondary text-xl">⚡️{s(amount)}</h3>
</div>
- <div>
- <div class="text-lg text-secondary">
- {$t("payments.to")}
- </div>
-
- <p class="text-2xl break-words font-semibold mb-8">{alias}</p>
+ <div class="text-xl">
+ <span class="text-secondary">{$t("payments.to")}</span>
+ <span class="break-words font-semibold">{alias}</span>
</div>
{#if ourfee}
@@ -109,7 +102,7 @@
use:enhance
on:submit={submit}
action="?/send"
- class="space-y-5"
+ class="space-y-2"
>
<input name="payreq" value={payreq} type="hidden" />
<input name="amount" value={amount} type="hidden" />
diff --git a/src/routes/(app)/sent/+page.server.ts b/src/routes/(app)/sent/+page.server.ts
deleted file mode 100644
index 62a4d9b4..00000000
--- a/src/routes/(app)/sent/+page.server.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { get, auth } from "$lib/utils";
-
-export let load = async ({ cookies }) => {
- let { payments } = await get("/payments", auth(cookies));
- return { payments };
-};
diff --git a/src/routes/(app)/sent/[id]/+page.server.ts b/src/routes/(app)/sent/[id]/+page.server.ts
new file mode 100644
index 00000000..38d95e4e
--- /dev/null
+++ b/src/routes/(app)/sent/[id]/+page.server.ts
@@ -0,0 +1,7 @@
+import { get, auth } from "$lib/utils";
+
+export let load = async ({ cookies, params }) => {
+ let { id } = params;
+ let p = await get(`/payments/${id}`, auth(cookies));
+ return p;
+};
diff --git a/src/routes/(app)/sent/+page.svelte b/src/routes/(app)/sent/[id]/+page.svelte
index 136bd395..ce2c8d6e 100644
--- a/src/routes/(app)/sent/+page.svelte
+++ b/src/routes/(app)/sent/[id]/+page.svelte
@@ -6,11 +6,8 @@
import { t } from "$lib/translations";
export let data;
- let { payments, user } = data;
+ let { amount, rate, tip, user } = data;
let { currency, username } = user;
-
- let payment = payments[0];
- let { amount, rate, tip } = payment;
amount = Math.abs(amount);
toast.pop(0);
@@ -18,7 +15,7 @@
<div class="text-center mt-20 md:mt-0">
<div
- class="flex w-full max-w-[200px] mx-auto py-20"
+ class="flex w-full max-w-[200px] mx-auto py-4"
in:scale={{ start: 0.5 }}
>
<Icon icon="check" style="mx-auto" />
@@ -43,7 +40,7 @@
</h3>
</div>
-<a href={`/${username}/payments`} use:focus>
+<a href={`/${username}`} use:focus>
<div class="opacity-0 w-screen h-screen fixed top-0 left-0 z-50" />
</a>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts b/src/routes/(app)/settings/[...tab]/+page.server.ts
index 42639b38..42639b38 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/+page.server.ts
+++ b/src/routes/(app)/settings/[...tab]/+page.server.ts
diff --git a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte b/src/routes/(app)/settings/[...tab]/+page.svelte
index 12980291..f59806d1 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/+page.svelte
+++ b/src/routes/(app)/settings/[...tab]/+page.svelte
@@ -137,7 +137,7 @@
content: JSON.stringify({
name: user.username,
about: user.address,
- picture: `${PUBLIC_COINOS_URL}/public/${user.profile}.webp`,
+ picture: `${$page.url.origin}/public/${user.profile}.webp`,
}),
tags: [],
};
@@ -206,7 +206,7 @@
<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="mt-24 mb-20 px-3 md:px-0 w-full md:max-w-lg mx-auto space-y-8">
<div class="header">
<h1 class="text-center text-3xl md:text-4xl font-semibold mb-10">
{$t("user.settings.header")}
@@ -216,7 +216,7 @@
class="font-bold flex justify-around items-center border-b pb-3 text-secondary"
>
{#each tabs.filter((t) => t.name !== "shopify") as { name, key }}
- <a href={`/${user.username}/settings/${name}`}>
+ <a href={`/settings/${name}`}>
<button
type="button"
class="hover:opacity-80"
@@ -249,9 +249,3 @@
</button>
</div>
</form>
-
-<style>
- .settings {
- view-transition-name: settings;
- }
-</style>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte b/src/routes/(app)/settings/[...tab]/_account.svelte
index 8e3323c7..8e3323c7 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_account.svelte
+++ b/src/routes/(app)/settings/[...tab]/_account.svelte
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte b/src/routes/(app)/settings/[...tab]/_pos.svelte
index 6a1c92a1..dcfcbe44 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_pos.svelte
+++ b/src/routes/(app)/settings/[...tab]/_pos.svelte
@@ -63,17 +63,6 @@
</div>
<div>
- <label for="memoPrompt" class="font-bold mb-1 block"
- >{$t("user.settings.memoPrompt")}</label
- >
- <textarea
- name="memoPrompt"
- placeholder={$t("user.settings.memoPromptPlaceholder")}
- bind:value={user.memoPrompt}
- />
-</div>
-
-<div>
<div class="flex justify-between items-center">
<span class="font-bold">{$t("user.settings.notifications")}</span>
<Toggle id="notify" bind:value={user.notify} />
@@ -95,18 +84,6 @@
<div>
<div class="flex justify-between items-center">
- <span class="font-bold">{$t("user.settings.publishNip5")}</span>
- <Toggle id="nip5" bind:value={user.nip5} />
- </div>
- <p class="text-secondary mt-1 w-9/12">
- {$t("user.settings.nip5a")}
- <span class="font-bold">{user.username}@{$page.url.host}</span>
- {$t("user.settings.nip5b")}
- </p>
-</div>
-
-<div>
- <div class="flex justify-between items-center">
<span class="font-bold">{$t("user.settings.autoWithdraw")}</span>
<Toggle id="autowithdraw" bind:value={user.autowithdraw} />
</div>
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte b/src/routes/(app)/settings/[...tab]/_security.svelte
index 6b44f830..6b44f830 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_security.svelte
+++ b/src/routes/(app)/settings/[...tab]/_security.svelte
diff --git a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte b/src/routes/(app)/settings/[...tab]/_shopify.svelte
index 0266fa9c..0266fa9c 100644
--- a/src/routes/(app)/[username]/settings/[...tab]/_shopify.svelte
+++ b/src/routes/(app)/settings/[...tab]/_shopify.svelte
diff --git a/src/routes/(app)/support/+page.svelte b/src/routes/(app)/support/+page.svelte
deleted file mode 100644
index ad31ad1a..00000000
--- a/src/routes/(app)/support/+page.svelte
+++ /dev/null
@@ -1,139 +0,0 @@
-<script>
- import { onDestroy } from "svelte";
- import { fail, success, post } from "$lib/utils";
- import { scale } from "svelte/transition";
- import AppHeader from "$comp/AppHeader.svelte";
- import Icon from "$comp/Icon.svelte";
- import { t } from "$lib/translations";
-
- export let data;
-
- let { user } = data;
- $: if (user) {
- data.subject = { ...user };
- }
-
- let email;
- let message;
- let sent;
- let recaptchaSiteKey = "6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM";
-
- let submit = (e) => {
- e.preventDefault();
- grecaptcha.ready(() => {
- grecaptcha.execute(recaptchaSiteKey, { action: "submit" }).then((token) =>
- post("/support", { username: user?.username, email, message, token })
- .then(() => (sent = true))
- .catch(() => fail("problem submitting"))
- );
- });
- };
-
- onDestroy(() => {
- const nodeBadge = document.querySelector(".grecaptcha-badge");
- if (nodeBadge) {
- document.body.removeChild(nodeBadge.parentNode);
- }
-
- const scriptSelector =
- "script[src='https://www.google.com/recaptcha/api.js?render=" +
- recaptchaSiteKey +
- "']";
- const script = document.querySelector(scriptSelector);
- if (script) {
- script.remove();
- }
- });
-</script>
-
-<svelte:head
- ><script
- src="https://www.google.com/recaptcha/api.js?render=6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM"
- ></script></svelte:head
->
-
-{#if user}
- <AppHeader {data} />
-{/if}
-
-<div class="container px-4 max-w-lg mx-auto mt-20 space-y-8">
- {#if sent}
- <h1 class="text-center text-3xl md:text-4xl font-semibold">Thank you!</h1>
- <div class="text-center text-lg text-secondary">
- Someone will be in touch shortly.
- </div>
-
- <div>
- <a href={user ? `/${user.username}` : "/"}>
- <button
- class="rounded-full border py-2 px-5 font-bold hover:opacity-80 w-full bg-black text-white"
- >Ok</button
- >
- </a>
- </div>
- {:else}
- <h1 class="text-center text-3xl md:text-4xl font-semibold mb-8">
- {$t("user.support.header")}
- </h1>
-
- <p class="text-secondary text-lg">
- Fill out this form or email us directly at <a
- class="underline"
- > and we'll do our best to get back to you in a timely manner.
- </p>
-
- <form on:submit={submit}>
- <div class="mb-4">
- <label for="account" class="font-semibold"
- >{$t("user.support.accountName")}</label
- >
- <input
- class="bg-primary"
- type="text"
- name="account"
- required
- value={user?.username || ""}
- />
- </div>
-
- <div class="mb-4">
- <label for="email" class="font-semibold"
- >{$t("user.support.email")}</label
- >
- <input
- class="bg-primary"
- type="email"
- name="email"
- bind:value={email}
- required
- />
- </div>
-
- <div class="mb-4">
- <label for="message" class="font-semibold"
- >{$t("user.support.message")}</label
- >
- <textarea
- rows={5}
- class="bg-primary"
- type="text"
- name="message"
- bind:value={message}
- required
- />
- </div>
-
- <button
- type="submit"
- disabled={!email || !message}
- class="g-recaptcha {!email || !message
- ? 'opacity-50'
- : 'opacity-100'} bg-black text-white font-bold rounded-xl py-3 w-full mx-auto {email &&
- message
- ? 'hover:opacity-80'
- : ''}">{$t("user.support.send")}</button
- >
- </form>
- {/if}
-</div>
diff --git a/src/routes/(app)/support/+server.ts b/src/routes/(app)/support/+server.ts
deleted file mode 100644
index e9a16cfc..00000000
--- a/src/routes/(app)/support/+server.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { json } from "@sveltejs/kit";
-import { auth, post } from "$lib/utils";
-
-export const prerender = false;
-
-export async function POST({ cookies, request }) {
- let result = await post("/email", await request.json(), auth(cookies));
- return json(result);
-}
diff --git a/src/routes/(app)/[username]/verify/[code]/+page.server.ts b/src/routes/(app)/verify/[code]/+page.server.ts
index fec1a85c..66ef39ad 100644
--- a/src/routes/(app)/[username]/verify/[code]/+page.server.ts
+++ b/src/routes/(app)/verify/[code]/+page.server.ts
@@ -10,5 +10,5 @@ export let load = async ({ params: { code } }) => {
return { error: "Verification failed" };
}
- redirect(307, `/${user.username}/settings/account?verified=true`);
+ redirect(307, `/settings/account?verified=true`);
};
diff --git a/src/routes/(app)/[username]/verify/[code]/+page.svelte b/src/routes/(app)/verify/[code]/+page.svelte
index 1e099dae..1e099dae 100644
--- a/src/routes/(app)/[username]/verify/[code]/+page.svelte
+++ b/src/routes/(app)/verify/[code]/+page.svelte