feat: simplify About modal with single version link to GitHub release (#123)
- Replace separate Frontend/Backend versions with single app version - Version is now a clickable link to the GitHub release page - Replace stopwatch SVG with actual app logo (favicon.svg) - Fix update check UX: previous result stays visible during re-check - Add 1s minimum delay for update check spinner visibility - Reserve space for update result to prevent modal jumping - Remove unused i18n keys (frontend/backend) - Update release-manager docs with version link info
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(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) {
|
||||
</button>
|
||||
<div className="about-header">
|
||||
<div className="about-logo">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<path d="M19.5 12c0 4.14-3.36 7.5-7.5 7.5S4.5 16.14 4.5 12 7.86 4.5 12 4.5s7.5 3.36 7.5 7.5z" />
|
||||
<path d="M12 8v4l2.5 2.5" />
|
||||
<path d="M9 2h6M12 2v2" />
|
||||
</svg>
|
||||
<img src="/favicon.svg" alt="MedAssist-ng" />
|
||||
</div>
|
||||
<h2>{t("about.appName", "MedAssist-ng")}</h2>
|
||||
<p className="about-tagline">{t("about.description", "Personal medication tracking and reminder app")}</p>
|
||||
</div>
|
||||
<div className="about-versions">
|
||||
<div className="about-version-row">
|
||||
<span className="about-version-label">{t("about.frontendVersion", "Frontend")}</span>
|
||||
<span className="about-version-value">{FRONTEND_VERSION}</span>
|
||||
</div>
|
||||
<div className="about-version-row">
|
||||
<span className="about-version-label">{t("about.backendVersion", "Backend")}</span>
|
||||
<span className="about-version-value">{backendVersion || "..."}</span>
|
||||
<span className="about-version-label">{t("about.version", "Version")}</span>
|
||||
<a
|
||||
href={`${GITHUB_URL}/releases/tag/v${FRONTEND_VERSION}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="about-version-value about-version-link"
|
||||
>
|
||||
{FRONTEND_VERSION}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="about-update-section">
|
||||
<button
|
||||
className="about-update-btn"
|
||||
onClick={checkForUpdates}
|
||||
disabled={updateCheckResult?.status === "checking"}
|
||||
>
|
||||
{updateCheckResult?.status === "checking" ? (
|
||||
<button className="about-update-btn" onClick={checkForUpdates} disabled={isChecking}>
|
||||
{isChecking ? (
|
||||
<>
|
||||
<span className="spinner-small"></span>
|
||||
{t("about.checking", "Checking...")}
|
||||
@@ -116,7 +111,7 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{updateCheckResult && updateCheckResult.status !== "checking" && (
|
||||
{updateCheckResult && (
|
||||
<div className={`about-update-result ${updateCheckResult.status}`}>
|
||||
{updateCheckResult.status === "up-to-date" && (
|
||||
<span className="update-status-text">✓ {t("about.upToDate", "You are up to date!")}</span>
|
||||
|
||||
@@ -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!",
|
||||
|
||||
@@ -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!",
|
||||
|
||||
+17
-15
@@ -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 {
|
||||
|
||||
@@ -16,10 +16,6 @@ describe("AboutModal", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(global.fetch as ReturnType<typeof vi.fn>).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(<AboutModal {...defaultProps} />);
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user