aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/(app)')
-rw-r--r--src/routes/(app)/settings/delete/+page.server.ts26
-rw-r--r--src/routes/(app)/settings/delete/+page@(app).svelte82
-rw-r--r--src/routes/(app)/settings/security/+page.svelte11
3 files changed, 119 insertions, 0 deletions
diff --git a/src/routes/(app)/settings/delete/+page.server.ts b/src/routes/(app)/settings/delete/+page.server.ts
new file mode 100644
index 00000000..05f4f026
--- /dev/null
+++ b/src/routes/(app)/settings/delete/+page.server.ts
@@ -0,0 +1,26 @@
+import { auth, post } from "$lib/utils";
+import { fail, redirect } from "@sveltejs/kit";
+
+export async function load({ parent, url }) {
+ const { user } = await parent();
+ if (!user) redirect(307, `/login?redirect=${encodeURIComponent(url.pathname)}`);
+}
+
+export const actions = {
+ default: async ({ cookies, request }) => {
+ const form = await request.formData();
+ const confirm = String(form.get("confirm") || "");
+ const pin = String(form.get("pin") || "");
+
+ try {
+ // The server wipes everything (lib/deluser), including disabling the
+ // account's bolt12 offers, and refuses while a real balance remains.
+ await post("/delete", { confirm, ...(pin ? { pin } : {}) }, auth(cookies));
+ } catch (e) {
+ return fail(400, { error: e?.message || String(e), confirm });
+ }
+
+ // The account is gone; /logout clears the session cookies.
+ redirect(303, "/logout");
+ },
+};
diff --git a/src/routes/(app)/settings/delete/+page@(app).svelte b/src/routes/(app)/settings/delete/+page@(app).svelte
new file mode 100644
index 00000000..e19a6604
--- /dev/null
+++ b/src/routes/(app)/settings/delete/+page@(app).svelte
@@ -0,0 +1,82 @@
+<script>
+ // Rendered outside the settings layout (+page@(app)) so this form isn't
+ // nested inside the settings save form.
+ import { enhance } from "$app/forms";
+ import { t } from "$lib/translations";
+
+ let { data, form } = $props();
+ let { user } = $derived(data);
+
+ let confirm = $state(form?.confirm || "");
+ let working = $state(false);
+ let matches = $derived(
+ confirm.trim().toLowerCase() === user.username.toLowerCase(),
+ );
+</script>
+
+<div class="container px-4 max-w-xl mx-auto space-y-5 mt-10">
+ <h1 class="text-3xl font-bold text-center">
+ {$t("user.settings.deleteAccount")}
+ </h1>
+
+ <div class="rounded-2xl border-2 border-error p-4 space-y-3">
+ <p class="flex gap-2">
+ <iconify-icon
+ noobserver
+ icon="ph:warning-bold"
+ width="24"
+ class="text-error shrink-0"
+ ></iconify-icon>
+ <span>{$t("user.settings.deleteAccountWarning")}</span>
+ </p>
+ <p class="text-secondary">{$t("user.settings.deleteAccountBalance")}</p>
+ </div>
+
+ {#if form?.error}<p class="text-error">{form.error}</p>{/if}
+
+ <form
+ method="POST"
+ class="space-y-3"
+ use:enhance={() => {
+ working = true;
+ return async ({ update }) => {
+ await update({ reset: false });
+ working = false;
+ };
+ }}
+ >
+ <label class="block space-y-1">
+ <span class="font-bold">{$t("user.settings.deleteAccountConfirm")}</span>
+ <input
+ class="input input-bordered w-full"
+ name="confirm"
+ autocomplete="off"
+ autocapitalize="off"
+ spellcheck="false"
+ placeholder={user.username}
+ bind:value={confirm}
+ />
+ </label>
+
+ {#if user.haspin}
+ <input
+ class="input input-bordered w-full"
+ type="password"
+ inputmode="numeric"
+ name="pin"
+ placeholder={$t("user.settings.securityPIN")}
+ />
+ {/if}
+
+ <button
+ class="btn btn-error w-full rounded-2xl"
+ disabled={!matches || working}
+ >
+ <iconify-icon noobserver icon="ph:trash-bold" width="24"></iconify-icon>
+ {$t("user.settings.deleteAccountButton")}
+ </button>
+ <a href="/settings/security" class="btn w-full rounded-2xl">
+ {$t("payments.cancel")}
+ </a>
+ </form>
+</div>
diff --git a/src/routes/(app)/settings/security/+page.svelte b/src/routes/(app)/settings/security/+page.svelte
index b774d919..b8c71aa6 100644
--- a/src/routes/(app)/settings/security/+page.svelte
+++ b/src/routes/(app)/settings/security/+page.svelte
@@ -288,3 +288,14 @@
</button>
</a>
</div>
+
+<div>
+ <span class="font-bold mb-1">{$t("user.settings.deleteAccount")}</span>
+ <p class="text-secondary mb-4">
+ {$t("user.settings.deleteAccountDescription")}
+ </p>
+ <a href="/settings/delete" class="btn btn-outline btn-error">
+ <iconify-icon noobserver icon="ph:trash-bold" width="32"></iconify-icon>
+ {$t("user.settings.deleteAccount")}
+ </a>
+</div>