mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
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>
133 lines
4.3 KiB
TypeScript
133 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Bug, Loader2 } from "lucide-react";
|
|
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";
|
|
|
|
export function BugReportSection({ id }: { id: string }) {
|
|
const { t } = useTranslations();
|
|
const { data: settings } = useAdminSettings();
|
|
const updateSettings = useUpdateSettings();
|
|
const [bugReportRepoDraft, setBugReportRepoDraft] = useState<string | null>(
|
|
null,
|
|
);
|
|
const [bugReportTokenDraft, setBugReportTokenDraft] = useState("");
|
|
|
|
const bugReportRepoValue =
|
|
bugReportRepoDraft ?? settings?.bugReportRepo ?? "";
|
|
const configured = settings?.bugReportConfigured ?? false;
|
|
const enabled = settings?.bugReportEnabled ?? true;
|
|
|
|
function saveBugReportSettings() {
|
|
const payload: Record<string, unknown> = {
|
|
bugReportRepo: bugReportRepoValue,
|
|
};
|
|
if (bugReportTokenDraft.trim().length > 0) {
|
|
payload.bugReportToken = bugReportTokenDraft.trim();
|
|
}
|
|
|
|
updateSettings.mutate(payload, {
|
|
onSuccess: () => {
|
|
setBugReportRepoDraft(null);
|
|
setBugReportTokenDraft("");
|
|
},
|
|
});
|
|
}
|
|
|
|
function toggleEnabled(next: boolean) {
|
|
updateSettings.mutate({ bugReportEnabled: next });
|
|
}
|
|
|
|
return (
|
|
<div
|
|
id={id}
|
|
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
|
|
>
|
|
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
<div className="flex items-center gap-2">
|
|
<Bug className="text-primary h-5 w-5" />
|
|
<h2 className="text-lg font-semibold">
|
|
{t("admin.bugReportGithub")}
|
|
</h2>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{configured && (
|
|
<Badge className="border-dracula-green/30 bg-dracula-green/15 text-dracula-green">
|
|
{t("admin.configured")}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<p className="text-muted-foreground mt-1 text-xs">
|
|
{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">
|
|
{t("admin.bugReportRepo")}
|
|
</Label>
|
|
<Input
|
|
id="admin-bugreport-repo"
|
|
value={bugReportRepoValue}
|
|
onChange={(event) => setBugReportRepoDraft(event.target.value)}
|
|
placeholder={t("admin.bugReportRepoPlaceholder")}
|
|
autoComplete="off"
|
|
data-lpignore="true"
|
|
data-1p-ignore="true"
|
|
disabled={updateSettings.isPending}
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label htmlFor="admin-bugreport-token" className="text-xs">
|
|
{t("admin.bugReportToken")}
|
|
</Label>
|
|
<PasswordInput
|
|
id="admin-bugreport-token"
|
|
value={bugReportTokenDraft}
|
|
onChange={(event) => setBugReportTokenDraft(event.target.value)}
|
|
placeholder={t("admin.bugReportTokenPlaceholder")}
|
|
disabled={updateSettings.isPending}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 flex justify-end gap-2">
|
|
<Button
|
|
size="sm"
|
|
onClick={saveBugReportSettings}
|
|
disabled={updateSettings.isPending}
|
|
>
|
|
{updateSettings.isPending && (
|
|
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
|
)}
|
|
{t("common.save")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|