feat(frontend): self-service delete, force delete pages
This commit is contained in:
parent
3f8f6d0f23
commit
e24c4f9b00
9 changed files with 298 additions and 11 deletions
|
@ -110,8 +110,9 @@
|
|||
|
||||
{#if !data.user.deleted}
|
||||
<div class="mb-3">
|
||||
<h4>Delete your account</h4>
|
||||
<p></p>
|
||||
<h4>{$t("settings.soft-delete-header")}</h4>
|
||||
<p>{$t("settings.soft-delete-hint")}</p>
|
||||
<a href="/settings/delete" class="btn btn-danger">{$t("settings.soft-delete-button")}</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
41
Foxnouns.Frontend/src/routes/settings/delete/+page.server.ts
Normal file
41
Foxnouns.Frontend/src/routes/settings/delete/+page.server.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { fastRequest } from "$api";
|
||||
import ApiError, { ErrorCode, type RawApiError } from "$api/error";
|
||||
import { clearToken } from "$lib";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
export const load = async ({ parent }) => {
|
||||
const { meUser } = await parent();
|
||||
if (!meUser) redirect(303, "/");
|
||||
|
||||
if (meUser.deleted)
|
||||
throw new ApiError({
|
||||
message: "You cannot use this page.",
|
||||
status: 403,
|
||||
code: ErrorCode.Forbidden,
|
||||
});
|
||||
|
||||
return { user: meUser! };
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
default: async ({ request, fetch, cookies }) => {
|
||||
const body = await request.formData();
|
||||
const username = body.get("username") as string;
|
||||
const currentUsername = body.get("current-username") as string;
|
||||
|
||||
if (!username || username !== currentUsername) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
message: "Username doesn't match your username.",
|
||||
status: 400,
|
||||
code: ErrorCode.BadRequest,
|
||||
} as RawApiError,
|
||||
};
|
||||
}
|
||||
|
||||
await fastRequest("POST", "/self-delete/delete", { fetch, cookies, isInternal: true });
|
||||
clearToken(cookies);
|
||||
redirect(303, "/settings/delete/success");
|
||||
},
|
||||
};
|
55
Foxnouns.Frontend/src/routes/settings/delete/+page@.svelte
Normal file
55
Foxnouns.Frontend/src/routes/settings/delete/+page@.svelte
Normal file
|
@ -0,0 +1,55 @@
|
|||
<script lang="ts">
|
||||
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
import type { ActionData, PageData } from "./$types";
|
||||
|
||||
type Props = { data: PageData; form: ActionData };
|
||||
let { data, form }: Props = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t("settings.soft-delete-page-header")} • pronouns.cc</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
<div class="w-lg-75 mx-auto">
|
||||
<h3>{$t("settings.soft-delete-page-header")}</h3>
|
||||
|
||||
<p>
|
||||
{$t("settings.soft-delete-page-explanation")}
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>{$t("settings.soft-delete-90-days")}</li>
|
||||
<li>
|
||||
{$t("settings.soft-delete-can-reactivate")}
|
||||
</li>
|
||||
<li>{$t("settings.soft-delete-keep-username")}</li>
|
||||
<li>
|
||||
{$t("settings.soft-delete-can-delete-permanently")}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<form method="POST">
|
||||
<FormStatusMarker {form} />
|
||||
<p>
|
||||
{$t("settings.soft-delete-input-label", { username: data.user.username })}
|
||||
<input
|
||||
class="form-control mt-2"
|
||||
type="text"
|
||||
name="username"
|
||||
required
|
||||
placeholder="@{data.user.username}"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<input type="hidden" value="@{data.user.username}" readonly name="current-username" />
|
||||
</p>
|
||||
<div class="btn-group mb-2">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
{$t("settings.soft-delete-page-button")}
|
||||
</button>
|
||||
<a href="/settings" class="btn btn-secondary">{$t("settings.force-delete-page-cancel")}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { t } from "$lib/i18n";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t("settings.soft-delete-page-header")} • pronouns.cc</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
<div class="w-lg-75 mx-auto">
|
||||
<h3>{$t("settings.account-is-deactivated-header")}</h3>
|
||||
<p>
|
||||
{$t("settings.account-is-deactivated-description")}
|
||||
</p>
|
||||
<p>{$t("settings.account-is-deleted-close-page")}</p>
|
||||
<p>
|
||||
<a href="/" class="btn btn-secondary">{$t("error.back-to-main-page-button")}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,53 @@
|
|||
import { fastRequest } from "$api";
|
||||
import ApiError, { ErrorCode, type RawApiError } from "$api/error";
|
||||
import { clearToken } from "$lib";
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
|
||||
export const load = async ({ parent }) => {
|
||||
const { meUser } = await parent();
|
||||
if (!meUser) redirect(303, "/");
|
||||
|
||||
if (!meUser.deleted)
|
||||
throw new ApiError({
|
||||
message: "You cannot use this page.",
|
||||
status: 403,
|
||||
code: ErrorCode.Forbidden,
|
||||
});
|
||||
|
||||
return { user: meUser! };
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
default: async ({ request, fetch, cookies }) => {
|
||||
const body = await request.formData();
|
||||
const username = body.get("username") as string;
|
||||
const currentUsername = body.get("current-username") as string;
|
||||
const confirmed = !!body.get("confirm");
|
||||
|
||||
if (!username || username !== currentUsername) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
message: "Username doesn't match your username.",
|
||||
status: 400,
|
||||
code: ErrorCode.BadRequest,
|
||||
} as RawApiError,
|
||||
};
|
||||
}
|
||||
|
||||
if (!confirmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
message: "You must check the box to continue.",
|
||||
status: 400,
|
||||
code: ErrorCode.BadRequest,
|
||||
} as RawApiError,
|
||||
};
|
||||
}
|
||||
|
||||
await fastRequest("POST", "/self-delete/force", { fetch, cookies, isInternal: true });
|
||||
clearToken(cookies);
|
||||
redirect(303, "/settings/force-delete/success");
|
||||
},
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
<script lang="ts">
|
||||
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
import type { ActionData, PageData } from "./$types";
|
||||
|
||||
type Props = { data: PageData; form: ActionData };
|
||||
let { data, form }: Props = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t("settings.force-delete-page-header")} • pronouns.cc</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
<div class="w-lg-75 mx-auto">
|
||||
<h3>{$t("settings.force-delete-page-header")}</h3>
|
||||
|
||||
<p>
|
||||
{$t("settings.force-delete-page-explanation")}
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>{$t("settings.force-delete-immediate-delete")}</li>
|
||||
<li>{$t("settings.force-delete-username-available")}</li>
|
||||
<li><strong>{$t("settings.force-delete-irreversible")}</strong></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
{$t("settings.force-delete-export-hint")}
|
||||
<a href="/settings/export">{$t("settings.force-delete-export-link")}</a>
|
||||
</p>
|
||||
|
||||
<form method="POST">
|
||||
<FormStatusMarker {form} />
|
||||
<p>
|
||||
{$t("settings.force-delete-input-label", { username: data.user.username })}
|
||||
<input
|
||||
class="form-control mt-2"
|
||||
type="text"
|
||||
name="username"
|
||||
required
|
||||
placeholder="@{data.user.username}"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<input type="hidden" value="@{data.user.username}" readonly name="current-username" />
|
||||
</p>
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
value="yes"
|
||||
name="confirm"
|
||||
id="confirm"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="confirm">
|
||||
{$t("settings.force-delete-checkbox-label")}
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-group mt-3 mb-2">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
{$t("settings.force-delete-page-button")}
|
||||
</button>
|
||||
<a href="/settings" class="btn btn-secondary">{$t("settings.force-delete-page-cancel")}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { t } from "$lib/i18n";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t("settings.force-delete-page-header")} • pronouns.cc</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
<div class="w-lg-75 mx-auto">
|
||||
<h3>{$t("settings.account-is-deleted-header")}</h3>
|
||||
<p>
|
||||
{$t("settings.account-is-deleted-permanently-description")}
|
||||
</p>
|
||||
<p>{$t("settings.account-is-deleted-close-page")}</p>
|
||||
<p>
|
||||
<a href="/" class="btn btn-secondary">{$t("error.back-to-main-page-button")}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
|
@ -6,15 +6,17 @@
|
|||
let { data }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="w-lg-75 mx-auto">
|
||||
<h3>{$t("settings.reactivated-header")}</h3>
|
||||
<div class="container">
|
||||
<div class="w-lg-75 mx-auto">
|
||||
<h3>{$t("settings.reactivated-header")}</h3>
|
||||
|
||||
<p>{$t("settings.reactivated-explanation")}</p>
|
||||
<p>{$t("settings.reactivated-explanation")}</p>
|
||||
|
||||
<div class="btn-group">
|
||||
<a href="/settings" class="btn btn-primary">{$t("edit-profile.back-to-settings-tab")}</a>
|
||||
<a href="/@{data.user.username}" class="btn btn-secondary">
|
||||
{$t("error.back-to-profile-button")}
|
||||
</a>
|
||||
<div class="btn-group">
|
||||
<a href="/settings" class="btn btn-primary">{$t("edit-profile.back-to-settings-tab")}</a>
|
||||
<a href="/@{data.user.username}" class="btn btn-secondary">
|
||||
{$t("error.back-to-profile-button")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue