diff --git a/.github/agents/release-manager.agent.md b/.github/agents/release-manager.agent.md index fb5babd..fea8fde 100644 --- a/.github/agents/release-manager.agent.md +++ b/.github/agents/release-manager.agent.md @@ -139,12 +139,14 @@ This script handles: branch creation → version bump → PR → CI wait → mer ### Version Files (MANDATORY) -The version number is displayed in the **About modal** (Settings → About) showing both Frontend and Backend versions. These are read from: +The version number is displayed in the **About modal** (Settings → About) as a single unified app version. This version is a **clickable link** pointing to the corresponding GitHub release (`https://github.com/DanielVolz/medassist-ng/releases/tag/vX.Y.Z`). The version is read from: - **`backend/package.json`** → Backend version, returned by `/health` endpoint -- **`frontend/package.json`** → Frontend version, injected at build time via Vite's `__APP_VERSION__` define +- **`frontend/package.json`** → Frontend version, injected at build time via Vite's `__APP_VERSION__` define and used to construct the release link -**Both files MUST be updated to the new version before tagging a release.** If forgotten, the About modal will show the old version even after the update. +**Both files MUST be updated to the new version before tagging a release.** If forgotten: +- The About modal will show the old version +- The version link will point to a non-existent GitHub release page ### Manual Release (if script is not available) diff --git a/backend/package-lock.json b/backend/package-lock.json index 4f82067..61c6c3d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "medassist-ng-backend", - "version": "1.7.1", + "version": "1.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "medassist-ng-backend", - "version": "1.7.1", + "version": "1.8.3", "dependencies": { "@fastify/cookie": "^10.0.1", "@fastify/cors": "^10.0.1", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 08dc3af..a240f3b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "medassist-ng-frontend", - "version": "1.7.1", + "version": "1.8.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "medassist-ng-frontend", - "version": "1.7.1", + "version": "1.8.3", "dependencies": { "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", diff --git a/frontend/src/components/AboutModal.tsx b/frontend/src/components/AboutModal.tsx index 4da0fa9..dd222d2 100644 --- a/frontend/src/components/AboutModal.tsx +++ b/frontend/src/components/AboutModal.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { FRONTEND_VERSION, GITHUB_URL } from "../App"; interface UpdateCheckResult { - status: "checking" | "up-to-date" | "update-available" | "error"; + status: "up-to-date" | "update-available" | "error"; latestVersion?: string; lastChecked?: string; } @@ -15,19 +15,13 @@ interface AboutModalProps { export default function AboutModal({ isOpen, onClose }: AboutModalProps) { const { t } = useTranslation(); - const [backendVersion, setBackendVersion] = useState(null); + const [isChecking, setIsChecking] = useState(false); const [updateCheckResult, setUpdateCheckResult] = useState(null); - // Fetch backend version and cached update result on mount + // Load cached update check result on mount useEffect(() => { if (!isOpen) return; - // Fetch backend version - fetch("/api/health") - .then((res) => res.json()) - .then((data) => setBackendVersion(data.version || "unknown")) - .catch(() => setBackendVersion("unknown")); - // Load cached update check result const cached = sessionStorage.getItem("updateCheckResult"); if (cached) { @@ -43,9 +37,13 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) { }, [isOpen]); async function checkForUpdates() { - setUpdateCheckResult({ status: "checking" }); + setIsChecking(true); + const minDelay = new Promise((resolve) => setTimeout(resolve, 1000)); try { - const res = await fetch(`https://api.github.com/repos/DanielVolz/medassist-ng/releases/latest`); + const [res] = await Promise.all([ + fetch(`https://api.github.com/repos/DanielVolz/medassist-ng/releases/latest`), + minDelay, + ]); if (!res.ok) throw new Error("Failed to fetch"); const data = await res.json(); const latestVersion = (data.tag_name || "").replace(/^v/, ""); @@ -61,6 +59,8 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) { sessionStorage.setItem("updateCheckResult", JSON.stringify(result)); } catch { setUpdateCheckResult({ status: "error" }); + } finally { + setIsChecking(false); } } @@ -74,32 +74,27 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
- - - - - + MedAssist-ng

