diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/PasswordInput.svelte | 4 | ||||
| -rw-r--r-- | src/lib/utils.ts | 2 | ||||
| -rw-r--r-- | src/locales/en.json | 12 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.server.ts | 6 | ||||
| -rw-r--r-- | src/routes/(app)/settings/+layout.server.ts | 4 | ||||
| -rw-r--r-- | src/routes/(app)/settings/+layout.svelte | 3 | ||||
| -rw-r--r-- | src/routes/(app)/settings/nostr/+page.svelte | 145 | ||||
| -rw-r--r-- | src/routes/(app)/settings/preferences/+page.svelte | 25 | ||||
| -rw-r--r-- | src/routes/(app)/settings/security/+page.svelte | 48 |
9 files changed, 178 insertions, 71 deletions
diff --git a/src/components/PasswordInput.svelte b/src/components/PasswordInput.svelte index 860899c2..8c7d8d7c 100644 --- a/src/components/PasswordInput.svelte +++ b/src/components/PasswordInput.svelte @@ -4,11 +4,11 @@ show = $bindable(), value = $bindable(), focus = () => {}, - placeholder, + placeholder = undefined, } = $props(); let toggle = (e) => { - e.preventDefault(); + e?.preventDefault(); show = !show; }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9cf87277..2e3fd658 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -98,8 +98,6 @@ export const copyNoNewlines = (text: string) => { success("Copied!"); }; -export const protectedRoutes: RegExp[] = [/customers/, /settings/, /payments/]; - let recent: string[] = []; export const success = (m: string, clear = true) => { if (recent.includes(m)) return; diff --git a/src/locales/en.json b/src/locales/en.json index cc8637c7..25d4813d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,7 +1,7 @@ { "accounts": { "showQR": "Show QR", - "revealNwc": "Reveal NWC", + "revealNwc": "Reveal connection string", "finish": "Finish", "back": "Back", "next": "Next", @@ -248,6 +248,7 @@ "placeholder": "Enter an address, invoice or username" }, "settings": { + "connectNostr": "Nostr Wallet Connect", "pleaseEnterYourPassword": "Please enter your password", "memoPrompt": "Checkout instructions", "memoPromptPlaceholder": "e.g. Please enter your name for the order", @@ -258,7 +259,8 @@ "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", - "shopify": "Shopify integration", + "nostr": "Nostr", + "shopify": "Shopify", "threshold": "Autowithdrawal threshold", "thresholdDesc": "When your balance exceeds this amount, a withdrawal will be triggered", "autoWithdraw": "Autowithdrawal", @@ -269,8 +271,10 @@ "pushNotificationsDisabled": "You've denied notification permission on this device. Check your browser settings if you want to undo that.", "notificationsDesc": "Receive an email whenever a payment lands in your account", "seedPhrase": "Seed phrase", - "nostrKeys": "Nostr key", - "nostrDescription": "Your nostr key is used to sign and decrypt messages, keep it safe or someone may be able to impersonate you.", + "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", "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", diff --git a/src/routes/(app)/+layout.server.ts b/src/routes/(app)/+layout.server.ts index d490a684..b024edc1 100644 --- a/src/routes/(app)/+layout.server.ts +++ b/src/routes/(app)/+layout.server.ts @@ -1,4 +1,4 @@ -import { auth, get, protectedRoutes, sleep } from "$lib/utils"; +import { auth, get, sleep } from "$lib/utils"; import { error, redirect } from "@sveltejs/kit"; export async function load({ cookies, request, url, params }) { @@ -47,10 +47,6 @@ export async function load({ cookies, request, url, params }) { redirect(307, `/${user.username}`); } - if (protectedRoutes.find((p) => pathname.match(p)) && !user) { - redirect(307, "/login"); - } - if (user && !user.pubkey && !pathname.includes("generate")) { console.log("user missing pubkey", user); redirect(303, "/generate"); diff --git a/src/routes/(app)/settings/+layout.server.ts b/src/routes/(app)/settings/+layout.server.ts index cd63d4f0..bcb7dbe2 100644 --- a/src/routes/(app)/settings/+layout.server.ts +++ b/src/routes/(app)/settings/+layout.server.ts @@ -1,7 +1,9 @@ import { auth, get } from "$lib/utils"; import { redirect } from "@sveltejs/kit"; -export async function load({ cookies, params, url }) { +export async function load({ cookies, params, parent, url }) { + const { user } = await parent(); + if (!user) redirect(307, `/register?redirect=${url.pathname}`); const tab = url.pathname.split("/").filter(Boolean).pop(); if (tab === "settings") redirect(307, `${url.pathname}/account`); params.cookies = cookies.getAll(); diff --git a/src/routes/(app)/settings/+layout.svelte b/src/routes/(app)/settings/+layout.svelte index edf256ab..b11927c1 100644 --- a/src/routes/(app)/settings/+layout.svelte +++ b/src/routes/(app)/settings/+layout.svelte @@ -40,7 +40,6 @@ { name: "account", key: "ACCOUNT" }, { name: "preferences", key: "POINT_OF_SALE" }, { name: "security", key: "SECURITY" }, - { name: "shopify", key: "SHOPIFY" }, ]; let { address, id, username } = user; @@ -212,7 +211,7 @@ type="button" class="hover:opacity-80" class:font-bold={tab === name || - (tab === "shopify" && name === "pos")} + (name === "preferences" && ["nostr", "shopify"].includes(tab))} >{$t(`user.settings.${key}`)}</button > </a> diff --git a/src/routes/(app)/settings/nostr/+page.svelte b/src/routes/(app)/settings/nostr/+page.svelte new file mode 100644 index 00000000..e81682dd --- /dev/null +++ b/src/routes/(app)/settings/nostr/+page.svelte @@ -0,0 +1,145 @@ +<script> + import { getNsec } from "$lib/nostr"; + import { page } from "$app/stores"; + import { tick } from "svelte"; + import { t } from "$lib/translations"; + import { copy } from "$lib/utils"; + import { + PUBLIC_COINOS_PUBKEY as pk, + PUBLIC_COINOS_RELAY as relay, + } from "$env/static/public"; + + let { data } = $props(); + let { user, rates } = $derived(data); + let { id, npub } = $derived(user); + + let importing = $state(), + nwc = $state(), + newNsec = $state(), + nsec = $state(), + pin = $state(""), + revealNsec = $state(); + + let toggleImporting = () => { + revealNsec = false; + importing = !importing; + }; + + let toggleNsec = async () => { + try { + nsec = await getNsec(user); + revealNsec = !revealNsec; + importing = false; + } catch (e) { + console.log(e); + } + }; + + let lud16 = $derived(`${user.username}@${$page.url.host}`); + let revealNwc = () => + (nwc = `nostr+walletconnect://${pk}?relay=${encodeURIComponent( + relay, + )}&secret=${user.nwc}&lud16=${lud16}`); +</script> + +<div> + <label for="nwc" class="font-bold">{$t("user.settings.nwc")}</label> + + <p class="text-secondary mb-1"> + {$t("user.settings.nwcDescription")} + </p> + + <div class="flex flex-wrap justify-center gap-2"> + <a href={nwc} class="btn grow"> + <img src="/images/nostr.png" class="w-8" /> + <div class="my-auto">{$t("user.settings.connectNostr")}</div> + </a> + + {#if !nwc} + <button onclick={revealNwc} type="button" class="btn grow"> + <iconify-icon icon="ph:warning-bold" width="32"></iconify-icon> + <div class="my-auto">{$t("accounts.revealNwc")}</div></button + > + {/if} + </div> +</div> + +{#if nwc} + <div class="break-all grow"> + {nwc} + </div> + <div class="flex flex-wrap gap-2"> + <button onclick={() => copy(nwc)} type="button" class="btn grow"> + <iconify-icon icon="ph:copy-bold" width="32"></iconify-icon> + <div class="my-auto">{$t("accounts.copy")}</div></button + > + <a href={`/qr/${encodeURIComponent(nwc)}`} class="btn grow"> + <iconify-icon icon="ph:qr-code-bold" width="32"></iconify-icon> + <div class="my-auto">{$t("user.receive.showQR")}</div> + </a> + </div> +{/if} + +<div> + <div class="font-bold">{$t("user.nostrPubkey")}</div> + <div class="flex gap-4"> + <div class="break-all grow"> + {npub} + </div> + <div class="flex my-auto gap-1"> + <button type="button" class="my-auto" onclick={() => copy(npub)} + ><iconify-icon icon="ph:copy-bold" width="32"></iconify-icon></button + > + <a href={`/qr/${encodeURIComponent(npub)}`} class="my-auto"> + <iconify-icon icon="ph:qr-code-bold" width="32"></iconify-icon> + </a> + </div> + </div> +</div> + +<div class="space-y-2"> + <label for="seedphrase" class="font-bold" + >{$t("user.settings.nostrKeys")}</label + > + + <p class="text-secondary mb-1"> + {$t("user.settings.nostrDescription")} + </p> + + <div class="flex flex-wrap sm:flex-nowrap gap-2"> + <button + type="button" + class="btn !w-auto flex-grow" + onclick={toggleImporting} + > + <iconify-icon icon="ph:arrow-down-left-bold" width="32"></iconify-icon> + {$t("user.settings.import")} + </button> + + <button type="button" class="btn !w-auto flex-grow" onclick={toggleNsec}> + {#if revealNsec} + <iconify-icon icon="ph:eye-slash-bold" width="32"></iconify-icon> + {$t("user.settings.hideNsec")} + {:else} + <iconify-icon icon="ph:warning-bold" width="32"></iconify-icon> + {$t("user.settings.revealNsec")} + {/if} + </button> + </div> + + {#if importing} + <input name="newNsec" bind:value={newNsec} placeholder="nsec..." /> + {/if} + + {#if revealNsec} + <button + type="button" + class="btn break-all !h-auto font-normal leading-normal flex-nowrap" + onclick={() => copy(nsec)} + > + <div>{nsec}</div> + + <iconify-icon icon="ph:copy-bold" width="32"></iconify-icon> + </button> + {/if} +</div> diff --git a/src/routes/(app)/settings/preferences/+page.svelte b/src/routes/(app)/settings/preferences/+page.svelte index a08eb952..28e43a08 100644 --- a/src/routes/(app)/settings/preferences/+page.svelte +++ b/src/routes/(app)/settings/preferences/+page.svelte @@ -180,7 +180,7 @@ <button type="button" class="flex w-full" onclick={editThreshold}> <div class="p-4 border rounded-2xl rounded-r-none border-r-0 bg-base-200"> <iconify-icon icon="ph:lightning-fill" class="text-yellow-300" - ></iconify-icon> + ></iconify-icon> </div> <div class="border-l-0 rounded-l-none pl-2 w-full p-4 border rounded-2xl text-left" @@ -201,7 +201,7 @@ <button type="button" class="flex w-full" onclick={editReserve}> <div class="p-4 border rounded-2xl rounded-r-none border-r-0 bg-base-200"> <iconify-icon icon="ph:lightning-fill" class="text-yellow-300" - ></iconify-icon> + ></iconify-icon> </div> <div class="border-l-0 rounded-l-none pl-2 w-full p-4 border rounded-2xl text-left" @@ -269,9 +269,18 @@ </div> {/if} -<a href="./shopify" class="block"> - <button type="button" class="btn"> - <img src="/icons/shopify.svg" class="w-8" /> - <div class="my-auto">{$t("user.settings.shopify")}</div> - </button> -</a> +<div class="flex flex-wrap gap-2 justify-center"> + <a href="/settings/nostr" class="block grow"> + <button type="button" class="btn"> + <img src="/images/nostr.png" class="w-8" /> + <div class="my-auto">{$t("user.settings.nostr")}</div> + </button> + </a> + + <a href="/settings/shopify" class="block grow"> + <button type="button" class="btn"> + <img src="/icons/shopify.svg" class="w-8" /> + <div class="my-auto">{$t("user.settings.shopify")}</div> + </button> + </a> +</div> diff --git a/src/routes/(app)/settings/security/+page.svelte b/src/routes/(app)/settings/security/+page.svelte index d40439ac..8298652b 100644 --- a/src/routes/(app)/settings/security/+page.svelte +++ b/src/routes/(app)/settings/security/+page.svelte @@ -1,6 +1,6 @@ <script> import { getContext } from "svelte"; - import { getMnemonic, getNsec } from "$lib/nostr"; + import { getMnemonic } from "$lib/nostr"; import { tick } from "svelte"; import { t } from "$lib/translations"; import Pin from "$comp/Pin.svelte"; @@ -244,49 +244,3 @@ {/if} </div> -<div class="space-y-2"> - <label for="seedphrase" class="font-bold" - >{$t("user.settings.nostrKeys")}</label - > - - <p class="text-secondary mb-1"> - {$t("user.settings.nostrDescription")} - </p> - - <div class="flex flex-wrap sm:flex-nowrap gap-2"> - <button - type="button" - class="btn !w-auto flex-grow" - onclick={toggleImporting} - > - <iconify-icon icon="ph:arrow-down-left-bold" width="32"></iconify-icon> - {$t("user.settings.import")} - </button> - - <button type="button" class="btn !w-auto flex-grow" onclick={toggleNsec}> - {#if revealNsec} - <iconify-icon icon="ph:eye-slash-bold" width="32"></iconify-icon> - {$t("user.settings.hideNsec")} - {:else} - <iconify-icon icon="ph:warning-bold" width="32"></iconify-icon> - {$t("user.settings.revealNsec")} - {/if} - </button> - </div> - - {#if importing} - <input name="newNsec" bind:value={newNsec} placeholder="nsec..." /> - {/if} - - {#if revealNsec} - <button - type="button" - class="btn break-all !h-auto font-normal leading-normal flex-nowrap" - onclick={() => copy(nsec)} - > - <div>{nsec}</div> - - <iconify-icon icon="ph:copy-bold" width="32"></iconify-icon> - </button> - {/if} -</div> |
