diff options
| author | Adam Soltys <[email protected]> | 2024-12-24 00:41:51 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-12-24 00:41:51 -0800 |
| commit | 3c0489e1cca4f6a211215b700489d256695c3ce1 (patch) | |
| tree | d477237d739237e2ca527750be2cd139393fb36a /src | |
| parent | 747e8d82fbfa88351a99e8d5cb5712ba4b6e4000 (diff) | |
| download | coinos-ui-3c0489e1cca4f6a211215b700489d256695c3ce1.tar.gz coinos-ui-3c0489e1cca4f6a211215b700489d256695c3ce1.zip | |
create fund
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/Lnurlw.svelte | 6 | ||||
| -rw-r--r-- | src/components/Payments.svelte | 120 | ||||
| -rw-r--r-- | src/locales/en.json | 23 | ||||
| -rw-r--r-- | src/routes/(app)/fund/[id]/+page.server.ts | 7 | ||||
| -rw-r--r-- | src/routes/(app)/fund/[id]/+page.svelte | 137 | ||||
| -rw-r--r-- | src/routes/(app)/fund/[id]/withdraw/+page.server.ts | 15 | ||||
| -rw-r--r-- | src/routes/(app)/fund/[id]/withdraw/+page.svelte | 59 | ||||
| -rw-r--r-- | src/routes/(app)/ln/[lnurl]/+page.server.ts | 9 | ||||
| -rw-r--r-- | src/routes/(app)/payment/[id]/+page.svelte | 6 | ||||
| -rw-r--r-- | src/routes/(app)/payments/[...page]/+page.svelte | 110 | ||||
| -rw-r--r-- | src/routes/(app)/qr/[text]/+page.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/send/[...text]/+page.svelte | 8 | ||||
| -rw-r--r-- | src/routes/(app)/send/fund/[...name]/[...amount]/+page.server.ts | 28 | ||||
| -rw-r--r-- | src/routes/(app)/send/fund/[...name]/[...amount]/+page.svelte | 70 | ||||
| -rw-r--r-- | src/routes/(app)/send/fund/[name]/[...amount]/+page.server.ts | 17 | ||||
| -rw-r--r-- | src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte | 66 |
16 files changed, 350 insertions, 333 deletions
diff --git a/src/components/Lnurlw.svelte b/src/components/Lnurlw.svelte index e3345d31..8793d3b4 100644 --- a/src/components/Lnurlw.svelte +++ b/src/components/Lnurlw.svelte @@ -55,7 +55,7 @@ <Numpad bind:amount {currency} {rate} /> {/if} - <form action="?/withdraw" method="POST" use:enhance onsubmit={submit}> + <form action="?/withdraw" method="POST" use:enhance onsubmit={submit} class="space-y-5"> <input name="amount" value={amount} type="hidden" /> <input name="username" value={username} type="hidden" /> <input name="currency" value={currency} type="hidden" /> @@ -65,11 +65,11 @@ <input name="k1" value={k1} type="hidden" /> <div class="flex w-full"> - <button type="submit" class="btn"> + <button type="submit" class="btn btn-accent"> {#if loading} <Spinner /> {:else} - {$t("payments.redeem")} + {$t("payments.withdraw")} {/if} </button> </div> diff --git a/src/components/Payments.svelte b/src/components/Payments.svelte new file mode 100644 index 00000000..2d38513e --- /dev/null +++ b/src/components/Payments.svelte @@ -0,0 +1,120 @@ +<script> + import { goto } from "$app/navigation"; + import Avatar from "$comp/Avatar.svelte"; + import { get, f, s, si, sat, loc, sats, types } from "$lib/utils"; + import { format } from "date-fns"; + import { t } from "$lib/translations"; + + const { fund, locale, payments } = $props(); +</script> + +<div class="text-base"> + {#each payments as p, i} + {@const amount = fund ? -p.amount : p.amount} + <div + 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={amount < 0} + onclick={() => goto(`/payment/${p.id}`)} + > + <div class="whitespace-nowrap my-auto col-span-3"> + <div class="font-bold flex items-center"> + <div class="flex items-center"> + <iconify-icon + icon="ph:lightning-fill" + width="24" + class="text-yellow-300" + ></iconify-icon> + <div>{s(Math.abs(amount), locale)}</div> + </div> + </div> + + <div class="text-secondary flex items-center text-base"> + {f(Math.abs(amount) * (p.rate / sats), p.currency, locale)} + + {#if p.tip} + <span class="text-sm text-secondary"> + +{Math.round((p.tip / Math.abs(amount)) * 100)}% + </span> + {/if} + </div> + </div> + + <div + class="flex my-auto col-span-6 truncate text-ellipsis overflow-hidden mx-auto" + > + {#if p.type === types.fund} + {#if fund} + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.user} size={12} disabled={true} /> + </div> + <div class="my-auto ml-1 text-secondary"> + {p.user.username} + </div> + </div> + {:else} + <a href={`/fund/${p.memo}`}> + <div class="text-secondary flex"> + <iconify-icon + icon="ph:lightning-fill" + class="text-yellow-300 text-3xl" + ></iconify-icon> + + <div class="my-auto">{$t("payments.fund")}</div> + </div> + </a> + {/if} + {:else if p.with} + <div class="flex"> + <div class="my-auto"> + <Avatar user={p.with} size={12} disabled={true} /> + </div> + <div class="my-auto ml-1 text-secondary"> + {p.with.username} + </div> + </div> + {:else} + <div class="text-secondary flex items-center gap-1"> + {#if p.type === types.lightning} + <iconify-icon + icon="ph:lightning-fill" + class="text-yellow-300 text-3xl" + ></iconify-icon> + {:else if p.type === types.ecash} + <img src="/images/cash.png" class="w-12" /> + {:else if p.type === types.reconcile} + <iconify-icon icon="ph:scales-bold" width="32"></iconify-icon> + {:else if p.type === types.bitcoin} + <iconify-icon icon="logos:bitcoin" class="text-3xl" + ></iconify-icon> + {:else if p.type === types.liquid} + <div class="my-auto"> + <img + src="/images/liquid.svg" + class="w-10 border-4 border-transparent" + alt="Liquid" + /> + </div> + {/if} + + <div class="my-auto"> + {amount > 0 ? (p.confirmed ? "Received" : "Pending") : "Sent"} + </div> + </div> + {/if} + </div> + + <div class="text-secondary text-right text-sm my-auto col-span-3"> + <div> + {format(new Date(p.created), "h:mm aaa", { locale })} + </div> + <div> + {format(new Date(p.created), "MMM d, yy", { locale })} + </div> + </div> + </div> + {:else} + <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p> + {/each} +</div> diff --git a/src/locales/en.json b/src/locales/en.json index 26f9779b..a0d0f855 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -111,7 +111,7 @@ "addTipq": "Add a tip?", "addMemo": "Add memo", "setMemo": "Set memo", - "memo": "Memo", + "memo": "Memo", "done": "Done", "paymentSuccessful": "Payment received!", "invoice": "Invoice", @@ -193,7 +193,7 @@ "subheader": "Latest Updates" }, "user": { - "loadMore": "Load more", + "loadMore": "Load more", "url": "Public URL", "install": "Install app", "moreDetails": "Profile details", @@ -250,8 +250,8 @@ "placeholder": "Enter an address, invoice or username" }, "settings": { - "integrations": "Integrations", - "connectNostr": "Nostr Wallet Connect", + "integrations": "Integrations", + "connectNostr": "Nostr Wallet Connect", "pleaseEnterYourPassword": "Please enter your password", "memoPrompt": "Checkout instructions", "memoPromptPlaceholder": "e.g. Please enter your name for the order", @@ -262,7 +262,7 @@ "destinationPlaceholder": "Enter a Bitcoin, Liquid or Lightning address", "reserve": "Reserve amount", "reserveDesc": "The amount to be left in your account after the withdrawal is complete", - "nostr": "Nostr", + "nostr": "Nostr", "shopify": "Shopify", "threshold": "Autowithdrawal threshold", "thresholdDesc": "When your balance exceeds this amount, a withdrawal will be triggered", @@ -276,8 +276,8 @@ "seedPhrase": "Seed phrase", "nostrKeys": "Nostr secret key", "nostrDescription": "Your nostr secret key is used to sign and decrypt messages, keep it safe or someone may be able to impersonate you.", - "nwc": "Nostr wallet connect", - "nwcDescription": "Enable your nostr app to send payments from your account", + "nwc": "Nostr wallet connect", + "nwcDescription": "Enable your nostr app to send payments from your account", "seedDescription": "This 12 word seed phrase is used to derive your nostr private key and your self-custodial wallet keys which determine your auto-withdrawal addresses. Back it up and don't let anyone see it.", "emailDescription": "For password resets and payment notifications only", "hideNsec": "Hide nostr secret key", @@ -361,6 +361,7 @@ "memo": "Notes", "voucherText": "Enter an ecash token or lnurlw code", "createEcash": "Mint ecash", + "lnurlw": "LNURLw", "tips": "Tips", "options": "options", "bump": "Bump fee", @@ -368,6 +369,8 @@ "platformFee": "Platform fee", "fastest": "Fastest", "fundingAmount": "Funding amount", + "createFund": "Create a fund", + "withdraw": "Withdraw", "fast": "Fast", "ecashDesc": "Ecash tokens are funded with bitcoin. They may be redeemed in exchange for a lightning payment equal to their value or loaded back into a coinos account at any time. They can also be transferred to other ecash wallets either wholly or partially. You might use them to give bitcoin to someone who doesn't have a wallet by texting the token to them or sharing a link to it. Learn more at", "ecashToken": "Ecash token", @@ -376,8 +379,8 @@ "slow": "Slow", "create": "Create", "fund": "Fund", - "addFunds": "Add Funds", - "takeFunds": "Take Funds", + "addFunds": "Add funds", + "takeFunds": "Take funds", "show": "Show", "showText": "Show text", "showQr": "Show QR", @@ -386,7 +389,7 @@ "qr": "QR", "copy": "Copy", "amountPrompt": "Prompt for amount", - "shareLink": "Share link", + "shareLink": "Share", "copyLink": "Copy link", "copyText": "Copy text", "openLink": "Open wallet", diff --git a/src/routes/(app)/fund/[id]/+page.server.ts b/src/routes/(app)/fund/[id]/+page.server.ts index 668ea3af..9ea33627 100644 --- a/src/routes/(app)/fund/[id]/+page.server.ts +++ b/src/routes/(app)/fund/[id]/+page.server.ts @@ -2,9 +2,12 @@ import getRates from "$lib/rates"; import { auth, get, post } from "$lib/utils"; import { redirect } from "@sveltejs/kit"; -export const load = async ({ params: { id } }) => { +export const load = async ({ depends, params: { id }, parent }) => { + depends("app:payments"); const data = await get(`/fund/${id}`); - data.rate = await getRates(); + const rates = await getRates(); + const { user } = await parent(); + data.rate = rates[user?.currency || "USD"]; return data; }; diff --git a/src/routes/(app)/fund/[id]/+page.svelte b/src/routes/(app)/fund/[id]/+page.svelte index 23dce871..f99b17d7 100644 --- a/src/routes/(app)/fund/[id]/+page.svelte +++ b/src/routes/(app)/fund/[id]/+page.svelte @@ -1,28 +1,33 @@ <script> + import Amount from "$comp/Amount.svelte"; + import Payments from "$comp/Payments.svelte"; import { format } from "date-fns"; import { enhance } from "$app/forms"; import { page } from "$app/stores"; import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; import { toast } from "@zerodevx/svelte-toast"; import { t } from "$lib/translations"; - import { types, copy, f, s, sat, sats } from "$lib/utils"; + import { types, copy, f, s, sat, sats, loc } from "$lib/utils"; import { loginRedirect } from "$lib/store"; + import { bech32 } from "@scure/base"; + const encoder = new TextEncoder(); let { data } = $props(); - let { amount, src, payments, rate, user } = data; + let { amount, payments, rate, user } = data; let { id } = $page.params; - - toast.pop(0); + let locale = loc(user); let currency = user ? user.currency : "CAD"; let amountFiat = $derived(parseFloat(((amount * rate) / sats).toFixed(2))); - $loginRedirect = $page.url.pathname; + $effect(() => ($loginRedirect = $page.url.pathname)); let show = $state(); let toggle = () => (show = !show); + let href = $derived(encodeURIComponent($page.url.href)); + let withdrawUrl = encoder.encode(`${$page.url.origin}/api/fund/${id}/withdraw`); + let lnurlw = bech32.encode("lnurl", bech32.toWords(withdrawUrl), 20000); </script> <div class="container px-4 max-w-4xl mx-auto mt-10 space-y-5"> @@ -30,117 +35,55 @@ <div class="md:shadow-xl rounded-3xl md:px-10 pt-5 pb-10 space-y-5 w-full md:mx-5" > - <div class="relative flex justify-center gap-2"> - <!-- <p class="absolute right-0 top-1 underline"><Icon icon="settings" /></p> --> - {#if id.split("-").length === 4} - <h1 class="text-2xl md:text-3xl font-semibold my-auto">Card {id}</h1> - {:else} - <img src="/images/moneypot.jpg" class="w-24" alt="Fund" /> - <div class="my-auto"> - <h1 class="text-2xl font-semibold my-auto"> - {$t("payments.fund")} - </h1> - <h1 class="text-secondary my-auto">{id}</h1> - </div> - {/if} - </div> {#if show} - <img {src} class="mx-auto" alt={id} /> + <div class="max-w-[360px] mx-auto"> + <img + src={`/qr/${href}/raw`} + class="z-10 border-4 border-white w-" + alt="URL" + /> + </div> {/if} <div class="flex justify-center gap-4"> - <div class="my-auto"> - <h2 class="text-2xl md:text-3xl font-semibold"> - {f(amountFiat, currency)} - </h2> - <h3 class="text-secondary md:text-lg mt-1"> - {sat(amount)} - </h3> - </div> + <Amount {amount} {currency} {rate} {locale} /> </div> <div class="flex gap-2" data-sveltekit-prefetch="off"> <div class="grow"> <a href={`/send/fund/${id}`}> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - > - <Icon icon="plus" /> + <button class="btn"> + <iconify-icon icon="ph:plus-bold" width="32"></iconify-icon> {$t("payments.addFunds")} </button> </a> </div> <div class="grow"> <a href={`/fund/${id}/withdraw`}> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - > - <Icon icon="minus" style="rotate-180" /> + <button class="btn"> + <iconify-icon + icon="ph:hand-coins-bold" + width="32" + flip="horizontal" + ></iconify-icon> {$t("payments.takeFunds")} </button> </a> </div> </div> <div class="flex gap-2" data-sveltekit-prefetch="off"> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - onclick={toggle} - > - <Icon icon="qr" style="invert" /> - {show ? $t("payments.hide") : $t("payments.show")} QR - </button> - <button - class="rounded-full border py-3 px-2 font-bold hover:opacity-80 flex gap-1 w-full justify-center" - onclick={() => copy($page.url.host + $page.url.pathname)} - > - <Icon icon="copy" style="w-6" /> - {$t("payments.copyLink")} - </button> - </div> - <div class="text-base"> - {#each payments as p} - <a href={`/payment/${p.id}`}> - <div class="grid grid-cols-3 border-b h-24 hover:bg-gray-100 px-4"> - <div class="whitespace-nowrap my-auto"> - <div class="font-bold" class:text-red-800={p.amount > 0}> - {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)}% - </span> - {/if} - </div> - - <div class="text-secondary"> - {sat(Math.abs(p.amount) + (p.tip || 0))} - </div> - </div> - - <div class="flex my-auto"> - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.user} size={16} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary"> - {p.user.username} - </div> - </div> - </div> - - <div class="text-secondary text-right text-sm my-auto"> - <div> - {format(new Date(p.created), "h:mm aaa")} - </div> - <div> - {format(new Date(p.created), "MMM d")} - </div> - </div> - </div> - </a> - {:else} - <p class="text-secondary text-lg text-center"> - {$t("payments.empty")} - </p> - {/each} + <a href={`/qr/${href}`} class="btn !w-auto grow"> + <iconify-icon icon="ph:link-bold" width="32"></iconify-icon> + <div class="my-auto">{$t("payments.shareLink")}</div> + </a> + <a href={`/qr/${lnurlw}`} class="btn !w-auto grow"> + <iconify-icon + icon="ph:lightning-fill" + width="24" + class="text-yellow-300" + ></iconify-icon> + {$t("payments.lnurlw")} + </a> </div> + <Payments {payments} fund={true} /> </div> </div> </div> diff --git a/src/routes/(app)/fund/[id]/withdraw/+page.server.ts b/src/routes/(app)/fund/[id]/withdraw/+page.server.ts index 1e477918..696c52ff 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.server.ts +++ b/src/routes/(app)/fund/[id]/withdraw/+page.server.ts @@ -1,9 +1,15 @@ +import getRates from "$lib/rates"; +import { auth, fd, get, post } from "$lib/utils"; import { fail, redirect } from "@sveltejs/kit"; -import { fd, auth, post } from "$lib/utils"; -export async function load({ parent }) { +export async function load({ params, parent }) { const { user } = await parent(); if (!user) redirect(307, "/register"); + const rates = await getRates(); + const rate = rates[user.currency]; + const fund = await get(`/fund/${params.id}`); + const { amount: balance } = fund; + return { ...params, balance, rate }; } export const actions = { @@ -11,8 +17,9 @@ export const actions = { const body = await fd(request); try { await post("/take", body, auth(cookies)); - } catch (e: any) { - return fail(400, { message: e.message }); + } catch (e) { + const { message } = e as Error; + return fail(400, { message }); } redirect(307, `/fund/${body.id}`); diff --git a/src/routes/(app)/fund/[id]/withdraw/+page.svelte b/src/routes/(app)/fund/[id]/withdraw/+page.svelte index 89654d23..f6f12b1a 100644 --- a/src/routes/(app)/fund/[id]/withdraw/+page.svelte +++ b/src/routes/(app)/fund/[id]/withdraw/+page.svelte @@ -1,40 +1,35 @@ <script> - import { run } from 'svelte/legacy'; - + import handler from "$lib/handler"; import { t } from "$lib/translations"; import { enhance } from "$app/forms"; - import Icon from "$comp/Icon.svelte"; import Numpad from "$comp/Numpad.svelte"; import Spinner from "$comp/Spinner.svelte"; import { page } from "$app/stores"; - import { back } from "$lib/utils"; + import { s } from "$lib/utils"; import { pin } from "$lib/store"; let { data, form } = $props(); - let { rates } = data; - let { amount, id } = $state($page.params); - let { currency } = $state(data.user); + let { currency } = $derived(data.user); let loading = $state(); + let { id, balance, rate } = $derived(data); + let { amount } = $state(data); + let fiat = $state(); - let submit = () => (loading = true); - let update = () => { + let submit = $state(); + let submitting = $state(); + let toggle = () => (submitting = !submitting); + $effect(() => { if (form?.message.includes("pin")) $pin = undefined; loading = false; - }; - - run(() => { - update(form); }); - let rate; - run(() => { - rate = rates[currency]; - }); -</script> -<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" onclick={back}> - <Icon icon="arrow-left" style="w-10" /> -</button> + let setMax = (e) => { + e.preventDefault(); + fiat = false; + amount = balance; + }; +</script> {#if form?.message} <div class="text-red-600 text-center"> @@ -43,22 +38,32 @@ {/if} <div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount bind:currency bind:rate /> + <Numpad bind:amount {currency} {fiat} {rate} {submit} /> - <form method="POST" use:enhance onsubmit={submit}> + <form use:enhance={handler(toggle)} method="POST" class="space-y-5"> <input name="id" value={id} type="hidden" /> <input name="amount" value={amount} type="hidden" /> <input name="pin" value={$pin} type="hidden" /> - <div class="flex w-full"> + <div class="flex gap-2"> + <button + type="button" + class="btn !w-auto grow" + onclick={setMax} + disabled={submitting} + onkeydown={setMax}>Max ⚡️{s(balance)}</button + > + <button + use:focus + bind:this={submit} type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" + class="btn btn-accent !w-auto grow" > - {#if loading} + {#if submitting} <Spinner /> {:else} - {$t("payments.send")} + <div class="my-auto">{$t("payments.next")}</div> {/if} </button> </div> diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.ts b/src/routes/(app)/ln/[lnurl]/+page.server.ts index 681137ad..53445461 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.server.ts +++ b/src/routes/(app)/ln/[lnurl]/+page.server.ts @@ -7,10 +7,12 @@ export async function load({ params, parent }) { if (!user) redirect(307, "/register"); const rates = await getRates(); + console.log("YO"); let data; try { const { lnurl } = params; data = await get(`/decode?text=${lnurl}`); + console.log("DATA", data); } catch (e) { const { message } = e as Error; error(500, message); @@ -76,8 +78,11 @@ export const actions = { auth(cookies), ); - const c = callback.includes("?") ? "&" : "?"; - await fetch(`${callback}${c}k1=${k1}&pr=${pr}`).then((r) => r.json()); + const url = new URL(callback); + url.searchParams.append("k1", k1); + url.searchParams.append("pr", pr); + + await fetch(url.toString()); redirect(307, "/payments"); }, diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index 279b87e7..63b8ccca 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -112,7 +112,11 @@ <a href={`/fund/${p.memo}`}> <div class="flex"> <div class="my-auto"> - <img src="/images/moneypot.png" class="w-12" alt="Pot" /> + <iconify-icon + icon="ph:lightning-fill" + width="24" + class="text-yellow-300" + ></iconify-icon> </div> <div class="my-auto ml-1">{p.memo}</div> </div> diff --git a/src/routes/(app)/payments/[...page]/+page.svelte b/src/routes/(app)/payments/[...page]/+page.svelte index d53c13c0..09bdf4c7 100644 --- a/src/routes/(app)/payments/[...page]/+page.svelte +++ b/src/routes/(app)/payments/[...page]/+page.svelte @@ -1,6 +1,6 @@ <script> - import { run } from "svelte/legacy"; import Avatar from "$comp/Avatar.svelte"; + import Payments from "$comp/Payments.svelte"; import { onMount } from "svelte"; import { format } from "date-fns"; import { newPayment } from "$lib/store"; @@ -193,113 +193,17 @@ </div> </a> {:else if isEllipsis(i)} - <div class="join-item btn !w-auto !btn-sm btn-disabled !bg-base-200">...</div> + <div + class="join-item btn !w-auto !btn-sm btn-disabled !bg-base-200" + > + ... + </div> {/if} {/each} {/if} </div> - <div class="text-base"> - {#each payments as p, i} - <div - 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"> - <div class="flex items-center"> - <iconify-icon - icon="ph:lightning-fill" - width="24" - class="text-yellow-300" - ></iconify-icon> - <div>{s(Math.abs(p.amount), userLocale)}</div> - </div> - </div> - - <div class="text-secondary flex items-center text-base"> - {f(Math.abs(p.amount) * (p.rate / sats), p.currency, userLocale)} - - {#if p.tip} - <span class="text-sm text-secondary"> - +{Math.round((p.tip / Math.abs(p.amount)) * 100)}% - </span> - {/if} - </div> - </div> - - <div - class="flex my-auto col-span-6 truncate text-ellipsis overflow-hidden mx-auto" - > - {#if p.type === types.pot} - <a href={`/pot/${p.memo}`}> - <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> - </a> - {:else if p.with} - <div class="flex"> - <div class="my-auto"> - <Avatar user={p.with} size={12} disabled={true} /> - </div> - <div class="my-auto ml-1 text-secondary"> - {p.with.username} - </div> - </div> - {:else} - <div class="text-secondary flex items-center gap-1"> - {#if p.type === types.lightning} - <iconify-icon - icon="ph:lightning-fill" - class="text-yellow-300 text-3xl" - ></iconify-icon> - {:else if p.type === types.ecash} - <img src="/images/cash.png" class="w-12" /> - {:else if p.type === types.reconcile} - <iconify-icon icon="ph:scales-bold" width="32"></iconify-icon> - {:else if p.type === types.bitcoin} - <iconify-icon icon="logos:bitcoin" class="text-3xl" - ></iconify-icon> - {:else if p.type === types.liquid} - <div class="my-auto"> - <img - src="/images/liquid.svg" - class="w-10 border-4 border-transparent" - alt="Liquid" - /> - </div> - {/if} - - <div class="my-auto"> - {p.amount > 0 - ? p.confirmed - ? "Received" - : "Pending" - : "Sent"} - </div> - </div> - {/if} - </div> - - <div class="text-secondary text-right text-sm my-auto col-span-3"> - <div> - {format(new Date(p.created), "h:mm aaa", { locale })} - </div> - <div> - {format(new Date(p.created), "MMM d, yy", { locale })} - </div> - </div> - </div> - {:else} - <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p> - {/each} - </div> + <Payments {payments} /> <div class="grid grid-cols-3 w-full text-center text-lg"> {#each Object.keys(incoming) as c} diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte index 46420743..ff5e8fa8 100644 --- a/src/routes/(app)/qr/[text]/+page.svelte +++ b/src/routes/(app)/qr/[text]/+page.svelte @@ -3,7 +3,7 @@ import Qr from "$comp/Qr.svelte"; import { back, copy } from "$lib/utils"; let { data } = $props(); - let { text } = data; + let { text } = $derived(data); </script> <div class="container px-4 max-w-4xl mx-auto space-y-5"> diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index ebc705b5..261a1784 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -94,6 +94,14 @@ <div class="my-auto">{$t("payments.createEcash")}</div> </button> </a> + + <a href="/send/fund" class="block"> + <button type="button" class="btn"> + <iconify-icon icon="ph:lightning-fill" class="text-yellow-300 text-2xl" + ></iconify-icon> + <div class="my-auto">{$t("payments.createFund")}</div> + </button> + </a> </form> {#if contacts.length} diff --git a/src/routes/(app)/send/fund/[...name]/[...amount]/+page.server.ts b/src/routes/(app)/send/fund/[...name]/[...amount]/+page.server.ts new file mode 100644 index 00000000..428d8d78 --- /dev/null +++ b/src/routes/(app)/send/fund/[...name]/[...amount]/+page.server.ts @@ -0,0 +1,28 @@ +import getRates from "$lib/rates"; +import { auth, fd, post } from "$lib/utils"; +import { fail, redirect } from "@sveltejs/kit"; + +export async function load({ parent, params }) { + let { name, amount } = params; + if (!name) name = crypto.randomUUID(); + const { user } = await parent(); + const rates = await getRates(); + const rate = rates[user.currency]; + return { name, amount, rate }; +} + +export const actions = { + default: async ({ cookies, request }) => { + const body = await fd(request); + let p; + + try { + p = await post("/payments", body, auth(cookies)); + } catch (e) { + const { message } = e as Error; + return fail(400, { message }); + } + + if (p) redirect(307, `/fund/${body.fund}`); + }, +}; diff --git a/src/routes/(app)/send/fund/[...name]/[...amount]/+page.svelte b/src/routes/(app)/send/fund/[...name]/[...amount]/+page.svelte new file mode 100644 index 00000000..314be3ef --- /dev/null +++ b/src/routes/(app)/send/fund/[...name]/[...amount]/+page.svelte @@ -0,0 +1,70 @@ +<script> + import { t } from "$lib/translations"; + import { enhance } from "$app/forms"; + import Numpad from "$comp/Numpad.svelte"; + import Spinner from "$comp/Spinner.svelte"; + import { page } from "$app/stores"; + import { pin } from "$lib/store"; + import handler from "$lib/handler"; + import { s } from "$lib/utils"; + + let { data, form } = $props(); + let { amount } = $state(data); + let { balance, currency } = data.user; + let { name, rate } = $derived(data); + let loading = $state(); + let fiat = $state(); + + let submit = $state(); + let submitting = $state(); + let toggle = () => (submitting = !submitting); + $effect(() => { + if (form?.message?.includes("pin")) $pin = undefined; + loading = false; + }); + + let setMax = (e) => { + e.preventDefault(); + fiat = false; + amount = balance; + }; +</script> + +{#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"> + <Numpad bind:amount {currency} {rate} {fiat} {submit} /> + + <form use:enhance={handler(toggle)} method="POST" class="space-y-5"> + <input name="fund" value={name} type="hidden" /> + <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + + <div class="flex gap-2"> + <button + type="button" + class="btn !w-auto grow" + onclick={setMax} + disabled={submitting} + onkeydown={setMax}>Max ⚡️{s(balance)}</button + > + + <button + use:focus + bind:this={submit} + type="submit" + class="btn btn-accent !w-auto grow" + > + {#if submitting} + <Spinner /> + {:else} + <div class="my-auto">{$t("payments.next")}</div> + {/if} + </button> + </div> + </form> +</div> diff --git a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.ts b/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.ts deleted file mode 100644 index ed39f96b..00000000 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.server.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { fail, redirect } from "@sveltejs/kit"; -import { auth, fd, post } from "$lib/utils"; - -export const actions = { - default: async ({ cookies, request }) => { - const body = await fd(request); - let p; - - try { - p = await post("/payments", body, auth(cookies)); - } catch (e: any) { - return fail(400, { message: e.message }); - } - - if (p) redirect(307, `/fund/${body.name}`); - }, -}; diff --git a/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte b/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte deleted file mode 100644 index d1c3b204..00000000 --- a/src/routes/(app)/send/fund/[name]/[...amount]/+page.svelte +++ /dev/null @@ -1,66 +0,0 @@ -<script> - import { run } from 'svelte/legacy'; - - import { t } from "$lib/translations"; - import { enhance } from "$app/forms"; - import Icon from "$comp/Icon.svelte"; - import Numpad from "$comp/Numpad.svelte"; - import Spinner from "$comp/Spinner.svelte"; - import { page } from "$app/stores"; - import { back } from "$lib/utils"; - import { pin } from "$lib/store"; - - let { data, form } = $props(); - - let { amount, name } = $state($page.params); - let { rates } = data; - let { currency } = data.user; - let loading = $state(); - - - let submit = () => (loading = true); - let update = () => { - if (form?.message?.includes("pin")) $pin = undefined; - loading = false; - }; - let rate; - run(() => { - rate = rates[currency]; - }); - run(() => { - update(form); - }); -</script> - -<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" onclick={back}> - <Icon icon="arrow-left" style="w-10" /> -</button> - -{#if form?.message} - <div class="text-red-600 text-center"> - {form.message} - </div> -{/if} - -<div class="container px-4 mt-20 max-w-xl mx-auto"> - <Numpad bind:amount {currency} bind:rate /> - - <form method="POST" use:enhance onsubmit={submit}> - <input name="name" value={name} type="hidden" /> - <input name="amount" value={amount} type="hidden" /> - <input name="pin" value={$pin} type="hidden" /> - - <div class="flex w-full"> - <button - type="submit" - class="opacity-100 hover:opacity-80'} rounded-2xl border py-3 font-bold mx-auto mt-2 bg-black text-white px-4 w-24" - > - {#if loading} - <Spinner /> - {:else} - {$t("payments.send")} - {/if} - </button> - </div> - </form> -</div> |
