diff options
| author | Adam Soltys <[email protected]> | 2026-03-04 23:06:35 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-04 23:06:35 -0800 |
| commit | e5f37b003d93a17c2a9958697269b2d3228d8830 (patch) | |
| tree | e2c3a3c4d1bec48d323e830f5be35d5832845a9a /src/components | |
| parent | e6882c6a8987e23215a2538b242431ae603c7101 (diff) | |
| download | coinos-ui-e5f37b003d93a17c2a9958697269b2d3228d8830.tar.gz coinos-ui-e5f37b003d93a17c2a9958697269b2d3228d8830.zip | |
tuning
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Account.svelte | 75 | ||||
| -rw-r--r-- | src/components/AppHeader.svelte | 12 | ||||
| -rw-r--r-- | src/components/InvoiceActions.svelte | 73 | ||||
| -rw-r--r-- | src/components/InvoiceData.svelte | 11 | ||||
| -rw-r--r-- | src/components/WalletPass.svelte | 100 |
5 files changed, 97 insertions, 174 deletions
diff --git a/src/components/Account.svelte b/src/components/Account.svelte index e29e1765..2d4b1e48 100644 --- a/src/components/Account.svelte +++ b/src/components/Account.svelte @@ -23,27 +23,27 @@ let arkBalance = $state(0); let arkLoading = $state(false); - let tryUnlockArk = async (): Promise<string | false> => { - const deriveArkKey = async (key: ArrayBuffer): Promise<string> => { - const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([ - import("@scure/bip32"), - import("@scure/bip39"), - import("@scure/bip39/wordlists/english.js"), - ]); - let mnemonicStr: string; - if (account.seed && isPrfEncrypted(account.seed)) { - const ent = await prfDecrypt(key, account.seed); - mnemonicStr = entropyToMnemonic(ent, wordlist); - } else { - const entropy = new Uint8Array(key).slice(0, 16); - mnemonicStr = entropyToMnemonic(entropy, wordlist); - } - const s = await mnemonicToSeed(mnemonicStr); - const master = HDKey.fromMasterSeed(s, versions); - const arkChild = master.derive("m/86'/0'/0'/0/0"); - return bytesToHex(arkChild.privateKey!); - }; + let deriveArkKey = async (key: ArrayBuffer): Promise<string> => { + const [{ HDKey }, { entropyToMnemonic, mnemonicToSeed }, { wordlist }] = await Promise.all([ + import("@scure/bip32"), + import("@scure/bip39"), + import("@scure/bip39/wordlists/english.js"), + ]); + let mnemonicStr: string; + if (account.seed && isPrfEncrypted(account.seed)) { + const ent = await prfDecrypt(key, account.seed); + mnemonicStr = entropyToMnemonic(ent, wordlist); + } else { + const entropy = new Uint8Array(key).slice(0, 16); + mnemonicStr = entropyToMnemonic(entropy, wordlist); + } + const s = await mnemonicToSeed(mnemonicStr); + const master = HDKey.fromMasterSeed(s, versions); + const arkChild = master.derive("m/86'/0'/0'/0/0"); + return bytesToHex(arkChild.privateKey!); + }; + let tryUnlockArk = async (): Promise<string | false> => { // Try cached PRF key or silent nsec const { getWalletEntropy, deriveNostrEntropy } = await import("$lib/walletEntropy"); const entropy = await getWalletEntropy(); @@ -65,24 +65,38 @@ } }; - let setAccount = async (event, url) => { + let aidParam = $derived(`aid=${id}`); + let withAid = (url: string) => url + (url.includes("?") ? "&" : "?") + aidParam; + + let setAccount = async (event, url, requireKey = false) => { event.preventDefault(); event.stopPropagation(); - document.cookie = `aid=${id}; path=/; max-age=86400`; if (isArk) { if ($arkkey && $arkaid === id) { - await goto(url); - } else { + await goto(withAid(url)); + } else if (requireKey) { const key = await tryUnlockArk(); if (key) { - await goto(url); + await goto(withAid(url)); $arkkey = key; $arkaid = id; } + } else { + // Try silent unlock only, navigate regardless + const { getWalletEntropy } = await import("$lib/walletEntropy"); + const entropy = await getWalletEntropy(); + if (entropy) { + try { + const key = await deriveArkKey(entropy); + $arkkey = key; + $arkaid = id; + } catch {} + } + goto(withAid(url)); } } else { $arkkey = undefined; - goto(url); + goto(withAid(url)); } }; @@ -106,8 +120,7 @@ let goSettings = (e) => { e.preventDefault(); e.stopPropagation(); - document.cookie = `aid=${id}; path=/; max-age=86400`; - goto(`/account/${id}`); + goto(`/account/${id}?${aidParam}`); }; </script> @@ -145,7 +158,7 @@ <!-- <PhClockCounterClockwiseBold width="38" /> --> <!-- </a> --> <a - href={`/account/${id}`} + href={`/account/${id}?${aidParam}`} class="hover:opacity-80 relative w-12 h-12 flex items-center justify-center" aria-label="Account settings" onclick={goSettings} @@ -165,14 +178,14 @@ </div> <div class="flex w-full text-xl gap-2"> - <a href={receiveUrl} class="contents" data-sveltekit-preload-data="tap" onclick={(e) => setAccount(e, receiveUrl)}> + <a href={receiveUrl} class="contents" onclick={(e) => e.stopPropagation()}> <button class="btn !w-auto flex-1" data-testid="account-receive"> <PhHandCoinsBold width="32" style="transform: scaleX(-1)" /> <div class="my-auto">{$t("user.dashboard.receive")}</div> </button> </a> - <a href={`/send`} class="contents" data-sveltekit-preload-data="tap" onclick={(e) => setAccount(e, "/send")}> + <a href={withAid("/send")} class="contents" onclick={(e) => setAccount(e, "/send")}> <button type="button" class="btn !w-auto flex-1"> <PhPaperPlaneRightBold width="32" /> <div class="my-auto">{$t("user.dashboard.send")}</div> diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index a12c656e..0d70bf49 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -4,12 +4,12 @@ import { goto } from "$app/navigation"; import Avatar from "$comp/Avatar.svelte"; import Menu from "$comp/Menu.svelte"; - import { loading, t } from "$lib/translations"; + import { t } from "$lib/translations"; import { page } from "$app/stores"; let { user, subject = $bindable() } = $props(); - let w: number | undefined = $state(); + let w: number = $state(globalThis.innerWidth || 1024); $effect(() => (subject = subject || user)); let bg = $derived( @@ -69,7 +69,6 @@ <svelte:window bind:innerWidth={w} /> -{#if !$loading} <header class="bg-gradient-to-r bg-primary bg-accent h-[175px] w-full relative bg-cover mb-20 !z-30" style:background-image={bg} @@ -79,7 +78,7 @@ {#each links as { href, icon: Icon, label }} <a {href} data-sveltekit-preload-data="tap" aria-label={label}> <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(href)}" aria-label={label}> - <Icon width={(w ?? 0) > 640 ? 32 : 24} /> + <Icon width={w > 640 ? 32 : 24} /> </button> </a> {/each} @@ -90,15 +89,14 @@ </a> {/if} </nav> - {#if subject} + {#if subject || user} <div class="absolute md:w-[64px] md:mx-auto lg:left-[154px] xl:left-[194px] left-[calc(50vw-64px)] -bottom-[64px] z-30" > - <Avatar user={subject} /> + <Avatar user={subject || user} /> </div> {/if} </header> -{/if} <style> header { diff --git a/src/components/InvoiceActions.svelte b/src/components/InvoiceActions.svelte index 1c34d5ac..65d218fd 100644 --- a/src/components/InvoiceActions.svelte +++ b/src/components/InvoiceActions.svelte @@ -24,6 +24,9 @@ } = $props(); let { aid, account, address_type, type } = $derived(invoice); + let isOwn = $derived(user?.id === invoice?.user?.id); + let isCustodial = $derived(aid === user?.id); + let showTypeSwitch = $derived(isCustodial); </script> <div class="text-secondary space-y-2 text-xl pt-2"> @@ -50,7 +53,7 @@ </button> <div class="flex flex-wrap gap-2 justify-around text-secondary w-full"> - {#if user?.id === invoice?.user?.id} + {#if isOwn} <button class="btn grow !w-auto" onclick={toggleAmount}> <PhPencilSimpleBold width="32" /> <div class="my-auto">{t("payments.setAmount")}</div> @@ -70,43 +73,43 @@ </button> </div> - - - <div class="flex gap-2"> - <button - class="btn flex-nowrap !w-auto grow" - class:bg-base-300={type === types.lightning} - class:text-secondary={type === types.lightning} - onclick={() => { - $amountPrompt = false; - newAmount = undefined; - setType(types.lightning); - }} - > - <div class="bg-black rounded-full w-8 h-8 items-center justify-center flex"> - <div class="m-auto"> - <PhLightningFill width="24" class="text-yellow-300" /> - </div> - </div> - <div class="my-auto text-lg">Lightning</div> - </button> - - {#if !account?.seed && !(account?.pubkey && account?.fingerprint) && account?.type !== "ark"} + {#if showTypeSwitch} + <div class="flex gap-2"> <button class="btn flex-nowrap !w-auto grow" - class:bg-base-300={type === types.bitcoin} - class:text-secondary={type === types.bitcoin} - onclick={() => setType(types.bitcoin)} + class:bg-base-300={type === types.lightning} + class:text-secondary={type === types.lightning} + onclick={() => { + $amountPrompt = false; + newAmount = undefined; + setType(types.lightning); + }} > - <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> - <div class="my-auto text-lg">Bitcoin</div> + <div class="bg-black rounded-full w-8 h-8 items-center justify-center flex"> + <div class="m-auto"> + <PhLightningFill width="24" class="text-yellow-300" /> + </div> + </div> + <div class="my-auto text-lg">Lightning</div> </button> - {/if} - </div> - <button type="button" class="btn" onclick={toggleType}> - <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> - <img src="/images/cash.png" class="w-8 my-auto" alt="Ecash" /> - <div class="my-auto">{t("payments.moreOptions")}</div> - </button> + {#if !account?.seed && !(account?.pubkey && account?.fingerprint) && account?.type !== "ark"} + <button + class="btn flex-nowrap !w-auto grow" + class:bg-base-300={type === types.bitcoin} + class:text-secondary={type === types.bitcoin} + onclick={() => setType(types.bitcoin)} + > + <img src="/images/bitcoin.svg" class="w-8" alt="Bitcoin" /> + <div class="my-auto text-lg">Bitcoin</div> + </button> + {/if} + </div> + + <button type="button" class="btn" onclick={toggleType}> + <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> + <img src="/images/cash.png" class="w-8 my-auto" alt="Ecash" /> + <div class="my-auto">{t("payments.moreOptions")}</div> + </button> + {/if} </div> diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte index ece30457..a613256b 100644 --- a/src/components/InvoiceData.svelte +++ b/src/components/InvoiceData.svelte @@ -17,6 +17,7 @@ tip, rate, align = "", + qrDataUrl, t, }: any = $props(); @@ -24,9 +25,17 @@ let { memo } = $derived(invoice); let qrSrc = $derived(`/qr/${encodeURIComponent(invoice.text)}/raw`); - let srcs: string[] = $state([]); + let srcs: string[] = $state(qrDataUrl ? [qrDataUrl] : []); + let lastDataUrl = qrDataUrl; $effect(() => { + // Update if the server-provided data URL changed (e.g. new invoice) + if (qrDataUrl && qrDataUrl !== lastDataUrl) { + lastDataUrl = qrDataUrl; + srcs = [qrDataUrl]; + return; + } + if (qrDataUrl && srcs.length) return; const next = qrSrc; if (!srcs.length) { srcs = [next]; diff --git a/src/components/WalletPass.svelte b/src/components/WalletPass.svelte deleted file mode 100644 index a3f00994..00000000 --- a/src/components/WalletPass.svelte +++ /dev/null @@ -1,100 +0,0 @@ -<script lang="ts"> - import PhEyeBold from "virtual:icons/ph/eye-bold"; - import PhEyeSlashBold from "virtual:icons/ph/eye-slash-bold"; - import { preventDefault } from "svelte/legacy"; - - import { focus } from "$lib/utils"; - import { t } from "$lib/translations"; - import { - rememberForOptions, - defaultRememberForMs, - rememberWalletPassword, - forgetWalletPassword, - } from "$lib/passwordCache"; - let { cancel = $bindable(), password = $bindable(), submit } = $props(); - let revealPassword = $state(); - let rememberForMs = $state(defaultRememberForMs); - - let handleSubmit = async () => { - if (rememberForMs) { - rememberWalletPassword(password, rememberForMs); - } else { - forgetWalletPassword(); - } - await submit(); - }; -</script> - -<div class="fixed inset-0 bg-gray-600/50 overflow-y-auto h-full w-full z-20"> - <div class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5"> - <h1 class="text-center text-2xl font-semibold">{$t("payments.enterWalletPass")}</h1> - <form onsubmit={preventDefault(handleSubmit)}> - <div class="relative mb-5"> - {#if revealPassword} - <input - name="password" - data-testid="walletpass-input" - type="text" - required - bind:value={password} - autocapitalize="none" - use:focus - /> - {:else} - <input - name="password" - data-testid="walletpass-input" - type="password" - required - bind:value={password} - autocapitalize="none" - use:focus - /> - {/if} - <button - type="button" - onclick={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-1/2" - aria-label={revealPassword ? "Hide password" : "Show password"} - > - {#if revealPassword} - <PhEyeBold width="32" /> - {:else} - <PhEyeSlashBold width="32" /> - {/if} - </button> - </div> - <div class="mb-5 space-y-2"> - <label for="rememberFor" class="text-sm text-gray-500">Remember for</label> - <select - id="rememberFor" - class="w-full" - value={rememberForMs} - onchange={(e) => (rememberForMs = Number((e.target as HTMLSelectElement).value))} - > - {#each rememberForOptions as option} - <option value={option.ms}>{option.label}</option> - {/each} - </select> - </div> - <div class="w-full flex justify-center gap-2"> - <button - type="button" - data-testid="walletpass-cancel" - class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow" - onclick={cancel} - onkeydown={cancel} - > - <div class="my-auto">Cancel</div> - </button> - <button - type="submit" - data-testid="walletpass-submit" - class="border rounded-2xl font-semibold mx-auto py-5 px-6 hover:opacity-80 mx-auto grow" - > - <div class="my-auto">Submit</div> - </button> - </div> - </form> - </div> -</div> |
