diff options
| author | Adam Soltys <[email protected]> | 2026-03-02 23:37:57 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-02 23:37:57 -0800 |
| commit | 68684395a080a834d823b39e2e9641372bbafbfd (patch) | |
| tree | d9a02ed31ef3be0fe7c274b08597271e7dac8f15 /src | |
| parent | 97fffca3e63e2edb4818305abdbcbb8953344aa3 (diff) | |
| download | coinos-ui-68684395a080a834d823b39e2e9641372bbafbfd.tar.gz coinos-ui-68684395a080a834d823b39e2e9641372bbafbfd.zip | |
dependency audit
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/Menu.svelte | 82 | ||||
| -rw-r--r-- | src/components/Payments.svelte | 12 | ||||
| -rw-r--r-- | src/components/Qr.svelte | 13 | ||||
| -rw-r--r-- | src/lib/locales.ts | 41 | ||||
| -rw-r--r-- | src/lib/random.ts | 28 | ||||
| -rw-r--r-- | src/lib/socket.ts | 5 | ||||
| -rw-r--r-- | src/lib/utils.ts | 34 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/(profile)/+page.ts | 4 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/accepted/[text]/raw/+server.ts | 10 | ||||
| -rw-r--r-- | src/routes/(app)/fund/[id]/+page.svelte | 1 | ||||
| -rw-r--r-- | src/routes/(app)/payment/[id]/+page.svelte | 7 | ||||
| -rw-r--r-- | src/routes/(app)/payment/[id]/plain/+page.svelte | 5 | ||||
| -rw-r--r-- | src/routes/(app)/payments/[...page]/+page.svelte | 14 | ||||
| -rw-r--r-- | src/routes/(app)/qr/[text]/raw/+server.ts | 10 | ||||
| -rw-r--r-- | src/routes/+layout.ts | 10 |
15 files changed, 105 insertions, 171 deletions
diff --git a/src/components/Menu.svelte b/src/components/Menu.svelte index 94fc7c3a..ca64a289 100644 --- a/src/components/Menu.svelte +++ b/src/components/Menu.svelte @@ -9,7 +9,6 @@ import CoinosLogo from "virtual:icons/coinos/logo"; import { theme } from "$lib/store"; import DarkToggle from "$comp/DarkToggle.svelte"; - import { OutClick } from "svelte-outclick"; let { t, user, w, opacity } = $props(); let dark = () => ($theme = $theme === "dark" ? "light" : "dark"); @@ -26,47 +25,50 @@ let hideMenu = () => (showMenu = false); let toggleMenu = () => (showMenu = !showMenu); + + const clickOutside = (node, callback) => { + const handler = (e) => { if (!node.contains(e.target)) callback(); }; + document.addEventListener("click", handler, true); + return { destroy: () => document.removeEventListener("click", handler, true) }; + }; </script> -<div> - <OutClick onOutClick={hideMenu}> - <button class="flex justify-center items-center bg-base-100 p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl {opacity('/support')}" onclick={toggleMenu} aria-label="Open menu"> - <PhListBold width={w > 640 ? 32 : 24} /> - </button> +<div use:clickOutside={hideMenu}> + <button class="flex justify-center items-center bg-base-100 p-2 rounded-full w-12 h-12 sm:w-16 sm:h-16 drop-shadow-xl {opacity('/support')}" onclick={toggleMenu} aria-label="Open menu"> + <PhListBold width={w > 640 ? 32 : 24} /> + </button> - <div - class="absolute top-14 right-0 bg-base-100 rounded-3xl p-8 shadow-xl z-50" - class:hidden={!showMenu} - class:block={showMenu} - > - <ul class="space-y-5 w-48"> - {#each menuButtons as { href, icon: Icon, key }} - <li> - {#if key.includes("dark")} - <DarkToggle /> - {:else} - <a - {href} - data-sveltekit-preload-code="eager" - data-sveltekit-preload-data="false" - onclick={hideMenu} + <div + class="absolute top-14 right-0 bg-base-100 rounded-3xl p-8 shadow-xl z-50" + class:hidden={!showMenu} + class:block={showMenu} + > + <ul class="space-y-5 w-48"> + {#each menuButtons as { href, icon: Icon, key }} + <li> + {#if key.includes("dark")} + <DarkToggle /> + {:else} + <a + {href} + data-sveltekit-preload-code="eager" + data-sveltekit-preload-data="false" + onclick={hideMenu} + > + <button + class="flex justify-center items-center font-semibold hover:opacity-80 gap-2" > - <button - class="flex justify-center items-center font-semibold hover:opacity-80 gap-2" - > - <Icon width="32" /> - {t(key)} - </button> - </a> - {/if} - </li> - {/each} - </ul> - <hr class="my-4" /> - <a href="/?stay=true" aria-label="Coinos home"> - <CoinosLogo width="160" /> - </a> - </div> - </OutClick> + <Icon width="32" /> + {t(key)} + </button> + </a> + {/if} + </li> + {/each} + </ul> + <hr class="my-4" /> + <a href="/?stay=true" aria-label="Coinos home"> + <CoinosLogo width="160" /> + </a> + </div> </div> - diff --git a/src/components/Payments.svelte b/src/components/Payments.svelte index 881da11d..2ebe9794 100644 --- a/src/components/Payments.svelte +++ b/src/components/Payments.svelte @@ -3,10 +3,8 @@ import LogosBitcoin from "virtual:icons/logos/bitcoin"; 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 { get, f, s, si, sat, loc, sats, types, formatDate } from "$lib/utils"; import { t } from "$lib/translations"; - import locales from "$lib/locales"; import { fiat } from "$lib/store"; const { fund = undefined, locale, user, payments }: any = $props(); const language = $derived(user?.language || "en"); @@ -102,14 +100,10 @@ <div class="text-secondary text-right text-sm my-auto col-span-3"> <div> - {format(new Date(p.created), "MMM d, yy", { - locale: locales[language], - })} + {formatDate(new Date(p.created), language, { month: "short", day: "numeric", year: "2-digit" })} </div> <div> - {format(new Date(p.created), "h:mm aaa", { - locale: locales[language], - })} + {formatDate(new Date(p.created), language, { hour: "numeric", minute: "2-digit" })} </div> </div> </div> diff --git a/src/components/Qr.svelte b/src/components/Qr.svelte index d5dbfc41..692f665e 100644 --- a/src/components/Qr.svelte +++ b/src/components/Qr.svelte @@ -1,14 +1,9 @@ <script lang="ts"> - import { onMount } from "svelte"; - import { browser } from "$app/environment"; let { icon = "", text }: any = $props(); let qr = $state(); - let screenfull; - onMount(async () => { - if (!browser) return; - screenfull = (await import("screenfull")).default; - }); + const toggleFullscreen = (el) => + document.fullscreenElement ? document.exitFullscreen() : el.requestFullscreen(); let src = $derived.by(() => { let url = `/qr/${encodeURIComponent(text)}`; @@ -21,8 +16,8 @@ <div class="w-72 sm:w-80 mx-auto cursor-pointer" bind:this={qr} - onclick={() => screenfull?.toggle?.(qr)} - onkeydown={(e) => (e.key === "Enter" || e.key === " ") && screenfull?.toggle?.(qr)} + onclick={() => toggleFullscreen(qr)} + onkeydown={(e) => (e.key === "Enter" || e.key === " ") && toggleFullscreen(qr)} role="button" tabindex="0" aria-label="Toggle fullscreen QR" diff --git a/src/lib/locales.ts b/src/lib/locales.ts deleted file mode 100644 index 83e7a1bf..00000000 --- a/src/lib/locales.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { de } from "date-fns/locale/de"; -import { el } from "date-fns/locale/el"; -import { enUS as en } from "date-fns/locale/en-US"; -import { es } from "date-fns/locale/es"; -import { faIR as fa } from "date-fns/locale/fa-IR"; -import { fr } from "date-fns/locale/fr"; -import { ja } from "date-fns/locale/ja"; -import { pt } from "date-fns/locale/pt"; -import { ru } from "date-fns/locale/ru"; -import { zhCN as zh } from "date-fns/locale/zh-CN"; -import { arSA as ar } from "date-fns/locale/ar-SA"; -import { hi } from "date-fns/locale/hi"; -import { bn } from "date-fns/locale/bn"; -import { tr } from "date-fns/locale/tr"; -import { ko } from "date-fns/locale/ko"; -import { it } from "date-fns/locale/it"; -import { nl } from "date-fns/locale/nl"; -import { th } from "date-fns/locale/th"; -import { pl } from "date-fns/locale/pl"; - -export default { - de, - el, - en, - es, - fa, - fr, - ja, - pt, - ru, - zh, - ar, - hi, - bn, - tr, - ko, - it, - nl, - th, - pl, -}; diff --git a/src/lib/random.ts b/src/lib/random.ts index 4c8dbda3..73afa443 100644 --- a/src/lib/random.ts +++ b/src/lib/random.ts @@ -1,15 +1,17 @@ -import { NumberDictionary, animals, colors, uniqueNamesGenerator } from "unique-names-generator"; +const animals = [ + "alligator", "bear", "cat", "dog", "eagle", "falcon", "gorilla", "hawk", + "iguana", "jaguar", "koala", "lion", "moose", "newt", "otter", "penguin", + "quail", "rabbit", "shark", "tiger", "urchin", "viper", "walrus", "fox", + "yak", "zebra", "badger", "crane", "dolphin", "elk", +]; -export const randomName = () => - uniqueNamesGenerator({ - dictionaries: [animals, NumberDictionary.generate({ min: 10, max: 99 })], - length: 2, - separator: "", - }); +const colors = [ + "red", "blue", "green", "gold", "silver", "coral", "amber", "violet", + "teal", "crimson", "ivory", "jade", "navy", "olive", "scarlet", +]; -export const randomPassword = () => - uniqueNamesGenerator({ - dictionaries: [colors, NumberDictionary.generate({ min: 100, max: 999 })], - length: 2, - separator: "", - }); +const pick = (arr: string[]) => arr[Math.floor(Math.random() * arr.length)]; +const randInt = (min: number, max: number) => min + Math.floor(Math.random() * (max - min + 1)); + +export const randomName = () => pick(animals) + randInt(10, 99); +export const randomPassword = () => pick(colors) + randInt(100, 999); diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 46784503..b2d070fe 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -3,8 +3,7 @@ import { navigating, page } from "$app/stores"; import { PUBLIC_SOCKET } from "$env/static/public"; import { vaultForwarding } from "$lib/ark"; import { event, importing, invoice, last, lastPayment, paymentSignal, request } from "$lib/store"; -import { s, sleep, success, wait } from "$lib/utils"; -import cookies from "js-cookie"; +import { getCookie, s, sleep, success, wait } from "$lib/utils"; import { get } from "svelte/store"; export let socket; @@ -61,7 +60,7 @@ export const messages = (data) => ({ url: { pathname }, } = get(page); - const username = cookies.get("username"); + const username = getCookie("username"); if (amount > 0 && !vaultForwarding) { if ( diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 7a4030d5..6815a5b2 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,12 +6,7 @@ import { sha256 } from "@noble/hashes/sha2.js"; import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js"; import { fail as svelteFail, redirect } from "@sveltejs/kit"; import { toast } from "@zerodevx/svelte-toast"; -import { - differenceInDays, - differenceInHours, - differenceInMinutes, - differenceInSeconds, -} from "date-fns"; + import { get as getStore } from "svelte/store"; export function scroll(section: any) { @@ -484,20 +479,23 @@ export const versions = { export const str = (s) => s.toString(); -export const ago = (t) => { - const now = new Date(); - const date = new Date(t * 1000); - const seconds = differenceInSeconds(now, date); - if (seconds < 60) return `${seconds}s`; - - const minutes = differenceInMinutes(now, date); - if (minutes < 60) return `${minutes}m`; +const localeTagMap: Record<string, string> = { + en: "en-US", + fa: "fa-IR", + zh: "zh-CN", + ar: "ar-SA", +}; +export const toLocaleTag = (lang = "en") => localeTagMap[lang] || lang; - const hours = differenceInHours(now, date); - if (hours < 24) return `${hours}h`; +export const formatDate = (date: Date, lang = "en", opts: Intl.DateTimeFormatOptions = {}) => + new Intl.DateTimeFormat(toLocaleTag(lang), opts).format(date); - const days = differenceInDays(now, date); - return `${days}d`; +export const ago = (t) => { + const diff = Math.floor((Date.now() - t * 1000) / 1000); + if (diff < 60) return `${diff}s`; + if (diff < 3600) return `${Math.floor(diff / 60)}m`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h`; + return `${Math.floor(diff / 86400)}d`; }; export const register = async ( diff --git a/src/routes/(app)/[username]/(profile)/+page.ts b/src/routes/(app)/[username]/(profile)/+page.ts index e6a3ffeb..f60bee57 100644 --- a/src/routes/(app)/[username]/(profile)/+page.ts +++ b/src/routes/(app)/[username]/(profile)/+page.ts @@ -1,9 +1,9 @@ import { signer } from "$lib/store"; -import cookies from "js-cookie"; +import { getCookie } from "$lib/utils"; import { get } from "svelte/store"; export const ssr = false; export async function load() { - const sk = cookies.get("sk"); + const sk = getCookie("sk"); if (sk && !get(signer)) signer.set({ method: "nsec", params: { sk }, ready: true }); } diff --git a/src/routes/(app)/[username]/accepted/[text]/raw/+server.ts b/src/routes/(app)/[username]/accepted/[text]/raw/+server.ts index ad7fc6b1..77bb8a09 100644 --- a/src/routes/(app)/[username]/accepted/[text]/raw/+server.ts +++ b/src/routes/(app)/[username]/accepted/[text]/raw/+server.ts @@ -1,15 +1,13 @@ -import Qr from "qrcode-base64"; +import QRCode from "qrcode"; export async function GET({ params }) { const { text } = params; - const dataUri = Qr.drawImg(text, { size: 600 }); - const base64Data = dataUri.split(",")[1]; - const binaryData = Buffer.from(base64Data, "base64"); + const buffer = await QRCode.toBuffer(text, { width: 600, type: "png" }); - return new Response(binaryData, { + return new Response(buffer, { headers: { - "Content-Type": "image/gif", + "Content-Type": "image/png", "Cache-Control": "public, max-age=31536000, immutable", }, }); diff --git a/src/routes/(app)/fund/[id]/+page.svelte b/src/routes/(app)/fund/[id]/+page.svelte index 94d768aa..dea5a19b 100644 --- a/src/routes/(app)/fund/[id]/+page.svelte +++ b/src/routes/(app)/fund/[id]/+page.svelte @@ -7,7 +7,6 @@ import PhLightningFill from "virtual:icons/ph/lightning-fill"; 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"; diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index 55daca8d..3e8c4788 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -5,7 +5,7 @@ import { browser } from "$app/environment"; import { onMount, tick } from "svelte"; import { t, loading } from "$lib/translations"; - import { copy, f, s, loc, toFiat, post, sats, fail, success, types } from "$lib/utils"; + import { copy, f, s, loc, toFiat, post, sats, fail, success, types, formatDate } from "$lib/utils"; import { fiat } from "$lib/store"; const toggleFiat = (e) => { @@ -14,9 +14,7 @@ $fiat = !$fiat; }; import Avatar from "$comp/Avatar.svelte"; - 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 expanded: Set<string> = $state(new Set()); @@ -24,7 +22,6 @@ let { data } = $props(); let user = $derived(data.user); let p = $derived(data.payment); - let locale = $derived(user ? locales[user.language] : locales["en"]); let userLocale = $derived(loc(user)); let { @@ -192,7 +189,7 @@ </span> </button> <div class="text-secondary"> - {format(new Date(created), "MMM d, yyyy", { locale })}<span class="mx-3">{format(new Date(created), "h:mmaaa", { locale })}</span> + {formatDate(new Date(created), user?.language, { month: "short", day: "numeric", year: "numeric" })}<span class="mx-3">{formatDate(new Date(created), user?.language, { hour: "numeric", minute: "2-digit" })}</span> </div> </div> diff --git a/src/routes/(app)/payment/[id]/plain/+page.svelte b/src/routes/(app)/payment/[id]/plain/+page.svelte index 1efe2c32..d92ab58f 100644 --- a/src/routes/(app)/payment/[id]/plain/+page.svelte +++ b/src/routes/(app)/payment/[id]/plain/+page.svelte @@ -3,10 +3,9 @@ import { onMount } from "svelte"; import { browser } from "$app/environment"; import { t } from "$lib/translations"; - import { back, copy, f, s, toFiat, post, sats, success, types } from "$lib/utils"; + import { back, copy, f, s, toFiat, post, sats, success, types, formatDate } from "$lib/utils"; import { fiat } from "$lib/store"; import Avatar from "$comp/Avatar.svelte"; - import { format } from "date-fns"; import { PUBLIC_EXPLORER as expl } from "$env/static/public"; let { data } = $props(); @@ -66,7 +65,7 @@ <div> <span class="text-lg text-secondary">Date</span> - <div>{format(new Date(created), "MMMM d")}, {format(new Date(created), "h:mm aaa")}</div> + <div>{formatDate(new Date(created), "en", { month: "long", day: "numeric" })}, {formatDate(new Date(created), "en", { hour: "numeric", minute: "2-digit" })}</div> </div> {#if type === "bitcoin"} diff --git a/src/routes/(app)/payments/[...page]/+page.svelte b/src/routes/(app)/payments/[...page]/+page.svelte index cbe5d07c..c34f04fe 100644 --- a/src/routes/(app)/payments/[...page]/+page.svelte +++ b/src/routes/(app)/payments/[...page]/+page.svelte @@ -3,12 +3,10 @@ import Avatar from "$comp/Avatar.svelte"; import Payments from "$comp/Payments.svelte"; import { onMount } from "svelte"; - import { format } from "date-fns"; import { newPayment, cachedPayments } from "$lib/store"; import { t } from "$lib/translations"; import { get, f, s, si, sat, loc, sats, types } from "$lib/utils"; import { page } from "$app/stores"; - import { differenceInDays, getUnixTime, sub } from "date-fns"; import { goto, invalidate } from "$app/navigation"; let { data } = $props(); @@ -26,28 +24,28 @@ let presets = $derived([ { title: $t("payments.day"), - start: sub(new Date(), { days: 1 }), + start: new Date(Date.now() - 86400000), end: null, }, { title: $t("payments.week"), - start: sub(new Date(), { days: 7 }), + start: new Date(Date.now() - 7 * 86400000), end: null, }, { title: $t("payments.month"), - start: sub(new Date(), { months: 1 }), + start: new Date(Date.now() - 30 * 86400000), end: null, }, { title: $t("payments.all"), - start: sub(new Date(), { years: 5 }), + start: new Date(Date.now() - 5 * 365 * 86400000), end: null, }, ]); let selection = $derived( - presets.findIndex((p) => Math.abs(differenceInDays(new Date(start * 1000), p.start)) < 1), + presets.findIndex((p) => Math.abs(new Date(start * 1000).getTime() - p.start.getTime()) < 86400000), ); $effect(() => { @@ -158,7 +156,7 @@ <div class="flex w-full gap-1"> {#each presets as { start, end, title }, i} <a - href={`/payments/${getUnixTime(start)}/1`} + href={`/payments/${Math.floor(start.getTime() / 1000)}/1`} class="btn !w-auto flex-1" class:!btn-active={selection === i} > diff --git a/src/routes/(app)/qr/[text]/raw/+server.ts b/src/routes/(app)/qr/[text]/raw/+server.ts index ad7fc6b1..77bb8a09 100644 --- a/src/routes/(app)/qr/[text]/raw/+server.ts +++ b/src/routes/(app)/qr/[text]/raw/+server.ts @@ -1,15 +1,13 @@ -import Qr from "qrcode-base64"; +import QRCode from "qrcode"; export async function GET({ params }) { const { text } = params; - const dataUri = Qr.drawImg(text, { size: 600 }); - const base64Data = dataUri.split(",")[1]; - const binaryData = Buffer.from(base64Data, "base64"); + const buffer = await QRCode.toBuffer(text, { width: 600, type: "png" }); - return new Response(binaryData, { + return new Response(buffer, { headers: { - "Content-Type": "image/gif", + "Content-Type": "image/png", "Cache-Control": "public, max-age=31536000, immutable", }, }); diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 719e35d6..0fcf5ec8 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,11 +1,7 @@ import { browser } from "$app/environment"; import { theme as themeStore } from "$lib/store"; import { addTranslations, setLocale, setRoute } from "$lib/translations/index"; -import cookies from "js-cookie"; - -const expires = new Date(); -expires.setSeconds(expires.getSeconds() + 21000000); -const opts = { path: "/", expires }; +import { getCookie, setCookie } from "$lib/utils"; /** @type {import('@sveltejs/kit').Load} */ export const load = async ({ data }) => { @@ -18,12 +14,12 @@ export const load = async ({ data }) => { await setLocale(locale); if (browser) { - let theme = cookies.get("theme"); + let theme = getCookie("theme"); if (!theme) { theme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; - cookies.set("theme", theme, opts); + setCookie("theme", theme, 21000000); if (theme === "dark") setTimeout(() => themeStore.set(theme), 100); } |
