feat(frontend): global notices
This commit is contained in:
parent
b07f4b75c0
commit
b0431ff962
7 changed files with 81 additions and 5 deletions
|
@ -68,6 +68,7 @@ builder
|
|||
{
|
||||
NamingStrategy = new SnakeCaseNamingStrategy(),
|
||||
};
|
||||
options.SerializerSettings.DateParseHandling = DateParseHandling.None;
|
||||
})
|
||||
.ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
|
|
49
Foxnouns.Frontend/src/lib/components/GlobalNotice.svelte
Normal file
49
Foxnouns.Frontend/src/lib/components/GlobalNotice.svelte
Normal file
|
@ -0,0 +1,49 @@
|
|||
<script lang="ts">
|
||||
import { fastRequest } from "$api";
|
||||
import type { UserSettings } from "$api/models";
|
||||
import { idTimestamp } from "$lib";
|
||||
import { t } from "$lib/i18n";
|
||||
import log from "$lib/log";
|
||||
import { renderUnsafeMarkdown } from "$lib/markdown";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
type Props = { id: string; message: string; settings?: UserSettings; token: string | null };
|
||||
let { id, message, settings, token }: Props = $props();
|
||||
|
||||
let lastReadNotice = $state(settings?.last_read_notice || null);
|
||||
|
||||
// Render the notice if:
|
||||
// - user is not logged in (no settings object)
|
||||
// - last read notice is null (never marked any notice as read)
|
||||
// - last read notice ID is smaller than the current one (has not marked the current notice as read)
|
||||
let renderNotice = $derived(!lastReadNotice || lastReadNotice < id);
|
||||
let canDismiss = $derived(!!token);
|
||||
let renderedMessage = $derived(renderUnsafeMarkdown(message));
|
||||
|
||||
let dismiss = async () => {
|
||||
if (!token) return;
|
||||
try {
|
||||
await fastRequest("PATCH", "/users/@me/settings", { token, body: { last_read_notice: id } });
|
||||
lastReadNotice = id;
|
||||
} catch (e) {
|
||||
log.error("error updating last read notice ID:", e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if renderNotice}
|
||||
<div class="alert alert-light" role="alert">
|
||||
<div>
|
||||
{@html renderedMessage}
|
||||
</div>
|
||||
{#if canDismiss}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y_invalid_attribute -->
|
||||
<a href="#" tabindex="0" role="button" onclick={() => dismiss()} onkeyup={() => dismiss()}>
|
||||
{$t("notification.mark-as-read")}
|
||||
</a>
|
||||
• {idTimestamp(id).toLocaleString(DateTime.DATETIME_MED)}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
|
@ -38,6 +38,6 @@
|
|||
</div>
|
||||
<div class="card-footer text-body-secondary">
|
||||
{idTimestamp(notification.id).toLocaleString(DateTime.DATETIME_MED)}
|
||||
• <a href="/settings/notifications/ack/{notification.id}">Mark as read</a>
|
||||
• <a href="/settings/notifications/ack/{notification.id}">{$t("notification.mark-as-read")}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -350,6 +350,8 @@
|
|||
"notification": {
|
||||
"suspension": "Your account has been suspended for the following reason: {{reason}}",
|
||||
"warning": "You have been warned for the following reason: {{reason}}",
|
||||
"warning-cleared-fields": "You have been warned for the following reason: {{reason}}\n\nAdditionally, the following fields have been cleared from your profile:\n{{clearedFields}}"
|
||||
"warning-cleared-fields": "You have been warned for the following reason: {{reason}}\n\nAdditionally, the following fields have been cleared from your profile:\n{{clearedFields}}",
|
||||
"mark-as-read": "Mark as read",
|
||||
"no-notifications": "You have no notifications."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import GlobalNotice from "$components/GlobalNotice.svelte";
|
||||
import type { PageData } from "./$types";
|
||||
|
||||
type Props = { data: PageData };
|
||||
|
@ -10,6 +11,15 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="container">
|
||||
{#if data.meta.notice}
|
||||
<GlobalNotice
|
||||
id={data.meta.notice.id}
|
||||
message={data.meta.notice.message}
|
||||
settings={data.meUser?.settings}
|
||||
token={data.token}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<h1>pronouns.cc</h1>
|
||||
|
||||
<p>
|
||||
|
|
|
@ -3,15 +3,28 @@
|
|||
import { t } from "$lib/i18n";
|
||||
import { Nav, NavLink } from "@sveltestrap/sveltestrap";
|
||||
import { isActive } from "$lib/pageUtils.svelte";
|
||||
import type { LayoutData } from "./$types";
|
||||
import GlobalNotice from "$components/GlobalNotice.svelte";
|
||||
|
||||
type Props = { children: Snippet };
|
||||
let { children }: Props = $props();
|
||||
type Props = { data: LayoutData; children: Snippet };
|
||||
let { data, children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$t("title.settings")} • pronouns.cc</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if data.meta.notice}
|
||||
<div class="container">
|
||||
<GlobalNotice
|
||||
id={data.meta.notice.id}
|
||||
message={data.meta.notice.message}
|
||||
settings={data.meUser?.settings}
|
||||
token={data.token}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="container">
|
||||
<Nav pills justified fill class="flex-column flex-md-row mb-2">
|
||||
<NavLink active={isActive(["/settings", "/settings/force-log-out"])} href="/settings">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import type { PageData } from "./$types";
|
||||
import Notification from "$components/settings/Notification.svelte";
|
||||
import UrlAlert from "$components/URLAlert.svelte";
|
||||
import { t } from "$lib/i18n";
|
||||
|
||||
type Props = { data: PageData };
|
||||
let { data }: Props = $props();
|
||||
|
@ -12,5 +13,5 @@
|
|||
{#each data.notifications as notification (notification.id)}
|
||||
<Notification {notification} />
|
||||
{:else}
|
||||
You have no notifications.
|
||||
{$t("notification.no-notifications")}
|
||||
{/each}
|
||||
|
|
Loading…
Add table
Reference in a new issue