mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 23:12:04 +00:00
124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { Copy, LifeBuoy } from "lucide-react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { SettingsCard } from "@/components/settings/settings-card";
|
|
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
|
import { SettingsCardActions } from "@/components/settings/_card-actions";
|
|
import type { IntegrationStatusEnvelope } from "@/components/settings/integrations/shared";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import { apiGet } from "@/lib/api/api-fetch";
|
|
import { recentErrorIds } from "@/lib/errors/error-id";
|
|
import { showErrorToast } from "@/lib/errors/error-toast";
|
|
import { useTranslations } from "@/lib/i18n/context";
|
|
import { queryKeys } from "@/lib/query-keys";
|
|
import { buildSupportInfo } from "@/lib/support/support-info";
|
|
|
|
interface VersionPayload {
|
|
version: string;
|
|
buildSha: string | null;
|
|
}
|
|
|
|
/**
|
|
* Settings, About: "Copy support info". Puts together what a bug report needs
|
|
* from reads the app already makes (build, language, time zone, modules, AI
|
|
* state, each connection's health, the last error ids of this session) and
|
|
* copies it as plain text. No new route, nothing leaves the browser until the
|
|
* person pastes it somewhere. See `src/lib/support/support-info.ts`.
|
|
*/
|
|
export function SupportInfoCard() {
|
|
const { t, locale } = useTranslations();
|
|
const { user } = useAuth();
|
|
const queryClient = useQueryClient();
|
|
const [busy, setBusy] = useState(false);
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
async function copy() {
|
|
setBusy(true);
|
|
try {
|
|
const [version, integrations] = await Promise.all([
|
|
queryClient
|
|
.fetchQuery({
|
|
queryKey: queryKeys.apiVersion(),
|
|
queryFn: async () => apiGet<VersionPayload>("/api/version"),
|
|
staleTime: Infinity,
|
|
})
|
|
.catch(() => null),
|
|
queryClient
|
|
.fetchQuery({
|
|
queryKey: queryKeys.integrationsStatus(),
|
|
queryFn: async () =>
|
|
apiGet<IntegrationStatusEnvelope>("/api/integrations/status"),
|
|
})
|
|
.catch(() => null),
|
|
]);
|
|
const text = buildSupportInfo({
|
|
version,
|
|
me: user
|
|
? {
|
|
id: user.id,
|
|
username: user.username,
|
|
email: user.email,
|
|
fullName: user.fullName,
|
|
timezone: user.timezone,
|
|
modules: user.modules,
|
|
ai: user.ai ?? null,
|
|
}
|
|
: null,
|
|
locale,
|
|
connections: integrations?.integrations ?? null,
|
|
recentErrorIds: recentErrorIds(),
|
|
client: {
|
|
userAgent:
|
|
typeof navigator !== "undefined" ? navigator.userAgent : null,
|
|
standalone:
|
|
typeof window !== "undefined" &&
|
|
typeof window.matchMedia === "function" &&
|
|
window.matchMedia("(display-mode: standalone)").matches,
|
|
},
|
|
generatedAt: new Date().toISOString(),
|
|
});
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch {
|
|
window.prompt(t("settings.about.support.copy"), text);
|
|
}
|
|
} catch (failure) {
|
|
showErrorToast(t("settings.about.support.copyFailed"), {
|
|
cause: failure,
|
|
});
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<SettingsCard>
|
|
<SettingsCardHeader
|
|
icon={LifeBuoy}
|
|
title={t("settings.about.support.title")}
|
|
description={t("settings.about.support.description")}
|
|
/>
|
|
<SettingsCardActions>
|
|
<Button
|
|
size="sm"
|
|
className="min-h-11 sm:min-h-9"
|
|
onClick={() => void copy()}
|
|
disabled={busy}
|
|
aria-live="polite"
|
|
>
|
|
<Copy className="size-4" aria-hidden="true" />
|
|
{copied
|
|
? t("settings.about.support.copied")
|
|
: t("settings.about.support.copy")}
|
|
</Button>
|
|
</SettingsCardActions>
|
|
</SettingsCard>
|
|
);
|
|
}
|