aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/settings
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/(app)/settings')
-rw-r--r--src/routes/(app)/settings/+layout.svelte14
-rw-r--r--src/routes/(app)/settings/account/+page.server.ts6
-rw-r--r--src/routes/(app)/settings/account/+page.svelte40
-rw-r--r--src/routes/(app)/settings/nostr/+page.server.ts8
-rw-r--r--src/routes/(app)/settings/nostr/+page.svelte119
-rw-r--r--src/routes/(app)/settings/profile/+page.svelte20
-rw-r--r--src/routes/(app)/settings/security/+page.svelte12
-rw-r--r--src/routes/(app)/settings/shopify/+page.svelte2
-rw-r--r--src/routes/(app)/settings/square/+page.server.ts18
-rw-r--r--src/routes/(app)/settings/square/+page.svelte0
10 files changed, 186 insertions, 53 deletions
diff --git a/src/routes/(app)/settings/+layout.svelte b/src/routes/(app)/settings/+layout.svelte
index 33398ed5..88708843 100644
--- a/src/routes/(app)/settings/+layout.svelte
+++ b/src/routes/(app)/settings/+layout.svelte
@@ -15,7 +15,7 @@
import { sign, send, getPrivateKey } from "$lib/nostr";
import { invalidateAll } from "$app/navigation";
import { getPublicKey } from "nostr-tools";
- import { bytesToHex } from "@noble/hashes/utils";
+ import { bytesToHex } from "@noble/hashes/utils.js";
let { children, data, form } = $props();
@@ -133,8 +133,7 @@
event = await sign(event, user);
send(event);
} catch (e) {
- console.log(e);
- warning("Nostr profile could not be updated");
+ warning("Nostr profile not updated");
}
}
@@ -209,9 +208,7 @@
{$t("user.settings.header")}
</h1>
- <div
- class="flex flex-wrap justify-around items-center border-b pb-3"
- >
+ <div class="flex flex-wrap justify-around items-center border-b pb-3">
{#each tabs as { name, key }}
<a
href={`/settings/${name}`}
@@ -228,17 +225,18 @@
</div>
<div
- class="z-10 fixed bottom-0 bg-base-100 shadow border-t border-t-8 border-accent w-full flex justify-center items-center py-3"
+ class="z-10 fixed bottom-0 bg-base-100 shadow border-accent w-full flex justify-center items-center py-3"
>
<button
bind:this={$save}
type="submit"
- class="btn !w-auto"
+ class="btn btn-accent !w-auto"
class:bg-base-200={submitting}
>
{#if submitting}
<Spinner />
{:else}
+ <iconify-icon icon="ph:floppy-disk-bold" width={32}></iconify-icon>
<div class="my-auto">{$t("user.settings.saveSettings")}</div>
{/if}
</button>
diff --git a/src/routes/(app)/settings/account/+page.server.ts b/src/routes/(app)/settings/account/+page.server.ts
index f0cc700e..ebb90f05 100644
--- a/src/routes/(app)/settings/account/+page.server.ts
+++ b/src/routes/(app)/settings/account/+page.server.ts
@@ -1,9 +1,11 @@
import getRates from "$lib/rates";
import updateUser from "$lib/settings";
+import { auth, get } from "$lib/utils";
-export async function load() {
+export async function load({ cookies }) {
const rates = await getRates();
- return { rates };
+ const connect = await get("/square/connect", auth(cookies));
+ return { connect, rates };
}
export const actions = {
diff --git a/src/routes/(app)/settings/account/+page.svelte b/src/routes/(app)/settings/account/+page.svelte
index c5c75d88..8b38ffe8 100644
--- a/src/routes/(app)/settings/account/+page.svelte
+++ b/src/routes/(app)/settings/account/+page.svelte
@@ -1,5 +1,6 @@
<script>
import { run } from "svelte/legacy";
+ import { theme } from "$lib/store";
import { onMount, tick } from "svelte";
import { browser } from "$app/environment";
@@ -13,8 +14,8 @@
let { data } = $props();
let { user } = $state(data);
- let { rates, subscriptions } = data;
- let { currency, email, verified } = $state(user);
+ let { connect, rates, subscriptions } = data;
+ let { currency, email, tip, verified } = $state(user);
let rate = rates[currency];
let fiats = Object.keys(rates).sort((a, b) => a.localeCompare(b));
@@ -97,6 +98,8 @@
$effect(() => {
if (email !== user.email) verified = false;
});
+
+ let revoke = () => {};
</script>
<div>
@@ -181,6 +184,19 @@
</p>
</div>
+<div class="space-y-1 relative">
+ <label for="tip" class="font-bold block">{$t("user.settings.tip")}</label>
+ <p class="text-secondary">
+ {$t("user.settings.tipDesc")}
+ </p>
+
+ <label
+ class="input input-bordered border-primary input-lg rounded-2xl flex items-center gap-2"
+ >
+ <input type="text" name="tip" class="clean" bind:value={tip} />
+ </label>
+</div>
+
<div>
<div class="flex justify-between items-center">
<span class="font-bold">{$t("user.settings.autoWithdraw")}</span>
@@ -191,7 +207,7 @@
</p>
</div>
-{#if user.autowithdraw}
+<div class:hidden={!user.autowithdraw}>
<div class="mb-2">
<label for="display" class="font-bold mb-1 block"
>{$t("user.settings.destination")}</label
@@ -252,7 +268,7 @@
{$t("user.settings.reserveDesc")}
</p>
</div>
-{/if}
+</div>
{#if editingThreshold}
<div
@@ -306,3 +322,19 @@
</div>
</div>
{/if}
+
+{#if connect !== "connected"}
+ <a href={connect} class="btn flex">
+ <img src="/images/square.svg" class="w-12" />
+ <div>{$t("user.settings.connectSquare")}</div></a
+ >
+{:else}
+ <button type="button" onclick={revoke} class="btn">
+ <img
+ src="/images/square.svg"
+ class="w-12"
+ class:invert={$theme === "dark"}
+ />
+ <div>{$t("user.settings.revokeSquare")}</div>
+ </button>
+{/if}
diff --git a/src/routes/(app)/settings/nostr/+page.server.ts b/src/routes/(app)/settings/nostr/+page.server.ts
index 1767ebaf..84b3fa27 100644
--- a/src/routes/(app)/settings/nostr/+page.server.ts
+++ b/src/routes/(app)/settings/nostr/+page.server.ts
@@ -4,7 +4,13 @@ import { auth, get } from "$lib/utils";
export const load = async ({ cookies }) => {
const { challenge } = await get("/challenge");
const apps = await get("/apps", auth(cookies));
- return { apps, challenge };
+
+ let offer;
+ try {
+ ({ hash: offer } = await get("/offer", auth(cookies)));
+ } catch (e) {}
+
+ return { apps, challenge, offer };
};
export const actions = {
diff --git a/src/routes/(app)/settings/nostr/+page.svelte b/src/routes/(app)/settings/nostr/+page.svelte
index 421b590a..3fe6e445 100644
--- a/src/routes/(app)/settings/nostr/+page.svelte
+++ b/src/routes/(app)/settings/nostr/+page.svelte
@@ -1,7 +1,7 @@
<script>
import { untrack } from "svelte";
import { browser } from "$app/environment";
- import { getNsec } from "$lib/nostr";
+ import { getNsec, send, sign } from "$lib/nostr";
import { page } from "$app/stores";
import { tick } from "svelte";
import { t } from "$lib/translations";
@@ -13,7 +13,7 @@
import { signer, save } from "$lib/store";
let { data } = $props();
- let { apps, challenge, user } = $derived(data);
+ let { apps, challenge, offer, user } = $derived(data);
let npub = $state(untrack(() => user.npub));
let extensionAvailable = $derived(browser && window.nostr);
let { locale } = $derived(user);
@@ -41,6 +41,33 @@
await tick();
$save.click();
};
+
+ let publishing = $state(),
+ published = $state();
+
+ // Kind 10058 is the NIP-177 BOLT12 offer list — publishing it lets nostr
+ // clients (Amethyst v1.13+) zap this user over BOLT12. Replaceable event,
+ // so republishing just updates it.
+ let publishOffer = async () => {
+ try {
+ publishing = true;
+ let event = {
+ kind: 10058,
+ content: "",
+ created_at: Math.round(Date.now() / 1000),
+ tags: [["offer", offer]],
+ };
+
+ event = await sign(event, user);
+ await send(event);
+ published = true;
+ } catch (e) {
+ console.log(e);
+ fail($t("user.settings.offerPublishFailed"));
+ } finally {
+ publishing = false;
+ }
+ };
</script>
<input type="hidden" name="challenge" value={challenge} />
@@ -145,6 +172,53 @@
</div>
</div>
+{#if offer}
+ <div class="space-y-2">
+ <h2 class="text-2xl font-bold mb-2">
+ {$t("user.settings.bolt12Offer")}
+ </h2>
+ <p class="text-secondary mb-4">
+ {$t("user.settings.bolt12OfferDescription")}
+ </p>
+
+ <div class="break-all text-secondary">{offer}</div>
+
+ <div class="flex my-auto gap-1">
+ <button
+ type="button"
+ class="my-auto btn btn-circle !w-auto grow"
+ onclick={() => copy(offer)}
+ aria-label="Copy"
+ ><iconify-icon noobserver icon="ph:copy-bold" width="32"
+ ></iconify-icon> {$t("accounts.copy")}</button
+ >
+
+ <a
+ href={`/qr/${encodeURIComponent(offer)}`}
+ class="my-auto btn btn-circle !w-auto grow"
+ aria-label="QR"
+ >
+ <iconify-icon noobserver icon="ph:qr-code-bold" width="32"
+ ></iconify-icon>
+ {$t("accounts.qr")}
+ </a>
+
+ <button
+ type="button"
+ class="my-auto btn btn-circle !w-auto grow"
+ onclick={publishOffer}
+ disabled={publishing}
+ aria-label="Publish"
+ ><iconify-icon noobserver icon="ph:megaphone-bold" width="32"
+ ></iconify-icon>
+ {published
+ ? $t("user.settings.offerPublished")
+ : $t("user.settings.publishOffer")}</button
+ >
+ </div>
+ </div>
+{/if}
+
{#if revealNwc}
<div class="break-all grow">
{nwc}
@@ -163,25 +237,26 @@
<div class="space-y-2">
<div class="font-bold">{$t("user.nostrPubkey")}</div>
- <div class="flex gap-4">
- <textarea name="pubkey" bind:value={npub} rows={3}> </textarea>
- <div class="flex my-auto gap-1">
- <button
- type="button"
- class="my-auto btn btn-circle"
- onclick={() => copy(npub)}
- aria-label="Copy"
- ><iconify-icon noobserver icon="ph:copy-bold" width="32"
- ></iconify-icon></button
- >
- <a
- href={`/qr/${encodeURIComponent(npub)}`}
- class="my-auto btn btn-circle"
- aria-label="QR"
- >
- <iconify-icon noobserver icon="ph:qr-code-bold" width="32"
- ></iconify-icon>
- </a>
+ <textarea name="pubkey" bind:value={npub} rows={3}></textarea>
+ <div>
+ <div class="flex my-auto gap-1">
+ <button
+ type="button"
+ class="my-auto btn btn-circle !w-auto grow"
+ onclick={() => copy(npub)}
+ aria-label="Copy"
+ ><iconify-icon noobserver icon="ph:copy-bold" width="32"
+ ></iconify-icon> {$t("accounts.copy")}</button
+ >
+
+ <a
+ href={`/qr/${encodeURIComponent(npub)}`}
+ class="my-auto btn btn-circle !w-auto grow"
+ aria-label="QR"
+ >
+ <iconify-icon noobserver icon="ph:qr-code-bold" width="32"></iconify-icon>
+{$t("accounts.qr")}
+ </a>
</div>
</div>
@@ -193,7 +268,7 @@
{/if}
</div>
-{#if user.nsec}
+{#if user.hasnsec || user.nsec}
<div class="space-y-2">
<label for="seedphrase" class="font-bold"
>{$t("user.settings.nostrKeys")}</label
diff --git a/src/routes/(app)/settings/profile/+page.svelte b/src/routes/(app)/settings/profile/+page.svelte
index bee50d39..3b7de9d3 100644
--- a/src/routes/(app)/settings/profile/+page.svelte
+++ b/src/routes/(app)/settings/profile/+page.svelte
@@ -3,16 +3,18 @@
import { avatar, banner as bannerStore } from "$lib/store";
import { t } from "$lib/translations";
import { page } from "$app/stores";
+ import PasswordInput from "$comp/PasswordInput.svelte";
let { data } = $props();
let { user } = $derived(data);
let { id } = user;
- let { about, banner, picture, display, username } =
- $state(user);
+ let { about, banner, picture, display, username } = $state(user);
let avatarFile,
avatarInput = $state(),
bannerFile,
+ password = $state(),
+ revealPassword = $state(),
bannerInput = $state();
let selectAvatar = () => avatarInput.click();
@@ -64,6 +66,16 @@
</div>
</div>
+<input type="hidden" name="confirm" bind:value={password} />
+
+<div>
+ <label for="password" class="block font-bold block mb-1"
+ >{$t("user.settings.newPassword")}</label
+ >
+
+ <PasswordInput bind:value={password} placeholder="(Leave blank to keep unchanged)" />
+</div>
+
<div>
<label for="display" class="font-bold mb-1 block"
>{$t("user.settings.displayName")}</label
@@ -104,7 +116,7 @@
>
<input
type="file"
- class="hidden"
+ class="hidden!"
bind:this={avatarInput}
onchange={(e) => handleFile(e, "picture")}
/>
@@ -146,7 +158,7 @@
>
<input
type="file"
- class="hidden"
+ class="hidden!"
bind:this={bannerInput}
onchange={(e) => handleFile(e, "banner")}
/>
diff --git a/src/routes/(app)/settings/security/+page.svelte b/src/routes/(app)/settings/security/+page.svelte
index ea1bf1f9..8c38bca1 100644
--- a/src/routes/(app)/settings/security/+page.svelte
+++ b/src/routes/(app)/settings/security/+page.svelte
@@ -4,7 +4,6 @@
import { t } from "$lib/translations";
import Pin from "$comp/Pin.svelte";
import Qr from "$comp/Qr.svelte";
- import PasswordInput from "$comp/PasswordInput.svelte";
import { copy, post, success, fail } from "$lib/utils";
import { save, pin as current } from "$lib/store";
import { invalidate } from "$app/navigation";
@@ -21,9 +20,7 @@
newNsec = $state(),
nsec = $state(),
otp = $state(),
- password = $state(),
pin = $state(""),
- revealPassword = $state(),
revealSeed,
revealNsec = $state(),
token = $state(""),
@@ -157,15 +154,6 @@
</script>
<input type="hidden" name="newpin" value={disablingPin ? "delete" : pin} />
-<input type="hidden" name="confirm" bind:value={password} />
-
-<div>
- <label for="password" class="block font-bold block mb-1"
- >{$t("user.settings.newPassword")}</label
- >
-
- <PasswordInput bind:value={password} />
-</div>
<div>
<span class="font-bold mb-1"
diff --git a/src/routes/(app)/settings/shopify/+page.svelte b/src/routes/(app)/settings/shopify/+page.svelte
index 5b0c7ceb..04f82009 100644
--- a/src/routes/(app)/settings/shopify/+page.svelte
+++ b/src/routes/(app)/settings/shopify/+page.svelte
@@ -91,6 +91,8 @@
</div>
<style>
+ @reference "../../../../app.css";
+
.active {
@apply text-blue-500;
}
diff --git a/src/routes/(app)/settings/square/+page.server.ts b/src/routes/(app)/settings/square/+page.server.ts
new file mode 100644
index 00000000..e00bdf1b
--- /dev/null
+++ b/src/routes/(app)/settings/square/+page.server.ts
@@ -0,0 +1,18 @@
+import { auth, get } from "$lib/utils";
+import { fail, redirect } from "@sveltejs/kit";
+
+export async function load({ cookies, url }) {
+ let connected;
+
+ const query = url.searchParams.toString();
+
+ try {
+ await get(`/square/auth?${query}`, auth(cookies));
+ connected = true;
+ } catch (e) {
+ const { message } = e as Error;
+ fail(400, { message });
+ }
+
+ if (connected) redirect(307, "/settings/account");
+}
diff --git a/src/routes/(app)/settings/square/+page.svelte b/src/routes/(app)/settings/square/+page.svelte
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/routes/(app)/settings/square/+page.svelte