aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.css16
-rw-r--r--src/components/Avatar.svelte3
-rw-r--r--src/components/Mnemonic.svelte6
-rw-r--r--src/components/Numpad.svelte4
-rw-r--r--src/components/Slider.svelte47
-rw-r--r--src/lib/parse.ts22
-rw-r--r--src/routes/(app)/account/pass/+page.svelte57
-rw-r--r--src/routes/(app)/account/savings/+page.svelte4
-rw-r--r--src/routes/(app)/account/seed/+page.svelte43
-rw-r--r--src/routes/(app)/logout/+page.server.ts1
-rw-r--r--src/routes/(app)/payment/[id]/+page.svelte20
-rw-r--r--src/routes/(app)/payments/[...page]/+page.svelte27
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.server.ts22
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/+page.svelte24
14 files changed, 126 insertions, 170 deletions
diff --git a/src/app.css b/src/app.css
index 3d3ffd94..f01c9346 100644
--- a/src/app.css
+++ b/src/app.css
@@ -21,7 +21,7 @@ html {
background-color: white;
}
-input {
+input:not([type="range"]) {
@apply input
}
@@ -37,7 +37,7 @@ select {
@apply select select-bordered select-lg select-primary;
}
-input, textarea, select, .input {
+input:not([type="range"]), textarea, select, .input {
@apply w-full rounded-2xl;
}
@@ -46,12 +46,12 @@ textarea:focus, textarea:focus-within, .input:focus, .input:focus-within {
}
input.clean, textarea.clean {
- border: none;
- outline: none;
- padding: 0;
- margin: 0;
- background: none;
- box-shadow: none;
+ border: none !important;
+ outline: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ background: none !important;
+ box-shadow: none !important;
}
.btn {
diff --git a/src/components/Avatar.svelte b/src/components/Avatar.svelte
index 467b825e..5b5ce713 100644
--- a/src/components/Avatar.svelte
+++ b/src/components/Avatar.svelte
@@ -1,8 +1,5 @@
<script lang="ts">
- import { run } from "svelte/legacy";
-
import { avatar, banner } from "$lib/store";
- import Icon from "$comp/Icon.svelte";
import { punk } from "$lib/utils";
interface Props {
diff --git a/src/components/Mnemonic.svelte b/src/components/Mnemonic.svelte
index 93035230..117c98f2 100644
--- a/src/components/Mnemonic.svelte
+++ b/src/components/Mnemonic.svelte
@@ -2,13 +2,13 @@
let { mnemonic } = $props();
</script>
-<div class="flex w-full border p-4 rounded-2xl h-full">
+<div class="card">
<div
- class="text-secondary grid grid-flow-col grid-rows-6 sm:grid-rows-none grid-cols-2 sm:grid-cols-3 sm:grid-flow-row gap-x-8 gap-y-2 justify-center whitespace-nowrap m-auto"
+ class="card-body text-secondary grid grid-flow-col grid-rows-6 sm:grid-rows-none grid-cols-2 sm:grid-cols-3 sm:grid-flow-row gap-x-8 gap-y-2 justify-center whitespace-nowrap m-auto"
>
{#each mnemonic.split(" ") as w, i}
<div>
- <span class="select-none text-black">{i + 1}.</span>
+ <span class="select-none text-neutral">{i + 1}.</span>
<span>{@html w === "-" ? "&mdash;" : w}</span>
</div>
{/each}
diff --git a/src/components/Numpad.svelte b/src/components/Numpad.svelte
index bbafc196..7cda6a4f 100644
--- a/src/components/Numpad.svelte
+++ b/src/components/Numpad.svelte
@@ -19,9 +19,9 @@
$: update(amount, amountFiat);
let update = (a, f) => {
if ($fiat) {
- amount = Math.round(f / (rate / sats));
+ amount = f ? Math.round(f / (rate / sats)) : 0;
} else {
- amountFiat = ((a * rate) / sats).toFixed(2);
+ amountFiat = a ? ((a * rate) / sats).toFixed(2) : 0;
}
};
diff --git a/src/components/Slider.svelte b/src/components/Slider.svelte
index 137c28b4..401b415a 100644
--- a/src/components/Slider.svelte
+++ b/src/components/Slider.svelte
@@ -6,12 +6,7 @@
max?: number;
}
- let {
- value = $bindable(),
- handle,
- min = 0,
- max = 100
- }: Props = $props();
+ let { value = $bindable(), handle, min = 0, max = 100 }: Props = $props();
</script>
<input
@@ -20,46 +15,8 @@
{min}
{max}
bind:value
- class="px-0 py-0 my-5"
- style="--bgPercent: {((value - min) * 100) / (max - min)}%;"
+ class="range"
oninput={handle}
/>
-<style>
- #slider {
- -webkit-appearance: none;
- width: 100%;
- height: 6px;
- border-radius: 5px;
- background: linear-gradient(
- to right,
- black 0%,
- black var(--bgPercent),
- #f5f7fa var(--bgPercent),
- #f5f7fa 100%
- );
- outline: none;
- }
-
- #slider:active {
- background-color: black;
- }
- #slider::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background: black;
- cursor: pointer;
- }
-
- #slider::-moz-range-thumb {
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background: black;
- cursor: pointer;
- }
-</style>
diff --git a/src/lib/parse.ts b/src/lib/parse.ts
index cdfa4d62..e4bd3497 100644
--- a/src/lib/parse.ts
+++ b/src/lib/parse.ts
@@ -1,5 +1,5 @@
import { PUBLIC_DOMAIN } from "$env/static/public";
-import { get, isLiquid, post } from "$lib/utils";
+import { get, sats, isLiquid, post } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
import bip21 from "bip21";
import { validate } from "bitcoin-address-validation";
@@ -21,11 +21,6 @@ export default async (s, host) => {
let user;
t = t.trim();
- if (t.toLowerCase().startsWith("bitcoin:"))
- ({
- address: t,
- options: { amount },
- } = bip21.decode(t));
if (t.startsWith("lightning:")) t = t.replace("lightning:", "");
if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0];
@@ -44,11 +39,12 @@ export default async (s, host) => {
if (t.includes(":")) t = t.split(":")[1];
if (t.toLowerCase().startsWith("lno")) {
- const { invoice } = await get(`/offer/${t}`);
- redirect(307, `/send/lightning/${invoice}`);
+ const { offer_amount_msat: a } = await get(`/decode/${t}`);
+ if (a) ({ invoice: t } = await post("/fetchinvoice", { offer: t }));
+ redirect(307, `/send/lightning/${t}`);
}
- if (t.toLowerCase().startsWith("lno")) {
+ if (t.toLowerCase().startsWith("ln")) {
try {
await get(`/invoice/${t}`);
} catch (e) {
@@ -57,9 +53,15 @@ export default async (s, host) => {
}
// bitcoin
+ if (t.toLowerCase().startsWith("bitcoin:"))
+ ({
+ address: t,
+ options: { amount },
+ } = bip21.decode(t));
+
if (validate(t) || isLiquid(t)) {
let r = `/send/bitcoin/${t}`;
- if (amount) r += `/${amount}`;
+ if (amount) r += `/${Math.round(amount * sats)}`;
redirect(307, r);
}
diff --git a/src/routes/(app)/account/pass/+page.svelte b/src/routes/(app)/account/pass/+page.svelte
index 62ef9b8e..3a372a33 100644
--- a/src/routes/(app)/account/pass/+page.svelte
+++ b/src/routes/(app)/account/pass/+page.svelte
@@ -1,10 +1,9 @@
<script lang="ts">
- import { preventDefault } from 'svelte/legacy';
+ import { preventDefault } from "svelte/legacy";
import { tick, onMount } from "svelte";
import { t } from "$lib/translations";
import { mnemonic } from "$lib/store";
- import Icon from "$comp/Icon.svelte";
import Spinner from "$comp/Spinner.svelte";
import { encrypt } from "nostr-tools/nip49";
import {
@@ -22,7 +21,10 @@
let { user } = data;
let submitting = $state();
- let confirm = $state(), password = $state(), revealPassword = $state(), revealConfirm = $state();
+ let confirm = $state(),
+ password = $state(),
+ revealPassword = $state(),
+ revealConfirm = $state();
let type = "bitcoin";
let name = $t("accounts.savings");
@@ -79,7 +81,10 @@
{$t("accounts.passwordWarning")}
</p>
- <div class="relative">
+ <label
+ for="password"
+ class="input flex items-center justify-center gap-2 w-full"
+ >
{#if revealPassword}
<input
name="password"
@@ -87,6 +92,7 @@
required
bind:value={password}
autocapitalize="none"
+ class="clean"
/>
{:else}
<input
@@ -97,27 +103,31 @@
required
bind:value={password}
autocapitalize="none"
+ class="clean"
/>
{/if}
- <button
- tabindex="-1"
- type="button"
+
+ <iconify-icon
+ class="cursor-pointer ml-auto"
onclick={() => (revealPassword = !revealPassword)}
- class="absolute right-5 -translate-y-1/2 top-1/2"
- >
- <Icon icon={revealPassword ? "eye" : "eye-off"} />
- </button>
- </div>
+ icon={revealPassword ? "ph:eye-bold" : "ph:eye-slash-bold"}
+ width="32"
+ ></iconify-icon>
+ </label>
- <div class="relative">
+ <label
+ for="confirm"
+ class="input flex items-center justify-center gap-2 w-full"
+ >
{#if revealConfirm}
<input
- name="password"
+ name="confirm"
type="text"
placeholder="Confirm password"
required
bind:value={confirm}
autocapitalize="none"
+ class="clean"
/>
{:else}
<input
@@ -126,28 +136,27 @@
required
bind:value={confirm}
autocapitalize="none"
+ class="clean"
/>
{/if}
- <button
- type="button"
- tabindex="-1"
+ <iconify-icon
+ class="cursor-pointer ml-auto"
onclick={() => (revealConfirm = !revealConfirm)}
- class="absolute right-5 -translate-y-1/2 top-1/2"
- >
- <Icon icon={revealConfirm ? "eye" : "eye-off"} />
- </button>
- </div>
+ icon={revealConfirm ? "ph:eye-bold" : "ph:eye-slash-bold"}
+ width="32"
+ ></iconify-icon>
+ </label>
<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">
+ <button type="button" class="btn !w-auto grow">
{$t("accounts.back")}
</button>
</a>
<button
disabled={submitting}
type="submit"
- class="border grow px-5 py-6 text-xl rounded-2xl bg-black text-white"
+ class="btn btn-accent !w-auto grow"
>
{#if submitting}
<Spinner />
diff --git a/src/routes/(app)/account/savings/+page.svelte b/src/routes/(app)/account/savings/+page.svelte
index d0474636..2e860076 100644
--- a/src/routes/(app)/account/savings/+page.svelte
+++ b/src/routes/(app)/account/savings/+page.svelte
@@ -11,7 +11,7 @@
<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="card card-side shadow shadow-base-400 p-8 hover:bg-base-200">
+ <div class="card card-side shadow shadow-base-300 p-8 hover:bg-base-200">
<figure class="w-20">
<iconify-icon icon="ph:file-plus-bold" width="64"></iconify-icon>
</figure>
@@ -22,7 +22,7 @@
</div>
</a>
<a href="/account/import" class="block">
- <div class="card card-side shadow p-8 hover:bg-base-200">
+ <div class="card card-side shadow shadow-base-300 p-8 hover:bg-base-200">
<figure class="w-20">
<iconify-icon icon="ph:folder-open-bold" width="64"></iconify-icon>
</figure>
diff --git a/src/routes/(app)/account/seed/+page.svelte b/src/routes/(app)/account/seed/+page.svelte
index b3e7d57c..a3e65ec2 100644
--- a/src/routes/(app)/account/seed/+page.svelte
+++ b/src/routes/(app)/account/seed/+page.svelte
@@ -4,7 +4,6 @@
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";
@@ -27,47 +26,31 @@
{$t("accounts.seedPhrase")}
</h1>
- <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5">
+ <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-2">
{#if $mnemonic}
<Mnemonic mnemonic={$mnemonic} />
{/if}
- <div>
- <button
- type="button"
- class="flex gap-2 text-center break-all rounded-2xl hover:opacity-80 py-3 px-4 w-full mx-auto justify-center border whitespace-nowrap mb-2"
- onclick={generate}
- >
- <Icon icon="random" style="w-8 my-auto" />
- <div class="my-auto">{$t("accounts.regenerate")}</div>
- </button>
+ <button type="button" class="btn" onclick={generate}>
+ <iconify-icon icon="ph:dice-three-bold" width="32" />
+ <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-3 px-4 w-full mx-auto justify-center border whitespace-nowrap mb-2"
- onclick={() => copy($mnemonic)}
- >
- <Icon icon="copy" style="w-8 my-auto" />
- <div class="my-auto">
- {$t("payments.copy")}
- </div>
- </button>
- </div>
+ <button type="button" class="btn" onclick={() => copy($mnemonic)}>
+ <iconify-icon icon="ph:copy-bold" width="32" />
+ <div class="my-auto">
+ {$t("payments.copy")}
+ </div>
+ </button>
<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"
- >
+ <button type="button" class="btn !w-auto grow">
{$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"
- >
+ <button type="button" class="btn btn-accent !w-auto grow">
{$t("accounts.next")}
</button>
</a>
diff --git a/src/routes/(app)/logout/+page.server.ts b/src/routes/(app)/logout/+page.server.ts
index 2a6fc71f..87ddbb59 100644
--- a/src/routes/(app)/logout/+page.server.ts
+++ b/src/routes/(app)/logout/+page.server.ts
@@ -11,5 +11,6 @@ export async function load({ cookies }) {
cookies.set("lang", "", opts);
cookies.set("username", "", opts);
cookies.set("token", "", opts);
+ cookies.set("pin", "", opts);
redirect(307, `/login?lang=${lang}`);
}
diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte
index 94226931..6171dce9 100644
--- a/src/routes/(app)/payment/[id]/+page.svelte
+++ b/src/routes/(app)/payment/[id]/+page.svelte
@@ -1,6 +1,4 @@
<script>
- import { run } from "svelte/legacy";
-
import { browser } from "$app/environment";
import { onMount, tick } from "svelte";
import { t, loading } from "$lib/translations";
@@ -20,12 +18,12 @@
import { format } from "date-fns";
import { PUBLIC_EXPLORER, PUBLIC_LIQUID_EXPLORER } from "$env/static/public";
import locales from "$lib/locales";
+ import { goto } from "$app/navigation";
let { data } = $props();
let { user, payment: p } = $state(data);
- let locale = locales[user.language];
+ let locale = user ? locales[user.language] : locales["en"];
- let { username } = user;
let {
id,
hash,
@@ -94,16 +92,14 @@
</h1>
{#if p.with}
- <div>
+ <div onclick={() => goto(`/${p.with.username}`)}>
<span class="text-lg text-secondary my-auto mr-2">{direction}</span>
- <a href={`/${p.with.username}`}>
- <div class="flex">
- <div class="my-auto">
- <Avatar user={p.with} size={20} />
- </div>
- <div class="my-auto ml-1">{p.with.username}</div>
+ <div class="flex">
+ <div class="my-auto">
+ <Avatar user={p.with} size={20} />
</div>
- </a>
+ <div class="my-auto ml-1">{p.with.username}</div>
+ </div>
</div>
{/if}
{#if p.type === types.fund}
diff --git a/src/routes/(app)/payments/[...page]/+page.svelte b/src/routes/(app)/payments/[...page]/+page.svelte
index e651ba90..cda7c350 100644
--- a/src/routes/(app)/payments/[...page]/+page.svelte
+++ b/src/routes/(app)/payments/[...page]/+page.svelte
@@ -142,7 +142,7 @@
let showPage = $derived((i) => {
const currentPage = parseInt(p);
- const maxVisiblePages = 12;
+ const maxVisiblePages = 10;
if (i === 0 || i === pages.length - 1) return true;
@@ -168,11 +168,11 @@
</script>
<div class="space-y-5">
- <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold">
+ <h1 class="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-2">
+ <div class="container w-full mx-auto text-lg px-2 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 href={`/payments/${getUnixTime(start) + "/"}1`}>
@@ -204,15 +204,15 @@
<div class="text-base">
{#each payments as p, i}
- <a href={`/payment/${p.id}`} class="contents">
<div
- class="grid grid-cols-12 border-b border-base-200 h-24 hover:bg-base-200 px-4"
+ class="grid grid-cols-12 border-b border-base-200 hover:bg-base-200 px-1 py-2 lg:p-4 cursor-pointer"
class:border-b-0={i === payments.length - 1}
+ class:text-error={p.amount < 0}
+ onclick={() => goto(`/payment/${p.id}`)}
>
<div class="whitespace-nowrap my-auto col-span-3">
<div
class="font-bold flex items-center"
- class:text-red-800={p.amount < 0}
>
<div class="flex items-center">
<iconify-icon
@@ -220,23 +220,23 @@
width="24"
class="text-yellow-300"
></iconify-icon>
- <div>{si(Math.abs(p.amount), 0, 1)}</div>
+ <div>{s(Math.abs(p.amount))}</div>
</div>
+ </div>
+
+ <div class="text-secondary flex items-center text-base">
+ {f(Math.abs(p.amount) * (p.rate / sats), p.currency)}
{#if p.tip}
<span class="text-sm text-secondary">
- +{Math.round((p.tip / Math.abs(p.amount)) * 100)}%
+ &nbsp;+{Math.round((p.tip / Math.abs(p.amount)) * 100)}%
</span>
{/if}
</div>
-
- <div class="text-secondary flex items-center">
- {f(Math.abs(p.amount) * (p.rate / sats), p.currency)}
- </div>
</div>
<div
- class="flex my-auto col-span-5 truncate text-ellipsis overflow-hidden mx-auto"
+ class="flex my-auto col-span-6 truncate text-ellipsis overflow-hidden mx-auto"
>
{#if p.type === types.pot}
<a href={`/pot/${p.memo}`}>
@@ -302,7 +302,6 @@
</div>
</div>
</div>
- </a>
{:else}
<p class="text-secondary text-lg text-center">{$t("payments.empty")}</p>
{/each}
diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.server.ts b/src/routes/(app)/send/lightning/[payreq]/+page.server.ts
index a101b803..1fb4056f 100644
--- a/src/routes/(app)/send/lightning/[payreq]/+page.server.ts
+++ b/src/routes/(app)/send/lightning/[payreq]/+page.server.ts
@@ -2,11 +2,29 @@ import { auth, fd, post } from "$lib/utils";
import { fail, redirect } from "@sveltejs/kit";
export async function load({ cookies, params }) {
- return post("/parse", params, auth(cookies));
+ if (params.payreq.startsWith("lno")) return params;
+ const data = await post("/parse", params, auth(cookies));
+ data.payreq = params.payreq;
+ return data;
}
export const actions = {
- setAmount: async ({ request }) => fd(request),
+ setAmount: async ({ cookies, params, request }) => {
+ let data = await fd(request);
+ const { payreq } = params;
+ const { amount } = data;
+ if (payreq.startsWith("lno")) {
+ const { invoice } = await post("/fetchinvoice", {
+ amount,
+ offer: payreq,
+ });
+
+ data = await post("/parse", { payreq: invoice }, auth(cookies));
+ data.payreq = invoice;
+ }
+
+ return data;
+ },
send: async ({ cookies, request }) => {
let p;
diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte
index d8bd5f8c..2d981326 100644
--- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte
+++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte
@@ -10,27 +10,24 @@
let { data, form } = $props();
- let { payreq } = $page.params;
let {
alias,
+ payreq,
ourfee,
rates,
user: { currency },
- } = $state(data);
+ } = $derived({ ...data, ...form });
let a = $state();
$effect(() => ($rate ||= rates[currency]));
+ $effect(() => form && (loading = false));
let showMax = $state();
let loading = $state();
let submit = () => (loading = true);
- let update = () => {
- if (form?.message?.includes("pin")) $pin = undefined;
- loading = false;
- };
- let show;
+ let next = $state();
let toggle = () => (show = !show);
let amount = $derived(form?.amount || data.amount);
let maxfee = $state(Math.max(5, Math.round(amount * 0.005)));
@@ -128,17 +125,14 @@
{/if}
</form>
{:else}
- <form
- method="POST"
- action="?/setAmount"
- class="w-[300px] mx-auto"
- use:enhance
- >
+ <form method="POST" action="?/setAmount" class="space-y-2" use:enhance>
<input type="hidden" value={a} name="amount" />
<input name="rate" value={$rate} type="hidden" />
- <Numpad bind:amount={a} {currency} bind:rate={$rate} />
- <button type="submit" class="btn"> Next </button>
+ <Numpad bind:amount={a} {currency} bind:rate={$rate} submit={next} />
+ <button type="submit" class="btn" bind:this={next}
+ >{$t("payments.next")}</button
+ >
</form>
{/if}
</div>