aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/PasswordInput.svelte4
-rw-r--r--src/lib/utils.ts2
-rw-r--r--src/locales/en.json12
-rw-r--r--src/routes/(app)/+layout.server.ts6
-rw-r--r--src/routes/(app)/settings/+layout.server.ts4
-rw-r--r--src/routes/(app)/settings/+layout.svelte3
-rw-r--r--src/routes/(app)/settings/nostr/+page.svelte145
-rw-r--r--src/routes/(app)/settings/preferences/+page.svelte25
-rw-r--r--src/routes/(app)/settings/security/+page.svelte48
-rw-r--r--static/images/nostr.pngbin0 -> 190459 bytes
10 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>
diff --git a/static/images/nostr.png b/static/images/nostr.png
new file mode 100644
index 00000000..ea73239c
--- /dev/null
+++ b/static/images/nostr.png
Binary files differ