mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-01 01:16:17 +02:00
* feat(notifications): add system notifications toggle in settings Add a per-user, per-workspace toggle to enable/disable native OS notification banners. Reuses the existing notification-preferences endpoint by introducing a `system_notifications` key alongside the inbox event groups; the realtime handler reads the cached preference and skips desktopAPI.showNotification when muted. Co-authored-by: multica-agent <github@multica.ai> * fix(notifications): fetch system_notifications pref lazily Settings is the only mounted reader of notificationPreferenceOptions, so a fresh app start (or any session that never visits Settings) left the cache empty and the muted preference silently fell back to default "all". Switch the inbox:new handler to ensureQueryData so the value is fetched on first use and cached for subsequent events. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { useWorkspaceId } from "@multica/core/hooks";
|
|
import { notificationPreferenceOptions } from "@multica/core/notification-preferences/queries";
|
|
import { useUpdateNotificationPreferences } from "@multica/core/notification-preferences/mutations";
|
|
import type { NotificationGroupKey, NotificationPreferences } from "@multica/core/types";
|
|
import { Card, CardContent } from "@multica/ui/components/ui/card";
|
|
import { Switch } from "@multica/ui/components/ui/switch";
|
|
import { toast } from "sonner";
|
|
import { useT } from "../../i18n";
|
|
|
|
// Inbox event groups rendered in the per-event toggle list. `system_notifications`
|
|
// is a sibling preference key but lives in its own section below.
|
|
const INBOX_GROUP_KEYS = [
|
|
"assignments",
|
|
"status_changes",
|
|
"comments",
|
|
"updates",
|
|
"agent_activity",
|
|
] as const;
|
|
type InboxGroupKey = (typeof INBOX_GROUP_KEYS)[number];
|
|
|
|
export function NotificationsTab() {
|
|
const { t } = useT("settings");
|
|
const wsId = useWorkspaceId();
|
|
const { data } = useQuery(notificationPreferenceOptions(wsId));
|
|
const mutation = useUpdateNotificationPreferences();
|
|
|
|
const preferences = data?.preferences ?? {};
|
|
|
|
const handleToggle = (key: NotificationGroupKey, enabled: boolean) => {
|
|
const updated: NotificationPreferences = {
|
|
...preferences,
|
|
[key]: enabled ? "all" : "muted",
|
|
};
|
|
// Remove keys set to "all" (default) to keep the object clean
|
|
if (enabled) {
|
|
delete updated[key];
|
|
}
|
|
mutation.mutate(updated, {
|
|
onError: () => toast.error(t(($) => $.notifications.toast_failed)),
|
|
});
|
|
};
|
|
|
|
const systemEnabled = preferences.system_notifications !== "muted";
|
|
|
|
return (
|
|
<div className="space-y-8">
|
|
<section className="space-y-4">
|
|
<div>
|
|
<h2 className="text-sm font-semibold">{t(($) => $.notifications.title)}</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{t(($) => $.notifications.description)}
|
|
</p>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardContent className="divide-y">
|
|
{INBOX_GROUP_KEYS.map((key: InboxGroupKey) => {
|
|
const enabled = preferences[key] !== "muted";
|
|
return (
|
|
<div
|
|
key={key}
|
|
className="flex items-center justify-between py-3 first:pt-0 last:pb-0"
|
|
>
|
|
<div className="space-y-0.5 pr-4">
|
|
<p className="text-sm font-medium">{t(($) => $.notifications.groups[key].label)}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t(($) => $.notifications.groups[key].description)}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={enabled}
|
|
onCheckedChange={(checked) => handleToggle(key, checked)}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
</CardContent>
|
|
</Card>
|
|
</section>
|
|
|
|
<section className="space-y-4">
|
|
<div>
|
|
<h2 className="text-sm font-semibold">{t(($) => $.notifications.system.title)}</h2>
|
|
<p className="text-sm text-muted-foreground mt-1">
|
|
{t(($) => $.notifications.system.description)}
|
|
</p>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardContent>
|
|
<div className="flex items-center justify-between">
|
|
<div className="space-y-0.5 pr-4">
|
|
<p className="text-sm font-medium">{t(($) => $.notifications.system.label)}</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{t(($) => $.notifications.system.hint)}
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
checked={systemEnabled}
|
|
onCheckedChange={(checked) => handleToggle("system_notifications", checked)}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|