diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.css | 16 | ||||
| -rw-r--r-- | src/components/Avatar.svelte | 3 | ||||
| -rw-r--r-- | src/components/Mnemonic.svelte | 6 | ||||
| -rw-r--r-- | src/components/Numpad.svelte | 4 | ||||
| -rw-r--r-- | src/components/Slider.svelte | 47 | ||||
| -rw-r--r-- | src/lib/parse.ts | 22 | ||||
| -rw-r--r-- | src/routes/(app)/account/pass/+page.svelte | 57 | ||||
| -rw-r--r-- | src/routes/(app)/account/savings/+page.svelte | 4 | ||||
| -rw-r--r-- | src/routes/(app)/account/seed/+page.svelte | 43 | ||||
| -rw-r--r-- | src/routes/(app)/logout/+page.server.ts | 1 | ||||
| -rw-r--r-- | src/routes/(app)/payment/[id]/+page.svelte | 20 | ||||
| -rw-r--r-- | src/routes/(app)/payments/[...page]/+page.svelte | 27 | ||||
| -rw-r--r-- | src/routes/(app)/send/lightning/[payreq]/+page.server.ts | 22 | ||||
| -rw-r--r-- | src/routes/(app)/send/lightning/[payreq]/+page.svelte | 24 |
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 === "-" ? "—" : 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)}% + +{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> |
