diff options
| author | Adam Soltys <[email protected]> | 2024-09-10 20:39:47 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-17 09:18:36 -0700 |
| commit | 1f63848ca9fb01336f04b41434de1cc06a5b3d93 (patch) | |
| tree | 2affa3041863be365f33d99e1f32bda60cf93a32 /src/routes/(app)/payments | |
| parent | 6e48c584605bec5623c12c470d04d07303983801 (diff) | |
| download | coinos-ui-1f63848ca9fb01336f04b41434de1cc06a5b3d93.tar.gz coinos-ui-1f63848ca9fb01336f04b41434de1cc06a5b3d93.zip | |
move routes
Diffstat (limited to 'src/routes/(app)/payments')
| -rw-r--r-- | src/routes/(app)/payments/[...page]/+page.server.ts | 28 | ||||
| -rw-r--r-- | src/routes/(app)/payments/[...page]/+page.svelte | 315 | ||||
| -rw-r--r-- | src/routes/(app)/payments/[...page]/+server.ts | 18 |
3 files changed, 361 insertions, 0 deletions
diff --git a/src/routes/(app)/payments/[...page]/+page.server.ts b/src/routes/(app)/payments/[...page]/+page.server.ts new file mode 100644 index 00000000..c2cd5683 --- /dev/null +++ b/src/routes/(app)/payments/[...page]/+page.server.ts @@ -0,0 +1,28 @@ +import { get, auth } from "$lib/utils"; + +let limit = 25; + +export async function load({ cookies, depends, params }) { + depends("app:payments"); + + let parts = params.page.split("/"); + + let start, end, page; + if (parts.length === 1) [page] = parts; + if (parts.length === 2) [start, page] = parts; + if (parts.length === 3) [start, end, page] = parts; + if (!parseInt(page)) page = 1; + + let offset = (page - 1) * limit; + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + + let url = `/payments?account=${params.account}&start=${ + start * 1000 + }&limit=${limit}&offset=${offset}`; + if (end) url += `&end=${end * 1000}`; + + let { count, payments, incoming, outgoing } = await get(url, auth(cookies)); + let pages = new Array(Math.ceil(count / limit)); + + return { payments, page, pages, start, end, incoming, outgoing }; +} diff --git a/src/routes/(app)/payments/[...page]/+page.svelte b/src/routes/(app)/payments/[...page]/+page.svelte new file mode 100644 index 00000000..b7ea7b6e --- /dev/null +++ b/src/routes/(app)/payments/[...page]/+page.svelte @@ -0,0 +1,315 @@ +<script> + import Avatar from "$comp/Avatar.svelte"; + import Icon from "$comp/Icon.svelte"; + import { onMount } from "svelte"; + import { format } from "date-fns"; + import { newPayment } from "$lib/store"; + import { t } from "$lib/translations"; + import { get, f, s, sat, sats, types } from "$lib/utils"; + import { page } from "$app/stores"; + import { differenceInDays, getUnixTime, sub } from "date-fns"; + import { goto, invalidate } from "$app/navigation"; + + export let data; + + let { start, end, user, rates, payments } = data; + + let change = ({ target: { value } }) => goto(value); + let link = (p) => { + if (p.pot) return `/pot/${p.pot}`; + if (p.with) return "/" + p.with.username; + return `/p/${p.id}`; + }; + + $: presets = [ + { + title: $t("payments.day"), + start: sub(new Date(), { days: 1 }), + end: null, + }, + { + title: $t("payments.week"), + start: sub(new Date(), { days: 7 }), + end: null, + }, + { + title: $t("payments.month"), + start: sub(new Date(), { months: 1 }), + end: null, + }, + { + title: $t("payments.all"), + start: sub(new Date(), { years: 5 }), + end: null, + }, + ]; + + $: selection = start + ? presets.findIndex( + (p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1 + ) + : 0; + + let p, + incoming, + outgoing, + pages = []; + $: data && + ({ page: p, pages, start, end, incoming, outgoing, payments } = data); + + $: $page && ($newPayment = false); + $: $newPayment && invalidate(`/users/${user.username}`); + + $: path = $page.params.page + ? $page.url.pathname.substring(0, $page.url.pathname.lastIndexOf("/")) + : $page.url.pathname; + + let csv = async () => { + let url = `/payments`; + if (start) url += `/${start}`; + if (end) url += `/${end}`; + + let { payments } = await get(url); + + payments = payments.map((p) => ({ + ...p, + created: new Date(p.created), + total: p.amount + (p.tip || 0), + 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, + 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), + })); + + let keys = [ + "type", + "id", + "created", + "rate", + "currency", + "amount", + "tip", + "total", + "fee", + "platform_fee", + "amount_fiat", + "tip_fiat", + "total_fiat", + "fee_fiat", + "platform_fee_fiat", + ]; + + let csv = + keys.map((k) => `"${k}"`).join(",") + + "\n" + + payments + .map((r) => keys.map((k) => `"${r[k] || ""}"`).join(",")) + .join("\n"); + + let filename = "payments.csv"; + let blob = new Blob([csv], { type: "text/csv;charset=utf-8;" }); + if (navigator.msSaveBlob) { + navigator.msSaveBlob(blob, filename); + } else { + let link = document.createElement("a"); + if (link.download !== undefined) { + let url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + }; +</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"> + {$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={`/payments/${getUnixTime(start) + "/"}1`} + > + <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> + + <div class="flex flex-wrap justify-center mb-8"> + {#if pages.length > 1} + {#each pages as _, i} + <a + class="mr-1 last:mr-0" + href={`${path}/${i + 1}`} + class:active={parseInt(p) === i + 1} + > + <div + class="border py-2 rounded-full border-2 w-12 h-12 hover:opacity-80 text-center" + > + {i + 1} + </div> + </a> + {/each} + {/if} + </div> + + <div class="text-base"> + {#each payments as p} + <a href={`/payment/${p.id}`}> + <div class="grid grid-cols-12 border-b h-24 hover:bg-gray-100 px-4"> + <div class="whitespace-nowrap my-auto col-span-3"> + <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 col-span-5 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"> + {#if [types.ecash, types.lightning].includes(p.type)} + <div class="text-3xl">⚡️</div> + {:else if p.type === types.bitcoin} + <div class="my-auto mr-1"> + <img + src="/images/bitcoin.svg" + class="w-12 border-4 border-transparent" + alt="Bitcoin" + /> + </div> + {:else if p.type === types.liquid} + <div class="my-auto mr-1"> + <img + src="/images/liquid.svg" + class="w-12 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")} + </div> + <div> + {format(new Date(p.created), "MMM d, yy")} + </div> + </div> + </div> + </a> + {:else} + <p class="text-secondary text-lg text-center">{$t("payments.empty")}</p> + {/each} + </div> + + <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" --> + <!-- >{$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 + > + + {@const totalIn = incoming[c]?.fiat || 0} + {@const tipsIn = incoming[c]?.fiatTips || 0} + {@const subtotalIn = totalIn - tipsIn} + + {@const totalOut = -outgoing[c]?.fiat || 0} + {@const tipsOut = outgoing[c]?.fiatTips || 0} + {@const subtotalOut = totalOut - tipsOut} + + <span class="text-left text-base text-secondary" + >{$t("payments.income")}</span + > + <!-- <span><b>{subtotalIn ? f(subtotalIn, c) : "-"}</b></span> --> + <span><b>{tipsIn ? f(tipsIn, c) : "-"}</b></span> + <span><b>{totalIn ? f(totalIn, c) : "-"}</b></span> + + <span class="text-left text-base text-secondary" + >{$t("payments.expenditure")}</span + > + <!-- <span><b>{subtotalOut ? f(subtotalOut, c) : "-"}</b></span> --> + <span><b>{tipsOut ? f(tipsOut, c) : "-"}</b></span> + <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> +</div> + +<style> + .active * { + @apply bg-black text-white border-black; + } + + .payments { + view-transition-name: payments; + } +</style> diff --git a/src/routes/(app)/payments/[...page]/+server.ts b/src/routes/(app)/payments/[...page]/+server.ts new file mode 100644 index 00000000..a81faaf3 --- /dev/null +++ b/src/routes/(app)/payments/[...page]/+server.ts @@ -0,0 +1,18 @@ +import { get, auth } from "$lib/utils"; +import { json } from "@sveltejs/kit"; + +export async function GET({ cookies, params }) { + let parts = params.page.split("/"); + + let start, end; + if (parts.length === 1) [start] = parts; + if (parts.length === 2) [start, end] = parts; + + if (!start) start = Date.now() / 1000 - 24 * 60 * 60; + + let url = `/payments?start=${start * 1000}`; + if (end) url += `&end=${end * 1000}`; + + let { payments } = await get(url, auth(cookies)); + return json({ payments }); +} |
