aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-03-02 23:37:57 -0800
committerAdam Soltys <[email protected]>2026-03-02 23:37:57 -0800
commit68684395a080a834d823b39e2e9641372bbafbfd (patch)
treed9a02ed31ef3be0fe7c274b08597271e7dac8f15 /src
parent97fffca3e63e2edb4818305abdbcbb8953344aa3 (diff)
downloadcoinos-ui-68684395a080a834d823b39e2e9641372bbafbfd.tar.gz
coinos-ui-68684395a080a834d823b39e2e9641372bbafbfd.zip
dependency audit
Diffstat (limited to 'src')
-rw-r--r--src/components/Menu.svelte82
-rw-r--r--src/components/Payments.svelte12
-rw-r--r--src/components/Qr.svelte13
-rw-r--r--src/lib/locales.ts41
-rw-r--r--src/lib/random.ts28
-rw-r--r--src/lib/socket.ts5
-rw-r--r--src/lib/utils.ts34
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.ts4
-rw-r--r--src/routes/(app)/[username]/accepted/[text]/raw/+server.ts10
-rw-r--r--src/routes/(app)/fund/[id]/+page.svelte1
-rw-r--r--src/routes/(app)/payment/[id]/+page.svelte7
-rw-r--r--src/routes/(app)/payment/[id]/plain/+page.svelte5
-rw-r--r--src/routes/(app)/payments/[...page]/+page.svelte14
-rw-r--r--src/routes/(app)/qr/[text]/raw/+server.ts10
-rw-r--r--src/routes/+layout.ts10
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);
}