feat(telemetry): opt-in admin alert pushes

- telemetryAlerts metadata flag, admin-gated server fn
- alerts sent only to admins with the flag enabled
- settings toggle visible to admins only, under device notifications
This commit is contained in:
2026-08-25 23:33:01 -07:00
parent 1ddcffc511
commit 3d8ee52a15
8 changed files with 200 additions and 85 deletions
@@ -1,11 +1,46 @@
import { useState } from "react";
import { Box, Title, Stack, Switch, Button, Text, Group } from "@mantine/core";
import { Trans, useLingui } from "@lingui/react/macro";
import { useWebPush } from "@/hooks/use-web-push";
import { useAuth } from "@/contexts/auth-context";
import { useIsAdmin } from "@/features/core/hooks/use-is-admin";
import { updateUserTelemetryAlerts } from "@/features/settings/server";
import toast from "@/lib/sonner";
const TelemetryAlertsSwitch = () => {
const { t } = useLingui();
const { metadata, set } = useAuth();
const [saving, setSaving] = useState(false);
const handleToggle = async (checked: boolean) => {
setSaving(true);
try {
const result = await updateUserTelemetryAlerts({ data: checked });
if (result?.metadata) {
set({ metadata: result.metadata });
}
} catch {
toast.error(t`Couldn't update telemetry alerts`);
} finally {
setSaving(false);
}
};
return (
<Switch
checked={metadata?.telemetryAlerts === true}
onChange={(e) => handleToggle(e.currentTarget.checked)}
disabled={saving}
label={t`Telemetry alerts`}
description={t`Error spikes and failing functions, pushed to this account`}
/>
);
};
export function NotificationsSection() {
const { supported, configured, permission, subscribed, busy, enable, disable, sendTest } =
useWebPush();
const isAdmin = useIsAdmin();
const { t } = useLingui();
if (!supported || !configured) {
@@ -64,6 +99,7 @@ export function NotificationsSection() {
disabled={busy}
label={t`Enable notifications on this device`}
/>
{subscribed && isAdmin && <TelemetryAlertsSwitch />}
{subscribed && (
<Group>
<Button
+27 -1
View File
@@ -1,5 +1,8 @@
import { createServerFn } from "@tanstack/react-start";
import { superTokensFunctionMiddleware } from "@/utils/supertokens";
import {
superTokensAdminFunctionMiddleware,
superTokensFunctionMiddleware,
} from "@/utils/supertokens";
// Whitelists: these values flow into Mantine's primaryColor / the
// data-mantine-color-scheme attribute — an unknown value crash-loops or
@@ -139,3 +142,26 @@ export const updateUserAccentColor = createServerFn({ method: "POST" })
},
};
});
export const updateUserTelemetryAlerts = createServerFn({ method: "POST" })
.validator((data: boolean) => data === true)
.middleware([superTokensAdminFunctionMiddleware])
.handler(async ({ context, data }) => {
const { userAuthId, metadata } = context;
if (!userAuthId) return;
const { updateUserMetadataFields } = await import(
"@/utils/supertokens-core.server"
);
await updateUserMetadataFields(userAuthId, {
telemetryAlerts: data,
});
return {
metadata: {
...metadata,
telemetryAlerts: data,
},
};
});