This commit is contained in:
Fred KISSIE
2025-11-05 08:38:23 +01:00
parent a247ef7564
commit b9ce316574
3 changed files with 98 additions and 46 deletions

View File

@@ -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",

View File

@@ -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 (
<div className="flex flex-col gap-1 relative z-1 overflow-clip">
{/* <small className="text-xs text-muted-foreground flex items-center gap-1">
<BellIcon className="flex-none size-3" />
<span>3 more updates</span>
</small> */}
<NewVersionAvailable />
<div className="flex flex-col gap-1 overflow-clip">
{data.updates.length > 0 && (
<small className="text-xs text-muted-foreground flex items-center gap-1">
<BellIcon className="flex-none size-3" />
<span>
{t("productUpdateMoreInfo", {
noOfUpdates: data.updates.length
})}
</span>
</small>
)}
<NewVersionAvailable version={data.latestVersion} />
</div>
);
}
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;

View File

@@ -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<ResponseT<ProductUpdate[]>>(
"/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
})
};