From 8808a6935a121f7915b2ef5b47009fbc680c7047 Mon Sep 17 00:00:00 2001 From: Kacper Wojciechowski <39823706+jog1t@users.noreply.github.com> Date: Thu, 18 Sep 2025 22:50:54 +0200 Subject: [PATCH] featI(cloud): add changelog --- frontend/src/app/changelog.tsx | 153 ++++++++++++++++++ .../data-providers/engine-data-provider.tsx | 20 +-- frontend/src/app/layout.tsx | 22 +++ frontend/src/components/ui/tooltip.tsx | 10 +- frontend/src/queries/global.ts | 25 ++- frontend/src/queries/types.ts | 27 ++++ 6 files changed, 242 insertions(+), 15 deletions(-) create mode 100644 frontend/src/app/changelog.tsx create mode 100644 frontend/src/queries/types.ts diff --git a/frontend/src/app/changelog.tsx b/frontend/src/app/changelog.tsx new file mode 100644 index 0000000000..454edcb18f --- /dev/null +++ b/frontend/src/app/changelog.tsx @@ -0,0 +1,153 @@ +import { faSparkle, Icon } from "@rivet-gg/icons"; +import { useSuspenseQuery } from "@tanstack/react-query"; +import { useLocalStorage } from "usehooks-ts"; +import { + Avatar, + AvatarFallback, + AvatarImage, + Badge, + cn, + Picture, + PictureFallback, + PictureImage, + Skeleton, + Slot, + WithTooltip, +} from "@/components"; +import { changelogQueryOptions } from "@/queries/global"; +import type { ChangelogItem } from "@/queries/types"; + +interface ChangelogEntryProps extends ChangelogItem { + isNew?: boolean; +} + +export function ChangelogEntry({ + published, + images, + title, + description, + slug, + authors, + isNew, +}: ChangelogEntryProps) { + return ( +
+
+
+
+ +
+

+ {isNew ? ( + New Update + ) : ( + Latest Update + )} +

+
+ + {new Date(published).toLocaleDateString()} + +
+ + + + + + + + + +

{title}

+ +

+ {description}{" "} + + Read more... + +

+
+
+
+ + + + {authors[0].name[0]} + + + +
+

+ {authors[0].name} +

+

+ {authors[0].role} +

+
+
+
+
+
+ ); +} +interface ChangelogProps { + className?: string; + children?: React.ReactNode; +} + +export function Changelog({ className, children, ...props }: ChangelogProps) { + const { data } = useSuspenseQuery(changelogQueryOptions()); + + const [lastChangelog, setLast] = useLocalStorage( + "rivet-lastchangelog", + null, + ); + + const hasNewChangelog = !lastChangelog + ? data.length > 0 + : data.some( + (entry) => new Date(entry.published) > new Date(lastChangelog), + ); + + return ( + { + if (isOpen) { + setLast(data[0].published); + } + }} + trigger={ + + {children} + + } + content={} + /> + ); +} diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx index 6ea9a862ee..1a8e548dcd 100644 --- a/frontend/src/app/data-providers/engine-data-provider.tsx +++ b/frontend/src/app/data-providers/engine-data-provider.tsx @@ -487,9 +487,7 @@ export const createNamespaceContext = ({ }, createRunnerConfigMutationOptions( opts: { - onSuccess?: ( - data: Rivet.NamespacesRunnerConfigsUpsertResponse, - ) => void; + onSuccess?: (data: Rivet.RunnerConfigsUpsertResponse) => void; } = {}, ) { return { @@ -500,14 +498,12 @@ export const createNamespaceContext = ({ config, }: { name: string; - config: Rivet.NamespacesRunnerConfig; + config: Rivet.RunnerConfig; }) => { - const response = - await client.namespacesRunnerConfigs.upsert( - namespaceId, - name, - config, - ); + const response = await client.runnerConfigs.upsert(name, { + namespace, + ...config, + }); return response; }, }; @@ -517,9 +513,9 @@ export const createNamespaceContext = ({ queryKey: [{ namespace }, "runners", "configs"], initialPageParam: undefined as string | undefined, queryFn: async ({ signal: abortSignal, pageParam }) => { - const response = await client.namespacesRunnerConfigs.list( - namespace, + const response = await client.runnerConfigs.list( { + namespace, cursor: pageParam ?? undefined, limit: RECORDS_PER_PAGE, }, diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 8e3de130c1..7ebef7246e 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -34,6 +34,7 @@ import { cn, DocsSheet, type ImperativePanelHandle, + Ping, ResizableHandle, ResizablePanel, ResizablePanelGroup, @@ -44,6 +45,7 @@ import { useInspectorDataProvider } from "@/components/actors"; import type { HeaderLinkProps } from "@/components/header/header-link"; import { ensureTrailingSlash } from "@/lib/utils"; import { ActorBuildsList } from "./actor-builds-list"; +import { Changelog } from "./changelog"; import { ContextSwitcher } from "./context-switcher"; import { useInspectorCredentials } from "./credentials-context"; import { NamespaceSelect } from "./namespace-select"; @@ -185,6 +187,26 @@ const Sidebar = ({ __APP_TYPE__ !== "cloud" ? "pb-4" : "", )} > + + + ; } -const WithTooltip = ({ trigger, content, ...rest }: WithTooltipProps) => { +const WithTooltip = ({ + trigger, + content, + contentProps, + ...rest +}: WithTooltipProps) => { return ( {trigger} - {content} + {content} ); diff --git a/frontend/src/queries/global.ts b/frontend/src/queries/global.ts index 7c4dfe4573..a4f137134c 100644 --- a/frontend/src/queries/global.ts +++ b/frontend/src/queries/global.ts @@ -1,6 +1,12 @@ -import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query"; +import { + MutationCache, + QueryCache, + QueryClient, + queryOptions, +} from "@tanstack/react-query"; import { toast } from "@/components"; import { modal } from "@/utils/modal-utils"; +import { Changelog } from "./types"; const queryCache = new QueryCache({ onError(error, query) { @@ -26,6 +32,23 @@ const mutationCache = new MutationCache({ }, }); +export const changelogQueryOptions = () => { + return queryOptions({ + queryKey: ["changelog", __APP_BUILD_ID__], + staleTime: 1 * 60 * 60 * 1000, // 1 hour + queryFn: async () => { + const response = await fetch( + "https://rivet-site.vercel.app/changelog.json", + ); + if (!response.ok) { + throw new Error("Failed to fetch changelog"); + } + const result = Changelog.parse(await response.json()); + return result; + }, + }); +}; + export const queryClient = new QueryClient({ defaultOptions: { queries: { diff --git a/frontend/src/queries/types.ts b/frontend/src/queries/types.ts new file mode 100644 index 0000000000..3dc37c341f --- /dev/null +++ b/frontend/src/queries/types.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; + +export const ChangelogItem = z.object({ + published: z.string(), + images: z.array( + z.object({ url: z.string(), width: z.number(), height: z.number() }), + ), + title: z.string(), + description: z.string(), + slug: z.string(), + authors: z.array( + z.object({ + name: z.string(), + role: z.string(), + avatar: z.object({ url: z.string() }), + socials: z.object({ + twitter: z.string().optional(), + github: z.string().optional(), + bluesky: z.string().optional(), + }), + }), + ), +}); +export const Changelog = z.array(ChangelogItem); + +export type Changelog = z.infer; +export type ChangelogItem = z.infer;