From 454752e46d80f368c7cb5e6766c5db5c72341ba5 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Mon, 3 Aug 2026 20:04:56 +0200 Subject: [PATCH] fix: address widget review feedback --- packages/widgets/src/bookmarks/component.tsx | 6 ++- packages/widgets/src/rssFeed/component.tsx | 42 +++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/widgets/src/bookmarks/component.tsx b/packages/widgets/src/bookmarks/component.tsx index aadf8b3b2..c241549ae 100644 --- a/packages/widgets/src/bookmarks/component.tsx +++ b/packages/widgets/src/bookmarks/component.tsx @@ -1,5 +1,6 @@ "use client"; +import { useMemo } from "react"; import { Anchor, Box, @@ -99,7 +100,10 @@ export default function BookmarksWidget({ [data], ); - const compactLayout = getCompactBookmarkLayout(width, height, data.length, options.layout); + const compactLayout = useMemo( + () => getCompactBookmarkLayout(width, height, data.length, options.layout), + [width, height, data.length, options.layout], + ); const compactHideTitle = options.hideTitle || (compactLayout.hideTitle && !options.hideIcon); const compactHideHostname = options.hideHostname || (compactLayout.hideHostname && !(options.hideTitle && options.hideIcon)); diff --git a/packages/widgets/src/rssFeed/component.tsx b/packages/widgets/src/rssFeed/component.tsx index 1d38d3ed3..5bcd3bfd3 100644 --- a/packages/widgets/src/rssFeed/component.tsx +++ b/packages/widgets/src/rssFeed/component.tsx @@ -1,7 +1,7 @@ "use client"; import { Alert, Card, Flex, Group, Image, ScrollArea, SimpleGrid, Text } from "@mantine/core"; -import { IconAlertTriangle, IconClock } from "@tabler/icons-react"; +import { IconAlertTriangle, IconClock, IconServerOff } from "@tabler/icons-react"; import dayjs from "dayjs"; import type { RouterInputs, RouterOutputs } from "@homarr/api"; @@ -11,45 +11,57 @@ import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; import { getSafeApplicationUrl, SAFE_NEW_TAB_REL } from "../common/application-url"; +import { BaseWidgetError } from "../errors/base-component"; import classes from "./component.module.scss"; const useLiveFeed = (input: RouterInputs["widget"]["rssFeed"]["getFeeds"]) => { - const { data, error } = clientApi.widget.rssFeed.getFeeds.useQuery(input); - if (error && data === undefined) throw error; + const query = clientApi.widget.rssFeed.getFeeds.useQuery(input); + if (query.error && query.data === undefined) { + return { hasError: true as const, refetch: query.refetch }; + } // Persisted query caches can still contain the array response used before failed-feed metadata was added. - const normalizedData = data as typeof data | RouterOutputs["widget"]["rssFeed"]["getFeeds"]["entries"]; + const normalizedData = query.data as typeof query.data | RouterOutputs["widget"]["rssFeed"]["getFeeds"]["entries"]; if (Array.isArray(normalizedData)) { return { + hasError: false as const, entries: normalizedData, failedFeedCount: 0, - isStale: Boolean(error), + isStale: Boolean(query.error), }; } return { + hasError: false as const, entries: normalizedData?.entries ?? [], failedFeedCount: normalizedData?.failedFeedCount ?? 0, - isStale: Boolean(error), + isStale: Boolean(query.error), }; }; export default function RssFeed({ options, width, height }: WidgetComponentProps<"rssFeed">) { - const { - entries: feedEntries, - failedFeedCount, - isStale, - } = useLiveFeed({ + const feed = useLiveFeed({ urls: options.feedUrls, maximumAmountPosts: typeof options.maximumAmountPosts === "number" ? options.maximumAmountPosts : 100, }); const board = useRequiredBoard(); const t = useScopedI18n("widget.rssFeed"); - const warning = isStale + if (feed.hasError) { + return ( + void feed.refetch()} + /> + ); + } + + const warning = feed.isStale ? t("warning.stale") - : failedFeedCount > 0 - ? t("warning.partial", { count: failedFeedCount }) + : feed.failedFeedCount > 0 + ? t("warning.partial", { count: feed.failedFeedCount }) : undefined; const languageDir = options.enableRtl ? "RTL" : "LTR"; @@ -69,7 +81,7 @@ export default function RssFeed({ options, width, height }: WidgetComponentProps )} - {feedEntries.map((feedEntry) => { + {feed.entries.map((feedEntry) => { const href = getSafeApplicationUrl(feedEntry.link, { baseUrl: feedEntry.feedUrl }); return (