mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
Drop the in-app bug-report surface end to end: the user submission page, the feedback intake and admin inbox, the optional GitHub-issue escalation, the operator config, and the dedicated achievement badges. - delete the /bugreport page and the bugreport / feedback / admin-feedback API routes - drop the Feedback model, its enums, and the AppSettings GitHub-issue and enable columns (migration 0205) - remove the admin Feedback section, the Bug Report integration card, and the system-status row - drop the bug-report nav entry and the app-settings provider that only carried its flag - remove the bug-hunter achievement badges and their metric - prune the related i18n keys across every locale and the encrypted-column and key-rotation registries
103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Copy, CheckCircle2, RotateCcw } from "lucide-react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
|
|
export interface ErrorDetailsProps {
|
|
/** The error object, whether it's a runtime Error or a typed API error. */
|
|
error: Error & { digest?: string };
|
|
/** Pass Next.js' `reset()` callback if available. */
|
|
reset?: () => void;
|
|
/** Request ID propagated from the server (x-request-id), if known. */
|
|
requestId?: string | null;
|
|
/** Extra fields to include in the diagnostic payload. */
|
|
context?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Structured error panel with a copy-to-clipboard payload that includes
|
|
* everything we'd need to reproduce or file an issue: message, stack digest,
|
|
* URL, user agent, locale, timestamp.
|
|
*/
|
|
export function ErrorDetails({
|
|
error,
|
|
reset,
|
|
requestId,
|
|
context,
|
|
}: ErrorDetailsProps) {
|
|
const { t, locale } = useTranslations();
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
async function handleCopy() {
|
|
// Only capture the pathname — some flows carry OAuth codes / state tokens
|
|
// in the query string (Withings connect, password reset) and we don't
|
|
// want users pasting those into public GitHub issues. Preserve the count
|
|
// for context without exposing values.
|
|
const loc = typeof window !== "undefined" ? window.location : null;
|
|
const urlPath = loc?.pathname ?? null;
|
|
const searchParamCount = loc?.search
|
|
? new URLSearchParams(loc.search).toString().split("&").filter(Boolean)
|
|
.length
|
|
: 0;
|
|
|
|
const payload = {
|
|
message: error.message,
|
|
digest: error.digest,
|
|
name: error.name,
|
|
stack: error.stack?.split("\n").slice(0, 10).join("\n"),
|
|
requestId: requestId ?? null,
|
|
urlPath,
|
|
searchParamCount,
|
|
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : null,
|
|
locale,
|
|
timestamp: new Date().toISOString(),
|
|
context,
|
|
};
|
|
|
|
const text = JSON.stringify(payload, null, 2);
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch {
|
|
// Fallback: open a prompt the user can copy from manually.
|
|
window.prompt(t("common.copyDetails"), text);
|
|
}
|
|
}
|
|
|
|
return (
|
|
// v1.4.27 MB6 — `min-h-dvh flex flex-col justify-center` makes
|
|
// the error panel anchor in the middle of the available viewport
|
|
// even when this component is mounted at the root layout. Without
|
|
// it the panel hugged the top edge under the notch on
|
|
// small-screen devices.
|
|
<div className="mx-auto flex min-h-dvh w-full max-w-xl flex-col justify-center space-y-4 p-6">
|
|
<h1 className="text-xl font-semibold">{t("errorBoundary.title")}</h1>
|
|
<p className="text-muted-foreground text-sm">
|
|
{t("errorBoundary.description")}
|
|
</p>
|
|
<div className="bg-muted/50 text-muted-foreground rounded-md border p-3 font-mono text-xs break-all">
|
|
{error.message || t("common.unknownError")}
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{reset && (
|
|
<Button onClick={reset} variant="outline">
|
|
<RotateCcw className="h-4 w-4" />
|
|
{t("common.retry")}
|
|
</Button>
|
|
)}
|
|
<Button onClick={handleCopy} variant="outline">
|
|
{copied ? (
|
|
<CheckCircle2 className="text-success h-4 w-4" />
|
|
) : (
|
|
<Copy className="h-4 w-4" />
|
|
)}
|
|
{copied ? t("common.copied") : t("common.copyDetails")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|