mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
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:
1 parent
dc01ab7934
commit
d438a8520d
11 files changed
+76
-2
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in new issue
Block a user