mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(settings): collapse Notifications redundancy onto one status surface
Telegram, ntfy, and Web Push cards used to paint their own
"Configured · Disabled / Active" badge pair in the top-right
header. The `<NotificationStatusCard>` rendered above the same
cards already shows a live channel pill ("Active · 12 min ago" /
"Auto-disabled — web_push_410_gone"), so the per-card badges were
the third (sometimes fourth) surface for the same state.
Drop the dual-Badge slot from all three cards. The section header
pill is the single source of truth, the per-card surface now
reads as identity + description + form, and the in-app vocabulary
no longer mixes "Konfiguriert", "Aktiv", and "Verbunden" for the
same fact.
Copy pass:
* Settings → Notifications description: "Live-Übersicht aller
konfigurierten Kanäle." (was a 13-word sentence repeating "Kanal"
three times).
* `notificationStatus.title` renames to "Zustellstatus" /
"Delivery status" so the page header and the first card don't
both anchor on "Kanal".
* `notificationStatus.description` compresses the "Auto-disabled"
definition.
* Telegram card title drops "-Benachrichtigungen" — the section is
Notifications, so saying it again is wasted shelf space.
* `telegramDescription` shortens to "Medikamenten-Erinnerungen
per Bot." / "Medication reminders via bot."
* `moodLogDescription` drops the second "moodLog" mention.
Notification-status SSR test updated to the new copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
af17db5dd5
commit
ff06b0ced2
6 files changed
+27
-64
No files matched your search
+6
-6
@@ -1506,7 +1506,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"title": "Benachrichtigungen",
|
"title": "Benachrichtigungen",
|
||||||
"description": "Zustellung pro Kanal und die Live-Übersicht der Kanalzuverlässigkeit für jeden konfigurierten Kanal."
|
"description": "Live-Übersicht aller konfigurierten Kanäle."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
@@ -1669,8 +1669,8 @@
|
|||||||
"singleDevice": "Einzelgerät",
|
"singleDevice": "Einzelgerät",
|
||||||
"multiDevice": "Multi-Gerät",
|
"multiDevice": "Multi-Gerät",
|
||||||
"backedUp": "gesichert",
|
"backedUp": "gesichert",
|
||||||
"telegram": "Telegram-Benachrichtigungen",
|
"telegram": "Telegram",
|
||||||
"telegramDescription": "Erhalte Erinnerungen bei vergessenen Medikamenten-Einnahmen per Telegram.",
|
"telegramDescription": "Medikamenten-Erinnerungen per Bot.",
|
||||||
"telegramSaved": "Telegram-Einstellungen gespeichert",
|
"telegramSaved": "Telegram-Einstellungen gespeichert",
|
||||||
"botToken": "Bot-Token",
|
"botToken": "Bot-Token",
|
||||||
"chatId": "Chat-ID",
|
"chatId": "Chat-ID",
|
||||||
@@ -1883,7 +1883,7 @@
|
|||||||
"saved": "Gespeichert",
|
"saved": "Gespeichert",
|
||||||
"savingError": "Fehler beim Speichern",
|
"savingError": "Fehler beim Speichern",
|
||||||
"moodLogTitle": "moodLog",
|
"moodLogTitle": "moodLog",
|
||||||
"moodLogDescription": "Stimmungsdaten aus moodLog importieren",
|
"moodLogDescription": "Stimmungseinträge übernehmen.",
|
||||||
"moodLogUrl": "moodLog URL",
|
"moodLogUrl": "moodLog URL",
|
||||||
"moodLogUrlPlaceholder": "https://mood.example.com",
|
"moodLogUrlPlaceholder": "https://mood.example.com",
|
||||||
"moodLogApiKey": "API-Key",
|
"moodLogApiKey": "API-Key",
|
||||||
@@ -1944,8 +1944,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notificationStatus": {
|
"notificationStatus": {
|
||||||
"title": "Kanalzuverlässigkeit",
|
"title": "Zustellstatus",
|
||||||
"description": "Live-Status jedes Benachrichtigungskanals — \"Automatisch deaktiviert\" bedeutet, HealthLog hat nach einem dauerhaften Fehler aufgehört, weiterzusenden.",
|
"description": "Pro Kanal aktueller Status; \"Automatisch deaktiviert\" stoppt nach dauerhaften Fehlern.",
|
||||||
"emptyDescription": "Noch keine Kanäle konfiguriert. Lege unten einen Kanal an, um die Zustellung zu überwachen.",
|
"emptyDescription": "Noch keine Kanäle konfiguriert. Lege unten einen Kanal an, um die Zustellung zu überwachen.",
|
||||||
"stateActive": "Aktiv",
|
"stateActive": "Aktiv",
|
||||||
"stateAutoDisabled": "Automatisch deaktiviert",
|
"stateAutoDisabled": "Automatisch deaktiviert",
|
||||||
|
|||||||
+6
-6
@@ -1510,7 +1510,7 @@
|
|||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"title": "Notifications",
|
"title": "Notifications",
|
||||||
"description": "Per-channel delivery and the live reliability board for every configured channel."
|
"description": "Live overview of every configured channel."
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
@@ -1676,8 +1676,8 @@
|
|||||||
"singleDevice": "Single device",
|
"singleDevice": "Single device",
|
||||||
"multiDevice": "Multi-device",
|
"multiDevice": "Multi-device",
|
||||||
"backedUp": "backed up",
|
"backedUp": "backed up",
|
||||||
"telegram": "Telegram Notifications",
|
"telegram": "Telegram",
|
||||||
"telegramDescription": "Get reminders for missed medication intake via Telegram.",
|
"telegramDescription": "Medication reminders via bot.",
|
||||||
"telegramSaved": "Telegram settings saved",
|
"telegramSaved": "Telegram settings saved",
|
||||||
"botToken": "Bot Token",
|
"botToken": "Bot Token",
|
||||||
"chatId": "Chat ID",
|
"chatId": "Chat ID",
|
||||||
@@ -1890,7 +1890,7 @@
|
|||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"savingError": "Error saving",
|
"savingError": "Error saving",
|
||||||
"moodLogTitle": "moodLog",
|
"moodLogTitle": "moodLog",
|
||||||
"moodLogDescription": "Import mood data from moodLog",
|
"moodLogDescription": "Import mood entries.",
|
||||||
"moodLogUrl": "moodLog URL",
|
"moodLogUrl": "moodLog URL",
|
||||||
"moodLogUrlPlaceholder": "https://mood.example.com",
|
"moodLogUrlPlaceholder": "https://mood.example.com",
|
||||||
"moodLogApiKey": "API Key",
|
"moodLogApiKey": "API Key",
|
||||||
@@ -1951,8 +1951,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notificationStatus": {
|
"notificationStatus": {
|
||||||
"title": "Channel reliability",
|
"title": "Delivery status",
|
||||||
"description": "Live status of every notification channel — Auto-disabled means HealthLog stopped retrying after a permanent error.",
|
"description": "Per-channel state; \"Auto-disabled\" stops retries after permanent errors.",
|
||||||
"emptyDescription": "No channels configured yet. Add a channel below to start tracking its delivery health.",
|
"emptyDescription": "No channels configured yet. Add a channel below to start tracking its delivery health.",
|
||||||
"stateActive": "Active",
|
"stateActive": "Active",
|
||||||
"stateAutoDisabled": "Auto-disabled",
|
"stateAutoDisabled": "Auto-disabled",
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ describe("<NotificationStatusCard />", () => {
|
|||||||
it("renders empty state when no channels exist", () => {
|
it("renders empty state when no channels exist", () => {
|
||||||
setChannels([]);
|
setChannels([]);
|
||||||
const html = render(<NotificationStatusCard />);
|
const html = render(<NotificationStatusCard />);
|
||||||
expect(html).toContain("Channel reliability");
|
expect(html).toContain("Delivery status");
|
||||||
expect(html).toContain("No channels configured yet");
|
expect(html).toContain("No channels configured yet");
|
||||||
expect(html).not.toContain('data-testid="notification-status-list"');
|
expect(html).not.toContain('data-testid="notification-status-list"');
|
||||||
});
|
});
|
||||||
@@ -189,7 +189,7 @@ describe("<NotificationStatusCard />", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const html = render(<NotificationStatusCard />, "de");
|
const html = render(<NotificationStatusCard />, "de");
|
||||||
expect(html).toContain("Kanalzuverlässigkeit");
|
expect(html).toContain("Zustellstatus");
|
||||||
expect(html).toContain("Automatisch deaktiviert");
|
expect(html).toContain("Automatisch deaktiviert");
|
||||||
expect(html).toContain("Wieder aktivieren");
|
expect(html).toContain("Wieder aktivieren");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useState } from "react";
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Bell, Loader2, Send } from "lucide-react";
|
import { Bell, Loader2, Send } from "lucide-react";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -101,24 +100,13 @@ export function NtfyCard({ isAuthenticated }: { isAuthenticated: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border-border rounded-xl border p-6">
|
<div className="bg-card border-border rounded-xl border p-6">
|
||||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
{/* v1.4.33 — drop the per-card status badge pair; the channel
|
||||||
|
pill in `<NotificationStatusCard>` above already shows
|
||||||
|
live ntfy state. See `telegram-card.tsx` for the same fix. */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Bell className="text-primary h-5 w-5" />
|
<Bell className="text-primary h-5 w-5" />
|
||||||
<h2 className="text-lg font-semibold">{t("settings.ntfy")}</h2>
|
<h2 className="text-lg font-semibold">{t("settings.ntfy")}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
{settings?.serverUrl && settings?.topic && (
|
|
||||||
<Badge className="border-dracula-green/30 bg-dracula-green/15 text-dracula-green">
|
|
||||||
{t("settings.configured")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{settings?.enabled && (
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{t("common.enabled")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
<p className="text-muted-foreground mt-1 text-xs">
|
||||||
{t("settings.ntfyDescription")}
|
{t("settings.ntfyDescription")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useState } from "react";
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Loader2, MessageCircle, Save, Send } from "lucide-react";
|
import { Loader2, MessageCircle, Save, Send } from "lucide-react";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -106,29 +105,16 @@ export function TelegramCard({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border-border rounded-xl border p-6">
|
<div className="bg-card border-border rounded-xl border p-6">
|
||||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
{/* v1.4.33 — drop the per-card status badge pair. The
|
||||||
|
`<NotificationStatusCard>` above already paints the live
|
||||||
|
channel pill for Telegram; surfacing the same fact a
|
||||||
|
second time inside this card produced overlapping status
|
||||||
|
surfaces (page header + card pill + dual Badge + the
|
||||||
|
enable Switch) for one bit of state. */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<MessageCircle className="text-primary h-5 w-5" />
|
<MessageCircle className="text-primary h-5 w-5" />
|
||||||
<h2 className="text-lg font-semibold">{t("settings.telegram")}</h2>
|
<h2 className="text-lg font-semibold">{t("settings.telegram")}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
{/* F-16 (v1.4.19): show one unified status pill instead of
|
|
||||||
the previous "Configured" + "Enabled" pair, which had no
|
|
||||||
visual hierarchy and made it unclear how the two states
|
|
||||||
differed. The new pill collapses both bits of info:
|
|
||||||
green when active, neutral when configured-but-paused. */}
|
|
||||||
{settings?.hasBotToken && settings?.chatId && settings?.enabled && (
|
|
||||||
<Badge className="border-dracula-green/30 bg-dracula-green/15 text-dracula-green">
|
|
||||||
{t("common.active")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{settings?.hasBotToken && settings?.chatId && !settings?.enabled && (
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{t("settings.configured")} · {t("common.disabled")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
<p className="text-muted-foreground mt-1 text-xs">
|
||||||
{t("settings.telegramDescription")}
|
{t("settings.telegramDescription")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BellRing, Loader2 } from "lucide-react";
|
import { BellRing, Loader2 } from "lucide-react";
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { TestConnectionButton } from "@/components/settings/test-connection-button";
|
import { TestConnectionButton } from "@/components/settings/test-connection-button";
|
||||||
import { useTranslations } from "@/lib/i18n/context";
|
import { useTranslations } from "@/lib/i18n/context";
|
||||||
@@ -157,24 +156,14 @@ export function WebPushCard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-card border-border rounded-xl border p-6">
|
<div className="bg-card border-border rounded-xl border p-6">
|
||||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
{/* v1.4.33 — drop the per-card status badge pair; the channel
|
||||||
|
pill in `<NotificationStatusCard>` above already shows
|
||||||
|
live Web Push state. See `telegram-card.tsx` for the same
|
||||||
|
fix. */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BellRing className="text-primary h-5 w-5" />
|
<BellRing className="text-primary h-5 w-5" />
|
||||||
<h2 className="text-lg font-semibold">{t("settings.webPush")}</h2>
|
<h2 className="text-lg font-semibold">{t("settings.webPush")}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
{!loading && isSubscribed && (
|
|
||||||
<Badge className="border-dracula-green/30 bg-dracula-green/15 text-dracula-green">
|
|
||||||
{t("settings.configured")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
{!loading && isSubscribed && (
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
{t("settings.webPushActive")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-muted-foreground mt-1 text-xs">
|
<p className="text-muted-foreground mt-1 text-xs">
|
||||||
{t("settings.webPushDescription")}
|
{t("settings.webPushDescription")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in new issue
Block a user