{t("about.appName", "MedAssist-ng")}

{t("about.description", "Personal medication tracking and reminder app")}

- {t("about.frontendVersion", "Frontend")} - {FRONTEND_VERSION} -
-
- {t("about.backendVersion", "Backend")} - {backendVersion || "..."} + {t("about.version", "Version")} + + {FRONTEND_VERSION} +
- - {updateCheckResult && updateCheckResult.status !== "checking" && ( + {updateCheckResult && (
{updateCheckResult.status === "up-to-date" && ( ✓ {t("about.upToDate", "You are up to date!")} diff --git a/frontend/src/i18n/de.json b/frontend/src/i18n/de.json index 5ea3eb1..cad2e07 100644 --- a/frontend/src/i18n/de.json +++ b/frontend/src/i18n/de.json @@ -474,8 +474,6 @@ "appName": "MedAssist-ng", "description": "Open-Source Medikamentenverwaltung und Planungsanwendung für selbst gehostete Umgebungen.", "version": "Version", - "frontend": "Frontend", - "backend": "Backend", "checkForUpdates": "Nach Updates suchen", "checking": "Prüfe...", "upToDate": "Du bist auf dem neuesten Stand!", diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index d699ded..b672a10 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -474,8 +474,6 @@ "appName": "MedAssist-ng", "description": "Open-source medication tracking and planning application for self-hosted environments.", "version": "Version", - "frontend": "Frontend", - "backend": "Backend", "checkForUpdates": "Check for Updates", "checking": "Checking...", "upToDate": "You're up to date!", diff --git a/frontend/src/styles.css b/frontend/src/styles.css index d117896..e57391b 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -4551,18 +4551,12 @@ h3 .reminder-icon.info-tooltip { width: 64px; height: 64px; margin: 0 auto 1rem; - background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); - border-radius: 16px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 4px 12px rgba(var(--accent-rgb, 59, 130, 246), 0.25); } -.about-logo svg { - width: 36px; - height: 36px; - stroke: white; +.about-logo img { + width: 64px; + height: 64px; + border-radius: 16px; } .about-header h2 { @@ -4586,15 +4580,12 @@ h3 .reminder-icon.info-tooltip { .about-version-row { display: flex; - justify-content: space-between; + justify-content: center; align-items: center; + gap: 0.75rem; padding: 0.5rem 0; } -.about-version-row:not(:last-child) { - border-bottom: 1px dashed var(--border-secondary); -} - .about-version-label { font-size: 0.875rem; color: var(--text-secondary); @@ -4610,9 +4601,20 @@ h3 .reminder-icon.info-tooltip { border-radius: 4px; } +a.about-version-link { + color: var(--accent-primary); + text-decoration: none; + transition: text-decoration 0.15s ease; +} + +a.about-version-link:hover { + text-decoration: underline; +} + .about-update-section { padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border-primary); + min-height: 148px; } .about-update-btn { diff --git a/frontend/src/test/components/AboutModal.test.tsx b/frontend/src/test/components/AboutModal.test.tsx index 5390c5a..e61e706 100644 --- a/frontend/src/test/components/AboutModal.test.tsx +++ b/frontend/src/test/components/AboutModal.test.tsx @@ -16,10 +16,6 @@ describe("AboutModal", () => { beforeEach(() => { vi.clearAllMocks(); - (global.fetch as ReturnType).mockResolvedValue({ - ok: true, - json: () => Promise.resolve({ version: "1.0.0" }), - }); }); it("returns null when not open", () => { @@ -65,8 +61,10 @@ describe("AboutModal", () => { expect(links.length).toBeGreaterThan(0); }); - it("fetches backend version on open", async () => { + it("renders version as link to GitHub release", () => { render(); - expect(fetch).toHaveBeenCalledWith("/api/health"); + const versionLink = screen.getByText("1.0.0").closest("a"); + expect(versionLink).toHaveAttribute("href", "https://github.com/test/repo/releases/tag/v1.0.0"); + expect(versionLink).toHaveAttribute("target", "_blank"); }); });