From c64b102aaa35df453cb957007cd3e57fc4125fcf Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Wed, 5 Nov 2025 23:29:48 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LayoutSidebar.tsx | 2 +- src/components/ProductUpdates.tsx | 261 +++++++++++++++++++++--------- src/lib/queries.ts | 2 +- 3 files changed, 189 insertions(+), 76 deletions(-) diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index 32bae1e8..50a0c8e8 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -139,7 +139,7 @@ export function LayoutSidebar({
- + {build === "enterprise" && (
diff --git a/src/components/ProductUpdates.tsx b/src/components/ProductUpdates.tsx index 5d56892b..b0b3530a 100644 --- a/src/components/ProductUpdates.tsx +++ b/src/components/ProductUpdates.tsx @@ -3,45 +3,159 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { useLocalStorage } from "@app/hooks/useLocalStorage"; import { cn } from "@app/lib/cn"; -import { productUpdatesQueries } from "@app/lib/queries"; +import { type ProductUpdate, productUpdatesQueries } from "@app/lib/queries"; import { useQueries } from "@tanstack/react-query"; -import { ArrowRight, BellIcon, XIcon } from "lucide-react"; +import { ArrowRight, BellIcon, ChevronRightIcon, XIcon } from "lucide-react"; import { useTranslations } from "next-intl"; +import { Transition } from "@headlessui/react"; +import * as React from "react"; -export default function ProductUpdates() { +export default function ProductUpdates({ + isCollapsed +}: { + isCollapsed?: boolean; +}) { const data = useQueries({ queries: [ productUpdatesQueries.list, productUpdatesQueries.latestVersion ], combine(result) { + if (result[0].isLoading || result[1].isLoading) return null; return { updates: result[0].data?.data ?? [], latestVersion: result[1].data }; } }); - + const { env } = useEnvContext(); const t = useTranslations(); + const [showMoreUpdatesText, setShowMoreUpdatesText] = React.useState(false); + + // we need to delay the initial + React.useEffect(() => { + const timeout = setTimeout(() => setShowMoreUpdatesText(true), 500); + return () => clearTimeout(timeout); + }, []); + + const [ignoredVersionUpdate, setIgnoredVersionUpdate] = useLocalStorage< + string | null + >("ignored-version", null); + + const [showNewVersionPopup, setShowNewVersionPopup] = React.useState(true); + + if (!data) return null; + + // const showNewVersionPopup = Boolean( + // data?.latestVersion?.data && + // ignoredVersionUpdate !== + // data.latestVersion.data?.pangolin.latestVersion && + // env.app.version !== data.latestVersion.data?.pangolin.latestVersion + // ); return ( -
- {data.updates.length > 0 && ( - - - - {t("productUpdateMoreInfo", { - noOfUpdates: data.updates.length - })} - - +
+ > + { + // setIgnoredVersionUpdate( + // data.latestVersion?.data?.pangolin.latestVersion ?? null + // ); + setShowNewVersionPopup(false); + }} + show={showNewVersionPopup} + /> + + + + {data.updates.length > 0 && ( + <> + + + {showNewVersionPopup + ? t("productUpdateMoreInfo", { + noOfUpdates: data.updates.length + }) + : t("productUpdateInfo", { + noOfUpdates: data.updates.length + })} + + + )} + + + 0} + />
); } +type ProductUpdatesPopupProps = { updates: ProductUpdate[]; show: boolean }; + +function ProductUpdatesPopup({ updates, show }: ProductUpdatesPopupProps) { + const [open, setOpen] = React.useState(false); + const t = useTranslations(); + + // we need to delay the initial opening state to have an animation on `appear` + React.useEffect(() => { + if (show) { + requestAnimationFrame(() => setOpen(true)); + } + }, [show]); + + return ( + +
+
+ +
+
+

What's new

+ + {updates[0].contents} + +
+ +
+
+ ); +} + type NewVersionAvailableProps = { + onClose: () => void; + show: boolean; version: | Awaited< ReturnType< @@ -49,72 +163,71 @@ type NewVersionAvailableProps = { typeof productUpdatesQueries.latestVersion.queryFn > > - > + >["data"] | undefined; }; -function NewVersionAvailable({ version }: NewVersionAvailableProps) { - const { env } = useEnvContext(); - console.log({ - env - }); +function NewVersionAvailable({ + version, + show, + onClose +}: NewVersionAvailableProps) { const t = useTranslations(); + const [open, setOpen] = React.useState(false); - const [ignoredVersionUpdate, setIgnoredVersionUpdate] = useLocalStorage< - string | null - >("ignored-version", null); - - const showNewVersionPopup = - version?.data && - ignoredVersionUpdate !== version.data?.pangolin.latestVersion && - env.app.version !== version.data?.pangolin.latestVersion; - - if (!showNewVersionPopup) return null; + // we need to delay the initial opening state to have an animation on `appear` + React.useEffect(() => { + if (show) { + requestAnimationFrame(() => setOpen(true)); + } + }, [show]); return ( -
- {version?.data && ( - <> -
- -
-
-

- {t("pangolinUpdateAvailable")} -

- - {t("pangolinUpdateAvailableInfo", { - version: version.data.pangolin.latestVersion - })} - - + - - - )} -
+ + + + )} +
+ ); } diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 9f1ca81c..d8e4ee89 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -4,7 +4,7 @@ import { build } from "@server/build"; import { remote } from "./api"; import type ResponseT from "@server/types/Response"; -type ProductUpdate = { +export type ProductUpdate = { link: string | null; edition: "enterprise" | "community" | "cloud" | null; id: number;