mirror of
https://github.com/temetro/temetro.git
synced 2026-08-06 17:07:40 +00:00
403e0e38e9
Resolve the backend URL from the current host at runtime (lib/backend-url.ts) so one build works on localhost and any clinic LAN IP without a rebuild; used by the API client, auth client, and socket. Wire the AI-chat mic to the Web Speech API with graceful fallback. Add a Settings "About & updates" panel (current/latest version, update command, shareable network address) and an optional dismissible update banner. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { X } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { getVersionInfo } from "@/lib/version";
|
|
|
|
const DISMISS_KEY = "temetro:update-dismissed";
|
|
|
|
// A small, dismissible notice shown when a newer temetro release exists. Updating
|
|
// is optional — dismissing remembers the version so we don't nag again until the
|
|
// next release. Errors are swallowed (offline / private deployments).
|
|
export function UpdateBanner() {
|
|
const { t } = useTranslation();
|
|
const [latest, setLatest] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
getVersionInfo()
|
|
.then((info) => {
|
|
if (!active || !info.updateAvailable || !info.latest) return;
|
|
if (localStorage.getItem(DISMISS_KEY) === info.latest) return;
|
|
setLatest(info.latest);
|
|
})
|
|
.catch(() => {
|
|
/* offline or no update server — stay silent */
|
|
});
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, []);
|
|
|
|
if (!latest) return null;
|
|
|
|
const dismiss = () => {
|
|
localStorage.setItem(DISMISS_KEY, latest);
|
|
setLatest(null);
|
|
};
|
|
|
|
return (
|
|
<div className="fixed right-4 bottom-4 z-50 flex max-w-sm items-start gap-3 rounded-2xl border border-border bg-card px-4 py-3 shadow-lg">
|
|
<div className="space-y-1.5">
|
|
<p className="text-sm text-foreground">
|
|
{t("settings.version.banner", { version: latest })}
|
|
</p>
|
|
<Link
|
|
className="text-sm font-medium text-primary underline-offset-4 hover:underline"
|
|
href="/settings?tab=version"
|
|
onClick={dismiss}
|
|
>
|
|
{t("settings.version.bannerUpdate")}
|
|
</Link>
|
|
</div>
|
|
<button
|
|
aria-label={t("settings.version.bannerDismiss")}
|
|
className="-mr-1 shrink-0 rounded-lg p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
|
onClick={dismiss}
|
|
type="button"
|
|
>
|
|
<X className="size-4" />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|