From 5a2501ca14e2bb84e80b80d624e11dd7bbd49e4e Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 30 Jun 2026 14:17:45 -0700 Subject: [PATCH 1/6] =?UTF-8?q?chore(landing):=20cleanup=20pass=20?= =?UTF-8?q?=E2=80=94=20size-*=20shorthand,=20drop=20redundant=20refs/memos?= =?UTF-8?q?,=20changelog=20useInfiniteQuery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - emcn: collapse 29 square h-N w-N pairs to size-* across models/integrations/blog/faq/landing-preview - landing-preview: remove redundant animationKeyRef (functional setState) + dead isDesktopRef - model-directory: hoist static provider options to a module const - auth-modal: drop useMemo over the cheap getBrandConfig() call - changelog: replace useState+fetch pagination with a co-located useInfiniteQuery hook (first page stays SSR-seeded via initialData) --- .../(landing)/blog/[slug]/share-button.tsx | 8 ++-- .../changelog-timeline/changelog-timeline.tsx | 44 ++++------------- .../use-changelog-releases.ts | 47 +++++++++++++++++++ .../components/auth-modal/auth-modal.tsx | 4 +- .../components/landing-faq/landing-faq.tsx | 2 +- .../landing-preview-home.tsx | 14 +++--- .../landing-preview-logs.tsx | 2 +- .../preview-block-node.tsx | 4 +- .../landing-preview/landing-preview.tsx | 9 +--- .../integrations/(shell)/[slug]/page.tsx | 6 +-- .../components/integration-card.tsx | 2 +- .../components/integration-icon.tsx | 6 +-- .../(shell)/[provider]/[model]/page.tsx | 2 +- .../models/(shell)/[provider]/page.tsx | 2 +- .../models/components/model-directory.tsx | 24 ++++------ .../models/components/model-primitives.tsx | 6 +-- 16 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 apps/sim/app/(landing)/changelog/components/changelog-timeline/use-changelog-releases.ts diff --git a/apps/sim/app/(landing)/blog/[slug]/share-button.tsx b/apps/sim/app/(landing)/blog/[slug]/share-button.tsx index 91f3ee0871d..5f88b24eddb 100644 --- a/apps/sim/app/(landing)/blog/[slug]/share-button.tsx +++ b/apps/sim/app/(landing)/blog/[slug]/share-button.tsx @@ -41,21 +41,21 @@ export function ShareButton({ url, title }: ShareButtonProps) { className='flex items-center gap-1.5 text-[var(--text-muted)] text-sm hover:text-[var(--text-primary)]' aria-label='Share this post' > - + Share - + {copied ? 'Copied!' : 'Copy link'} - + Share on X - + Share on LinkedIn diff --git a/apps/sim/app/(landing)/changelog/components/changelog-timeline/changelog-timeline.tsx b/apps/sim/app/(landing)/changelog/components/changelog-timeline/changelog-timeline.tsx index 69e0c1e1e87..8e3badd0539 100644 --- a/apps/sim/app/(landing)/changelog/components/changelog-timeline/changelog-timeline.tsx +++ b/apps/sim/app/(landing)/changelog/components/changelog-timeline/changelog-timeline.tsx @@ -1,11 +1,11 @@ 'use client' -import { type ReactNode, useState } from 'react' +import type { ReactNode } from 'react' import { Streamdown } from 'streamdown' import 'streamdown/styles.css' import { Avatar, AvatarFallback, AvatarImage, Chip, cn } from '@sim/emcn' -import type { ChangelogEntry, GitHubRelease } from '@/app/(landing)/changelog/types' -import { mapReleases, releasesEndpoint } from '@/app/(landing)/changelog/utils' +import { useChangelogReleases } from '@/app/(landing)/changelog/components/changelog-timeline/use-changelog-releases' +import type { ChangelogEntry } from '@/app/(landing)/changelog/types' /** * The changelog timeline - the single client leaf of the changelog page. Renders @@ -62,35 +62,9 @@ function formatDate(value: string): string { } export function ChangelogTimeline({ initialEntries }: ChangelogTimelineProps) { - const [entries, setEntries] = useState(initialEntries) - const [page, setPage] = useState(1) - const [loading, setLoading] = useState(false) - const [done, setDone] = useState(false) - - const loadMore = async () => { - if (loading || done) return - setLoading(true) - try { - const nextPage = page + 1 - // boundary-raw-fetch: external GitHub Releases API (cross-origin), not a same-origin contract - const res = await fetch(releasesEndpoint(nextPage), { - headers: { Accept: 'application/vnd.github+json' }, - }) - const releases = (await res.json()) as GitHubRelease[] - const mapped = mapReleases(releases ?? []) - - if (mapped.length === 0) { - setDone(true) - } else { - setEntries((prev) => [...prev, ...mapped]) - setPage(nextPage) - } - } catch { - setDone(true) - } finally { - setLoading(false) - } - } + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + useChangelogReleases(initialEntries) + const entries = data?.pages.flat() ?? initialEntries return (
@@ -213,10 +187,10 @@ export function ChangelogTimeline({ initialEntries }: ChangelogTimelineProps) { ) })} - {!done ? ( + {hasNextPage ? (
- - {loading ? 'Loading…' : 'Show more'} + fetchNextPage()} disabled={isFetchingNextPage}> + {isFetchingNextPage ? 'Loading…' : 'Show more'}
) : null} diff --git a/apps/sim/app/(landing)/changelog/components/changelog-timeline/use-changelog-releases.ts b/apps/sim/app/(landing)/changelog/components/changelog-timeline/use-changelog-releases.ts new file mode 100644 index 00000000000..947d1aff20e --- /dev/null +++ b/apps/sim/app/(landing)/changelog/components/changelog-timeline/use-changelog-releases.ts @@ -0,0 +1,47 @@ +'use client' + +import { useInfiniteQuery } from '@tanstack/react-query' +import type { ChangelogEntry, GitHubRelease } from '@/app/(landing)/changelog/types' +import { mapReleases, releasesEndpoint } from '@/app/(landing)/changelog/utils' + +/** + * React Query keys for the changelog release feed. Co-located with its sole + * consumer ({@link useChangelogReleases}) rather than in `hooks/queries/` + * because the feed is landing-specific and depends on the changelog feature's + * own mapper/types; keeping it here avoids a shared-hook → feature backward + * import. + */ +export const changelogKeys = { + all: ['changelog'] as const, + releases: () => [...changelogKeys.all, 'releases'] as const, +} + +async function fetchReleasesPage(page: number, signal?: AbortSignal): Promise { + // boundary-raw-fetch: external GitHub Releases API (cross-origin), not a same-origin contract + const res = await fetch(releasesEndpoint(page), { + headers: { Accept: 'application/vnd.github+json' }, + signal, + }) + const releases = (await res.json()) as GitHubRelease[] + return mapReleases(releases ?? []) +} + +/** + * Paginates GitHub releases for the changelog timeline. The server page fetches + * page 1 and passes it as `initialEntries`, seeded here via `initialData` so the + * first page stays server-rendered (no client refetch within `staleTime`); the + * "Show more" control drives `fetchNextPage`. A page that maps to zero entries + * (end of releases, or all prereleases) ends pagination — matching the prior + * load-more behavior. + */ +export function useChangelogReleases(initialEntries: ChangelogEntry[]) { + return useInfiniteQuery({ + queryKey: changelogKeys.releases(), + queryFn: ({ pageParam, signal }) => fetchReleasesPage(pageParam, signal), + initialPageParam: 1, + getNextPageParam: (lastPage, allPages) => + lastPage.length === 0 ? undefined : allPages.length + 1, + initialData: { pages: [initialEntries], pageParams: [1] }, + staleTime: 60 * 60 * 1000, + }) +} diff --git a/apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx b/apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx index bb20ed19fee..48b0c197bb1 100644 --- a/apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx +++ b/apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import { Loader, Modal, @@ -69,7 +69,7 @@ export function AuthModal({ children, defaultView = 'login', source }: AuthModal const [view, setView] = useState(defaultView) const [providerStatus, setProviderStatus] = useState(null) const [socialLoading, setSocialLoading] = useState<'github' | 'google' | 'microsoft' | null>(null) - const brand = useMemo(() => getBrandConfig(), []) + const brand = getBrandConfig() useEffect(() => { fetchProviderStatus().then(setProviderStatus) diff --git a/apps/sim/app/(landing)/components/landing-faq/landing-faq.tsx b/apps/sim/app/(landing)/components/landing-faq/landing-faq.tsx index 39a5492e216..1546588e003 100644 --- a/apps/sim/app/(landing)/components/landing-faq/landing-faq.tsx +++ b/apps/sim/app/(landing)/components/landing-faq/landing-faq.tsx @@ -61,7 +61,7 @@ export function LandingFAQ({ faqs }: LandingFAQProps) {