feat(admin): bug-report toggle, unify "Trennen" button styling

Bucket E+F of the v1.4.3 polish pass.

**Disconnect-button styling unified.** Three integrations had three
different "Trennen" buttons: Withings used `outline` + `text-
destructive`; moodLog used solid `destructive` (red bg, white text);
the AI/Codex card used `ghost` + `text-destructive`. Same action,
three readings — confusing. All three now use the Withings pattern
(`outline` + `text-destructive`). The actual confirmation button
inside the AlertDialog keeps its solid-red treatment so the final
"yes, do it" still reads as a strong commit.

**Bug-report explicit on/off toggle.** Until v1.4.3 the bug-report
feature was implicitly gated on "is the GitHub token + repo
configured". An admin who wanted to *temporarily* hide the report
button (e.g. during a freeze window) had to clear the encrypted
token, forcing re-entry on resume — destructive for a reversible
intent. Migration `0027_bug_report_enabled_toggle` adds
`app_settings.bug_report_enabled BOOLEAN NOT NULL DEFAULT TRUE`,
the admin settings schema accepts `bugReportEnabled`, and a Switch
in the admin → Bug Report card lets the operator flip it.
`POST /api/bugreport` now returns 503 ("Bug reports are disabled by
the administrator") when the flag is off — the client already
renders a "not configured" screen on that status, so the report
button hides gracefully without further client-side wiring.

i18n keys added: `admin.bugReportEnabledLabel` + `…Description` in
both DE and EN.

Tests + typecheck green (671/671).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-08 21:45:18 +02:00
1 parent dc01ab7934
commit d438a8520d
11 files changed
+76 -2

No files matched your search

+2
View File
@@ -1273,6 +1273,8 @@
"monitoringSave": "Monitoring speichern",
"bugReportGithub": "Bugreport (GitHub Issues)",
"bugReportGithubDescription": "Hinterlege GitHub-Issue-Token und Repository für eingehende Bugreports.",
"bugReportEnabledLabel": "Bug-Reports aktiviert",
"bugReportEnabledDescription": "Wenn deaktiviert, ist der Report-Button im Frontend nicht mehr sichtbar — Token und Repository bleiben gespeichert.",
"bugReportRepo": "Repository",
"bugReportRepoPlaceholder": "owner/repo",
"bugReportToken": "Issue-Token",
+2
View File
@@ -1273,6 +1273,8 @@
"monitoringSave": "Save monitoring",
"bugReportGithub": "Bug report (GitHub Issues)",
"bugReportGithubDescription": "Configure GitHub issue token and repository for incoming bug reports.",
"bugReportEnabledLabel": "Bug reports enabled",
"bugReportEnabledDescription": "When off, the in-app report button hides — token and repository stay configured.",
"bugReportRepo": "Repository",
"bugReportRepoPlaceholder": "owner/repo",
"bugReportToken": "Issue token",
@@ -0,0 +1,16 @@
-- v1.4.3: explicit on/off switch for the bug-report surface.
--
-- Until this release the bug-report feature was implicitly gated on
-- "is the GitHub token + repo configured?". That coupling meant an
-- admin who wanted to *temporarily* hide the bug-report button (e.g.
-- during a deploy freeze) had to clear the encrypted token, which
-- forced a re-entry on resume — destructive for a reversible
-- intent.
--
-- Default `true` so existing instances with credentials configured
-- keep the feature visible after the upgrade. Admins who want it off
-- flip the switch in `/admin/integrations` (or whichever section
-- mounts the bug-report card).
ALTER TABLE "app_settings"
ADD COLUMN "bug_report_enabled" BOOLEAN NOT NULL DEFAULT TRUE;
+5
View File
@@ -466,6 +466,11 @@ model AppSettings {
glitchtipEnvironment String? @map("glitchtip_environment")
githubIssueTokenEncrypted String? @map("github_issue_token_encrypted")
githubIssueRepo String? @map("github_issue_repo")
// v1.4.3: explicit toggle so an admin can hide the bug-report
// surface without nuking the token+repo config. Default true so
// existing instances with credentials don't accidentally lose
// the feature on upgrade.
bugReportEnabled Boolean @default(true) @map("bug_report_enabled")
// Medication reminder thresholds (minutes after windowEnd)
reminderLateMinutes Int @default(120) @map("reminder_late_minutes")
+3
View File
@@ -41,6 +41,7 @@ export const GET = apiHandler(async () => {
bugReportConfigured: Boolean(
settings?.githubIssueRepo && settings?.githubIssueTokenEncrypted,
),
bugReportEnabled: settings?.bugReportEnabled ?? true,
reminderLateMinutes: settings?.reminderLateMinutes ?? 120,
reminderMissedMinutes: settings?.reminderMissedMinutes ?? 240,
moodLogGlobal: settings?.moodLogGlobal ?? true,
@@ -73,6 +74,7 @@ export const PUT = apiHandler(async (request: NextRequest) => {
"umamiEnabled",
"glitchtipEnabled",
"moodLogGlobal",
"bugReportEnabled",
] as const;
for (const field of booleanFields) {
if (data[field] !== undefined) {
@@ -220,6 +222,7 @@ export const PUT = apiHandler(async (request: NextRequest) => {
bugReportConfigured: Boolean(
settings.githubIssueRepo && settings.githubIssueTokenEncrypted,
),
bugReportEnabled: settings.bugReportEnabled,
reminderLateMinutes: settings.reminderLateMinutes,
reminderMissedMinutes: settings.reminderMissedMinutes,
moodLogGlobal: settings.moodLogGlobal,
+9
View File
@@ -36,9 +36,18 @@ export const POST = apiHandler(async (request: NextRequest) => {
select: {
githubIssueTokenEncrypted: true,
githubIssueRepo: true,
bugReportEnabled: true,
},
});
// v1.4.3: explicit toggle. When the admin disables bug reports we
// return 503 immediately — the client already renders a "not
// configured" UI for that status, so the report button gracefully
// disappears without needing additional client-side wiring.
if (appSettings && appSettings.bugReportEnabled === false) {
return apiError("Bug reports are disabled by the administrator", 503);
}
let configuredToken: string | null = null;
if (appSettings?.githubIssueTokenEncrypted) {
try {
+1
View File
@@ -70,6 +70,7 @@ export interface AdminSettings {
glitchtipEnvironment: string | null;
bugReportRepo: string | null;
bugReportConfigured: boolean;
bugReportEnabled: boolean;
reminderLateMinutes: number;
reminderMissedMinutes: number;
}
@@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useTranslations } from "@/lib/i18n/context";
import { PasswordInput, useAdminSettings, useUpdateSettings } from "./_shared";
@@ -21,6 +22,7 @@ export function BugReportSection({ id }: { id: string }) {
const bugReportRepoValue =
bugReportRepoDraft ?? settings?.bugReportRepo ?? "";
const configured = settings?.bugReportConfigured ?? false;
const enabled = settings?.bugReportEnabled ?? true;
function saveBugReportSettings() {
const payload: Record<string, unknown> = {
@@ -38,6 +40,10 @@ export function BugReportSection({ id }: { id: string }) {
});
}
function toggleEnabled(next: boolean) {
updateSettings.mutate({ bugReportEnabled: next });
}
return (
<div
id={id}
@@ -62,6 +68,23 @@ export function BugReportSection({ id }: { id: string }) {
{t("admin.bugReportGithubDescription")}
</p>
<div className="bg-muted/40 mt-4 flex items-center justify-between gap-3 rounded-lg p-3">
<div>
<p className="text-sm font-medium">
{t("admin.bugReportEnabledLabel")}
</p>
<p className="text-muted-foreground text-xs">
{t("admin.bugReportEnabledDescription")}
</p>
</div>
<Switch
checked={enabled}
onCheckedChange={toggleEnabled}
disabled={updateSettings.isPending}
aria-label={t("admin.bugReportEnabledLabel")}
/>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
<div className="space-y-1.5">
<Label htmlFor="admin-bugreport-repo" className="text-xs">
+4 -1
View File
@@ -277,8 +277,11 @@ function InsightsSettingsCard({
)}
</p>
</div>
{/* v1.4.3: aligned with the Withings/moodLog disconnect
buttons — outline + text-destructive so the same action
reads the same everywhere in Settings. */}
<Button
variant="ghost"
variant="outline"
size="sm"
className="text-destructive shrink-0"
onClick={handleDisconnect}
@@ -644,7 +644,16 @@ function MoodLogCard() {
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive" size="sm">
{/* v1.4.3: aligned with the Withings disconnect button —
outline + text-destructive instead of solid destructive
so the trigger reads as a reversible action; the actual
"yes, disconnect" confirmation inside the dialog keeps
its solid-red treatment. */}
<Button
variant="outline"
size="sm"
className="text-destructive"
>
<Unlink className="mr-2 h-4 w-4" />
{t("settings.moodLogDisconnect")}
</Button>
+1
View File
@@ -71,6 +71,7 @@ export const adminSettingsSchema = z
.optional(),
bugReportToken: z.string().optional(),
clearBugReportToken: z.boolean().optional(),
bugReportEnabled: z.boolean().optional(),
reminderLateMinutes: z.number().int().min(15).max(480).optional(),
reminderMissedMinutes: z.number().int().min(30).max(720).optional(),
moodLogGlobal: z.boolean().optional(),