diff options
Diffstat (limited to 'src/routes/(app)/settings')
| -rw-r--r-- | src/routes/(app)/settings/+layout.svelte | 14 | ||||
| -rw-r--r-- | src/routes/(app)/settings/account/+page.server.ts | 6 | ||||
| -rw-r--r-- | src/routes/(app)/settings/account/+page.svelte | 40 | ||||
| -rw-r--r-- | src/routes/(app)/settings/nostr/+page.server.ts | 8 | ||||
| -rw-r--r-- | src/routes/(app)/settings/nostr/+page.svelte | 119 | ||||
| -rw-r--r-- | src/routes/(app)/settings/profile/+page.svelte | 20 | ||||
| -rw-r--r-- | src/routes/(app)/settings/security/+page.svelte | 12 | ||||
| -rw-r--r-- | src/routes/(app)/settings/shopify/+page.svelte | 2 | ||||
| -rw-r--r-- | src/routes/(app)/settings/square/+page.server.ts | 18 | ||||
| -rw-r--r-- | src/routes/(app)/settings/square/+page.svelte | 0 |
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 |
