feat(frontend): export ui
This commit is contained in:
parent
74222ead45
commit
c20831f20d
8 changed files with 104 additions and 9 deletions
|
@ -112,7 +112,13 @@
|
||||||
"create-member-name-label": "Member name",
|
"create-member-name-label": "Member name",
|
||||||
"auth-remove-method": "Remove",
|
"auth-remove-method": "Remove",
|
||||||
"force-log-out-warning": "Make sure you're still able to log in before using this!",
|
"force-log-out-warning": "Make sure you're still able to log in before using this!",
|
||||||
"force-log-out-confirmation": "Are you sure you want to log out from all devices? If you just want to log out from this device, click the \"Log out\" button on your settings page."
|
"force-log-out-confirmation": "Are you sure you want to log out from all devices? If you just want to log out from this device, click the \"Log out\" button on your settings page.",
|
||||||
|
"export-request-success": "Successfully requested a new export! Please note that it may take a few minutes to complete, especially if you have a lot of members.",
|
||||||
|
"export-title": "Request a copy of your data",
|
||||||
|
"export-info": "You can request a copy of your data once every 24 hours. Exports are stored for 15 days (a little over two weeks) and then deleted.",
|
||||||
|
"export-expires-at": "(expires {{expiresAt}})",
|
||||||
|
"export-download": "Download export",
|
||||||
|
"export-request-button": "Request a new export"
|
||||||
},
|
},
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import Error from "$components/Error.svelte";
|
import Error from "$components/Error.svelte";
|
||||||
import { idTimestamp } from "$lib";
|
import { idTimestamp } from "$lib";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
|
||||||
type Props = { data: PageData; form: ActionData };
|
type Props = { data: PageData; form: ActionData };
|
||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<h5>Change your username</h5>
|
<h5>Change your username</h5>
|
||||||
<form method="POST" action="?/changeUsername">
|
<form method="POST" action="?/changeUsername" use:enhance>
|
||||||
<FormGroup class="mb-3">
|
<FormGroup class="mb-3">
|
||||||
<InputGroup class="m-1 mt-3 w-md-75">
|
<InputGroup class="m-1 mt-3 w-md-75">
|
||||||
<Input
|
<Input
|
||||||
|
|
35
Foxnouns.Frontend/src/routes/settings/export/+page.server.ts
Normal file
35
Foxnouns.Frontend/src/routes/settings/export/+page.server.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { apiRequest, fastRequest } from "$api";
|
||||||
|
import ApiError from "$api/error.js";
|
||||||
|
import log from "$lib/log.js";
|
||||||
|
import { DateTime, Duration } from "luxon";
|
||||||
|
|
||||||
|
type Export = { url: string | null; expires_at: string | null };
|
||||||
|
|
||||||
|
export const load = async ({ fetch, cookies }) => {
|
||||||
|
const resp = await apiRequest<Export>("GET", "/data-exports", {
|
||||||
|
fetch,
|
||||||
|
cookies,
|
||||||
|
isInternal: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let canExport = true;
|
||||||
|
if (resp.expires_at) {
|
||||||
|
const created = DateTime.fromISO(resp.expires_at).minus(Duration.fromObject({ days: 15 }));
|
||||||
|
canExport = DateTime.now().diff(created, "seconds").seconds >= 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { url: resp.url, expiresAt: resp.expires_at, canExport };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
default: async ({ fetch, cookies }) => {
|
||||||
|
try {
|
||||||
|
fastRequest("POST", "/data-exports", { fetch, cookies, isInternal: true });
|
||||||
|
return { ok: true, error: null };
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ApiError) return { ok: false, error: e.obj };
|
||||||
|
log.error("Error requesting data export:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
49
Foxnouns.Frontend/src/routes/settings/export/+page.svelte
Normal file
49
Foxnouns.Frontend/src/routes/settings/export/+page.svelte
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import type { ActionData, PageData } from "./$types";
|
||||||
|
import ErrorAlert from "$components/ErrorAlert.svelte";
|
||||||
|
import { Icon } from "@sveltestrap/sveltestrap";
|
||||||
|
import { t } from "$lib/i18n";
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
|
||||||
|
type Props = { data: PageData; form: ActionData };
|
||||||
|
let { data, form }: Props = $props();
|
||||||
|
|
||||||
|
let expiresAt = $derived.by(() => {
|
||||||
|
if (!data.expiresAt) return null;
|
||||||
|
return DateTime.fromISO(data.expiresAt);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="mx-auto w-lg-75">
|
||||||
|
<h3>{$t("settings.export-title")}</h3>
|
||||||
|
|
||||||
|
{#if form?.ok}
|
||||||
|
<p class="text-success-emphasis">
|
||||||
|
<Icon name="check-circle-fill" />
|
||||||
|
{$t("settings.export-request-success")}
|
||||||
|
</p>
|
||||||
|
{:else if form?.error}
|
||||||
|
<ErrorAlert error={form.error} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{$t("settings.export-info")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="POST" use:enhance>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="submit" class="btn btn-primary" disabled={!data.canExport}>
|
||||||
|
{$t("settings.export-request-button")}
|
||||||
|
</button>
|
||||||
|
{#if data.url}
|
||||||
|
<a href={data.url} target="_blank" class="btn btn-success">
|
||||||
|
{$t("settings.export-download")}
|
||||||
|
{#if expiresAt}
|
||||||
|
{$t("settings.export-expires-at", { expiresAt: expiresAt.toRelative() })}
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -14,6 +14,7 @@
|
||||||
import SidEditor from "$components/editor/SidEditor.svelte";
|
import SidEditor from "$components/editor/SidEditor.svelte";
|
||||||
import BioEditor from "$components/editor/BioEditor.svelte";
|
import BioEditor from "$components/editor/BioEditor.svelte";
|
||||||
import { PUBLIC_BASE_URL } from "$env/static/public";
|
import { PUBLIC_BASE_URL } from "$env/static/public";
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
|
||||||
type Props = { data: PageData; form: ActionData };
|
type Props = { data: PageData; form: ActionData };
|
||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
@ -83,7 +84,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<h4>{$t("edit-profile.member-name")}</h4>
|
<h4>{$t("edit-profile.member-name")}</h4>
|
||||||
<form method="POST" action="?/changeName" class="mb-3">
|
<form method="POST" action="?/changeName" class="mb-3" use:enhance>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<input
|
<input
|
||||||
name="name"
|
name="name"
|
||||||
|
@ -99,7 +100,7 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<h4>{$t("edit-profile.display-name")}</h4>
|
<h4>{$t("edit-profile.display-name")}</h4>
|
||||||
<form class="mb-3" method="POST" action="?/changeDisplayName">
|
<form class="mb-3" method="POST" action="?/changeDisplayName" use:enhance>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<input
|
<input
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
@ -117,7 +118,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<h4>{$t("edit-profile.profile-options-header")}</h4>
|
<h4>{$t("edit-profile.profile-options-header")}</h4>
|
||||||
<form method="POST" action="?/options">
|
<form method="POST" action="?/options" use:enhance>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
|
@ -146,7 +147,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<h4>{$t("edit-profile.bio-tab")}</h4>
|
<h4>{$t("edit-profile.bio-tab")}</h4>
|
||||||
<form method="POST" action="?/bio">
|
<form method="POST" action="?/bio" use:enhance>
|
||||||
<BioEditor bind:value={bio} maxLength={data.meta.limits.bio_length} />
|
<BioEditor bind:value={bio} maxLength={data.meta.limits.bio_length} />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
import ErrorAlert from "$components/ErrorAlert.svelte";
|
import ErrorAlert from "$components/ErrorAlert.svelte";
|
||||||
import { t } from "$lib/i18n";
|
import { t } from "$lib/i18n";
|
||||||
import type { ActionData } from "./$types";
|
import type { ActionData } from "./$types";
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
<ErrorAlert error={form.error} />
|
<ErrorAlert error={form.error} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<form method="POST">
|
<form method="POST" use:enhance>
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label class="form-label" for="name">{$t("settings.create-member-name-label")}</label>
|
<label class="form-label" for="name">{$t("settings.create-member-name-label")}</label>
|
||||||
<input class="form-control" type="text" id="name" name="name" required autocomplete="off" />
|
<input class="form-control" type="text" id="name" name="name" required autocomplete="off" />
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import { DateTime, FixedOffsetZone } from "luxon";
|
import { DateTime, FixedOffsetZone } from "luxon";
|
||||||
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
|
import FormStatusMarker from "$components/editor/FormStatusMarker.svelte";
|
||||||
import SidEditor from "$components/editor/SidEditor.svelte";
|
import SidEditor from "$components/editor/SidEditor.svelte";
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
|
||||||
type Props = { data: PageData; form: ActionData };
|
type Props = { data: PageData; form: ActionData };
|
||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
@ -128,7 +129,7 @@
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<h4>{$t("edit-profile.profile-options-header")}</h4>
|
<h4>{$t("edit-profile.profile-options-header")}</h4>
|
||||||
<form method="POST" action="?/options">
|
<form method="POST" action="?/options" use:enhance>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="member-title">{$t("edit-profile.member-header-label")}</label>
|
<label class="form-label" for="member-title">{$t("edit-profile.member-header-label")}</label>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import type { ActionData, PageData } from "./$types";
|
import type { ActionData, PageData } from "./$types";
|
||||||
import BioEditor from "$components/editor/BioEditor.svelte";
|
import BioEditor from "$components/editor/BioEditor.svelte";
|
||||||
import { t } from "$lib/i18n";
|
import { t } from "$lib/i18n";
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
|
||||||
type Props = { data: PageData; form: ActionData };
|
type Props = { data: PageData; form: ActionData };
|
||||||
let { data, form }: Props = $props();
|
let { data, form }: Props = $props();
|
||||||
|
@ -12,6 +13,6 @@
|
||||||
|
|
||||||
<h4>{$t("edit-profile.bio-tab")}</h4>
|
<h4>{$t("edit-profile.bio-tab")}</h4>
|
||||||
<FormStatusMarker {form} />
|
<FormStatusMarker {form} />
|
||||||
<form method="POST">
|
<form method="POST" use:enhance>
|
||||||
<BioEditor bind:value={bio} maxLength={data.meta.limits.bio_length} />
|
<BioEditor bind:value={bio} maxLength={data.meta.limits.bio_length} />
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in a new issue