diff --git a/messages/en-US.json b/messages/en-US.json index 1b5c5e87..3466edf4 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1279,6 +1279,8 @@ "settingsErrorUpdateDescription": "An error occurred while updating settings", "sidebarCollapse": "Collapse", "sidebarExpand": "Expand", + "productUpdateMoreInfo": "{noOfUpdates} more updates", + "productUpdateInfo": "{noOfUpdates} updates", "pangolinUpdateAvailable": "New version available", "pangolinUpdateAvailableInfo": "Version {version} is ready to install", "pangolinUpdateAvailableReleaseNotes": "View release notes", diff --git a/src/components/ProductUpdates.tsx b/src/components/ProductUpdates.tsx index 75590c84..0b9e35de 100644 --- a/src/components/ProductUpdates.tsx +++ b/src/components/ProductUpdates.tsx @@ -3,29 +3,59 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { useLocalStorage } from "@app/hooks/useLocalStorage"; import { cn } from "@app/lib/cn"; -import { versionsQueries } from "@app/lib/queries"; -import { useQuery } from "@tanstack/react-query"; +import { productUpdatesQueries } from "@app/lib/queries"; +import { useQueries } from "@tanstack/react-query"; import { ArrowRight, BellIcon, XIcon } from "lucide-react"; import { useTranslations } from "next-intl"; -interface ProductUpdatesProps {} +export default function ProductUpdates() { + const data = useQueries({ + queries: [ + productUpdatesQueries.list, + productUpdatesQueries.latestVersion + ], + combine(result) { + return { + updates: result[0].data?.data ?? [], + latestVersion: result[1].data + }; + } + }); + + const t = useTranslations(); -export default function ProductUpdates({}: ProductUpdatesProps) { return ( -
- {/* - - 3 more updates - */} - +
+ {data.updates.length > 0 && ( + + + + {t("productUpdateMoreInfo", { + noOfUpdates: data.updates.length + })} + + + )} +
); } -function NewVersionAvailable() { +type NewVersionAvailableProps = { + version: + | Awaited< + ReturnType< + NonNullable< + typeof productUpdatesQueries.latestVersion.queryFn + > + > + > + | undefined; +}; + +function NewVersionAvailable({ version }: NewVersionAvailableProps) { const { env } = useEnvContext(); const t = useTranslations(); - const { data: version } = useQuery(versionsQueries.latestVersion()); const [ignoredVersionUpdate, setIgnoredVersionUpdate] = useLocalStorage< string | null @@ -33,8 +63,8 @@ function NewVersionAvailable() { const showNewVersionPopup = version?.data && - ignoredVersionUpdate !== version.data.pangolin.latestVersion && - env.app.version !== version.data.pangolin.latestVersion; + ignoredVersionUpdate !== version.data?.pangolin.latestVersion && + env.app.version !== version.data?.pangolin.latestVersion; if (!showNewVersionPopup) return null; diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 37ec0e76..9f1ca81c 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -1,38 +1,58 @@ -import { - type InfiniteData, - type QueryClient, - keepPreviousData, - queryOptions, - type skipToken -} from "@tanstack/react-query"; +import { keepPreviousData, queryOptions } from "@tanstack/react-query"; import { durationToMs } from "./durationToMs"; import { build } from "@server/build"; import { remote } from "./api"; import type ResponseT from "@server/types/Response"; -export const versionsQueries = { - latestVersion: () => - queryOptions({ - queryKey: ["LATEST_VERSION"] as const, - queryFn: async ({ signal }) => { - const data = await remote.get< - ResponseT<{ - pangolin: { - latestVersion: string; - releaseNotes: string; - }; - }> - >("/latest-version"); - return data.data; - }, - placeholderData: keepPreviousData, - refetchInterval: (query) => { - if (query.state.data) { - return durationToMs(30, "minutes"); - } - return false; - }, - enabled: build === "oss" || build === "enterprise" // disabled in cloud version - // because we don't need to listen for new versions there - }) +type ProductUpdate = { + link: string | null; + edition: "enterprise" | "community" | "cloud" | null; + id: number; + priority: "CRITICAL" | "IMPORTANT" | "NORMAL" | null; + title: string; + contents: string; + publishedAt: Date; + showUntil: Date; +}; + +export const productUpdatesQueries = { + list: queryOptions({ + queryKey: ["PRODUCT_UPDATES"] as const, + queryFn: async ({ signal }) => { + const data = await remote.get>( + "/product-updates", + { signal } + ); + return data.data; + }, + refetchInterval: (query) => { + if (query.state.data) { + return durationToMs(5, "minutes"); + } + return false; + } + }), + latestVersion: queryOptions({ + queryKey: ["LATEST_VERSION"] as const, + queryFn: async ({ signal }) => { + const data = await remote.get< + ResponseT<{ + pangolin: { + latestVersion: string; + releaseNotes: string; + }; + }> + >("/versions", { signal }); + return data.data; + }, + placeholderData: keepPreviousData, + refetchInterval: (query) => { + if (query.state.data) { + return durationToMs(30, "minutes"); + } + return false; + }, + enabled: build === "oss" || build === "enterprise" // disabled in cloud version + // because we don't need to listen for new versions there + }) };