From c7431ef1f59b08ab331237cab9f88247bdcb82e2 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Sun, 2 Aug 2026 16:10:16 +0200 Subject: [PATCH] fix: harden widget review follow-ups --- packages/widgets/src/coolify/resource-row.tsx | 7 +++++-- .../widgets/src/coolify/servers-section.tsx | 2 +- .../network-status/component.tsx | 17 ++++++++++++----- .../widgets/src/paperless-ngx/component.tsx | 7 ++++++- packages/widgets/src/vpn/component.tsx | 5 +++-- packages/widgets/src/vpn/vpn-card.tsx | 12 ++++++++++-- 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/packages/widgets/src/coolify/resource-row.tsx b/packages/widgets/src/coolify/resource-row.tsx index fba5279ca..44ca137a1 100644 --- a/packages/widgets/src/coolify/resource-row.tsx +++ b/packages/widgets/src/coolify/resource-row.tsx @@ -3,6 +3,8 @@ import { ActionIcon, Anchor, Group, Indicator, Stack, Text } from "@mantine/core"; import { IconFileText, IconLink } from "@tabler/icons-react"; +import { useScopedI18n } from "@homarr/translation/client"; + import actionTargetClasses from "../common/action-target.module.css"; import { cleanFqdn, getResourceTimestamp, getStatusColor, parseStatus } from "./coolify-utils"; @@ -25,6 +27,7 @@ interface ResourceRowProps { } export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRowProps) { + const t = useScopedI18n("widget.coolify"); const status = parseStatus(item.status ?? ""); const statusColor = getStatusColor(status); @@ -64,7 +67,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow href={cleanFqdn(item.fqdn)} target="_blank" rel="noopener noreferrer" - aria-label={`Open ${item.name}`} + aria-label={t("action.openResource", { name: item.name })} size="xs" variant="subtle" c="dimmed" @@ -79,7 +82,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow href={logsUrl} target="_blank" rel="noopener noreferrer" - aria-label={`Open ${item.name} logs`} + aria-label={t("action.openLogs", { name: item.name })} size="xs" variant="subtle" c="dimmed" diff --git a/packages/widgets/src/coolify/servers-section.tsx b/packages/widgets/src/coolify/servers-section.tsx index 6a5da6ef4..24f82852a 100644 --- a/packages/widgets/src/coolify/servers-section.tsx +++ b/packages/widgets/src/coolify/servers-section.tsx @@ -42,7 +42,7 @@ export function ServersSection({ ) { - const { data: summaries = [], isPending } = clientApi.widget.networkController.summary.useQuery({ + const { + data: summaries, + error, + isPending, + } = clientApi.widget.networkController.summary.useQuery({ integrationIds, }); - const t = useI18n(); + const t = useScopedI18n("widget.networkControllerStatus"); + const tCommon = useScopedI18n("common"); - const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]); + const data = useMemo(() => (summaries ?? []).flatMap(({ summary }) => summary), [summaries]); const isAdvanced = displayMode === "advanced"; const countWifiGuests = data.reduce((sum, summary) => sum + summary.wifi.guests, 0); const countWifiUsers = data.reduce((sum, summary) => sum + summary.wifi.users, 0); @@ -38,11 +43,13 @@ export default function NetworkControllerNetworkStatusWidget({ const countLanUsers = data.reduce((sum, summary) => sum + summary.lan.users, 0); const useHorizontalStats = !isAdvanced && height < 150 && width >= 200; + if (error && summaries === undefined) throw error; + if (isPending || data.length === 0) { return (
- {isPending ? t("common.action.loading") : t("widget.networkControllerStatus.error.integrationsDisconnected")} + {isPending ? tCommon("action.loading") : t("error.integrationsDisconnected")}
); diff --git a/packages/widgets/src/paperless-ngx/component.tsx b/packages/widgets/src/paperless-ngx/component.tsx index e0d08e49e..ae22402d8 100644 --- a/packages/widgets/src/paperless-ngx/component.tsx +++ b/packages/widgets/src/paperless-ngx/component.tsx @@ -79,7 +79,11 @@ export default function PaperlessNgxWidget({ }: WidgetComponentProps<"paperlessNgx">) { const t = useScopedI18n("widget.paperlessNgx"); const tCommon = useScopedI18n("common"); - const { data: stats, isPending } = clientApi.widget.paperlessNgx.getStats.useQuery({ + const { + data: stats, + error, + isPending, + } = clientApi.widget.paperlessNgx.getStats.useQuery({ integrationId: integrationIds[0] ?? "", }); @@ -92,6 +96,7 @@ export default function PaperlessNgxWidget({ ); } + if (error && stats === undefined) throw error; if (!stats) return ; const canShowInboxHero = options.showDocumentsInbox && options.showDocumentsTotal; diff --git a/packages/widgets/src/vpn/component.tsx b/packages/widgets/src/vpn/component.tsx index 295499d78..d9526923c 100644 --- a/packages/widgets/src/vpn/component.tsx +++ b/packages/widgets/src/vpn/component.tsx @@ -3,7 +3,7 @@ import { Center, Flex, ScrollArea, SimpleGrid, Stack, Text } from "@mantine/core"; import { clientApi } from "@homarr/api/client"; -import { useScopedI18n } from "@homarr/translation/client"; +import { useCurrentLocale, useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; import { VpnIntegrationCard } from "./vpn-card"; @@ -21,6 +21,7 @@ export default function VpnWidget({ }); const t = useScopedI18n("widget.vpn"); const tCommon = useScopedI18n("common"); + const locale = useCurrentLocale(); const dense = displayMode === "compact" && (width < 240 || height < 120); if (isPending || integrations.length === 0) { @@ -56,7 +57,7 @@ export default function VpnWidget({ {!result.error && new Date(result.integration.updatedAt).getTime() > 0 && ( - {new Date(result.integration.updatedAt).toLocaleString()} + {new Date(result.integration.updatedAt).toLocaleString(locale)} )} diff --git a/packages/widgets/src/vpn/vpn-card.tsx b/packages/widgets/src/vpn/vpn-card.tsx index 8ddcfd7fd..aeaa36c0c 100644 --- a/packages/widgets/src/vpn/vpn-card.tsx +++ b/packages/widgets/src/vpn/vpn-card.tsx @@ -102,8 +102,12 @@ function VpnStatusColumn({ compact: boolean; dense: boolean; }) { + const t = useScopedI18n("widget.vpn"); // Connected shows a shield with a check; anything else (stopped or unavailable) shows a shield with a cross. const ShieldIcon = vpnStatus === RUNNING_STATUS ? IconShieldCheck : IconShieldX; + const statusLabel = t( + vpnStatus === RUNNING_STATUS ? "status.running" : vpnStatus ? "status.notRunning" : "status.unavailable", + ); return ( } > DNS - {status || "unavailable"} + {statusLabel} ); }