@@ -410,7 +408,7 @@ function MiniTablePanel() {
className='border-[var(--border-1)] border-r border-b bg-[var(--surface-1)] p-0 text-left'
>
-
+
{col.label}
diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
index 08ac2936f2d..c9b37d6f296 100644
--- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-logs/landing-preview-logs.tsx
@@ -248,7 +248,7 @@ export function LandingPreviewLogs() {
)}
>
{label}
- {sortKey === key &&
}
+ {sortKey === key &&
}
))}
diff --git a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
index 347a12e0c6d..60955b52c41 100644
--- a/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/components/landing-preview-workflow/preview-block-node.tsx
@@ -83,7 +83,7 @@ const MODEL_PROVIDER_ICONS: Array<{
{ prefix: 'o4', icon: OpenAIIcon },
{ prefix: 'claude-', icon: AnthropicIcon },
{ prefix: 'gemini-', icon: GeminiIcon },
- { prefix: 'grok-', icon: xAIIcon, size: 'h-[17px] w-[17px]' },
+ { prefix: 'grok-', icon: xAIIcon, size: 'size-[17px]' },
{ prefix: 'mistral-', icon: MistralIcon },
]
@@ -219,7 +219,7 @@ export const PreviewBlockNode = memo(function PreviewBlockNode({
{ModelIcon && (
)}
{row.value}
diff --git a/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx b/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx
index 1422ad833c5..027e29e6ddf 100644
--- a/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx
+++ b/apps/sim/app/(landing)/components/landing-preview/landing-preview.tsx
@@ -121,7 +121,6 @@ export function LandingPreview({
}: LandingPreviewProps) {
const [activeView, setActiveView] = useState(view)
const [activeWorkflowId, setActiveWorkflowId] = useState(workflowId)
- const animationKeyRef = useRef(0)
const [animationKey, setAnimationKey] = useState(0)
const [autoTypeHome, setAutoTypeHome] = useState(false)
const [isDesktop, setIsDesktop] = useState(true)
@@ -129,7 +128,6 @@ export function LandingPreview({
const demoIndexRef = useRef(0)
const demoTimerRef = useRef | null>(null)
const autoCycleActiveRef = useRef(true)
- const isDesktopRef = useRef(true)
const clearDemoTimer = useCallback(() => {
if (demoTimerRef.current) {
@@ -144,8 +142,7 @@ export function LandingPreview({
if (step.type === 'workflow' && step.workflowId) {
setActiveWorkflowId(step.workflowId)
setActiveView('workflow')
- animationKeyRef.current += 1
- setAnimationKey(animationKeyRef.current)
+ setAnimationKey((k) => k + 1)
} else if (step.type === 'home') {
setActiveView('home')
setAutoTypeHome(true)
@@ -168,7 +165,6 @@ export function LandingPreview({
useEffect(() => {
const desktop = window.matchMedia('(min-width: 1024px)').matches
- isDesktopRef.current = desktop
setIsDesktop(desktop)
if (!desktop) return
if (!autoplay) return
@@ -188,8 +184,7 @@ export function LandingPreview({
setAutoTypeHome(false)
setActiveWorkflowId(id)
setActiveView('workflow')
- animationKeyRef.current += 1
- setAnimationKey(animationKeyRef.current)
+ setAnimationKey((k) => k + 1)
},
[stopAutoCycle]
)
diff --git a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx
index 7991b535123..996a8c0cd8e 100644
--- a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx
+++ b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx
@@ -403,7 +403,7 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
name={name}
Icon={IconComponent}
className='size-12 rounded-xl border border-[var(--border-1)]'
- iconClassName='h-6 w-6'
+ iconClassName='size-6'
fallbackClassName='text-[20px]'
aria-hidden='true'
/>
@@ -745,7 +745,7 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
Icon={ToolIcon}
as='span'
className='size-6 rounded-[4px]'
- iconClassName='h-3.5 w-3.5'
+ iconClassName='size-3.5'
fallbackClassName='text-[10px]'
aria-hidden='true'
/>
@@ -935,7 +935,7 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
name={name}
Icon={IconComponent}
className='size-14 rounded-xl'
- iconClassName='h-7 w-7'
+ iconClassName='size-7'
fallbackClassName='text-[22px]'
aria-hidden='true'
/>
diff --git a/apps/sim/app/(landing)/integrations/(shell)/page.tsx b/apps/sim/app/(landing)/integrations/(shell)/page.tsx
index 8c4f1cb4713..b116122d52a 100644
--- a/apps/sim/app/(landing)/integrations/(shell)/page.tsx
+++ b/apps/sim/app/(landing)/integrations/(shell)/page.tsx
@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
+import type { SearchParams } from 'nuqs/server'
import { SITE_URL } from '@/lib/core/utils/urls'
import {
blockTypeToIconMap,
@@ -11,6 +12,7 @@ import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
import { IntegrationCard } from '@/app/(landing)/integrations/components/integration-card'
import { IntegrationGrid } from '@/app/(landing)/integrations/components/integration-grid'
import { RequestIntegrationModal } from '@/app/(landing)/integrations/components/request-integration-modal'
+import { integrationsSearchParamsCache } from '@/app/(landing)/integrations/search-params'
const allIntegrations = INTEGRATIONS
const INTEGRATION_COUNT = allIntegrations.length
@@ -87,7 +89,13 @@ export const metadata: Metadata = {
alternates: { canonical: `${baseUrl}/integrations` },
}
-export default function IntegrationsPage() {
+export default async function IntegrationsPage({
+ searchParams,
+}: {
+ searchParams: Promise
+}) {
+ await integrationsSearchParamsCache.parse(searchParams)
+
const breadcrumbJsonLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
diff --git a/apps/sim/app/(landing)/integrations/components/integration-card.tsx b/apps/sim/app/(landing)/integrations/components/integration-card.tsx
index ce57b42a7a3..646e47417b3 100644
--- a/apps/sim/app/(landing)/integrations/components/integration-card.tsx
+++ b/apps/sim/app/(landing)/integrations/components/integration-card.tsx
@@ -61,7 +61,7 @@ export function IntegrationRow({ integration, IconComponent }: IntegrationItemPr
name={name}
Icon={IconComponent}
className='size-8 shrink-0 rounded-xl border border-[var(--border-1)]'
- iconClassName='h-4 w-4'
+ iconClassName='size-4'
fallbackClassName='text-[13px]'
aria-hidden='true'
/>
diff --git a/apps/sim/app/(landing)/integrations/components/integration-grid.tsx b/apps/sim/app/(landing)/integrations/components/integration-grid.tsx
index 93464acf5c6..a78827c500f 100644
--- a/apps/sim/app/(landing)/integrations/components/integration-grid.tsx
+++ b/apps/sim/app/(landing)/integrations/components/integration-grid.tsx
@@ -1,9 +1,16 @@
'use client'
-import { useState } from 'react'
import { ChipInput, Search } from '@sim/emcn'
+import { debounce, useQueryStates } from 'nuqs'
import { blockTypeToIconMap, formatIntegrationType, type Integration } from '@/lib/integrations'
import { IntegrationRow } from '@/app/(landing)/integrations/components/integration-card'
+import {
+ integrationsParsers,
+ integrationsUrlKeys,
+} from '@/app/(landing)/integrations/search-params'
+
+/** Debounce window for writing the search term to the URL (filtering is instant). */
+const SEARCH_DEBOUNCE_MS = 300
const PILL_BASE =
'rounded-[5px] border border-[var(--border-1)] px-[9px] py-0.5 text-small text-[var(--text-primary)] transition-colors' as const
@@ -15,8 +22,11 @@ interface IntegrationGridProps {
}
export function IntegrationGrid({ integrations }: IntegrationGridProps) {
- const [query, setQuery] = useState('')
- const [activeCategory, setActiveCategory] = useState(null)
+ const [{ q: query, category }, setParams] = useQueryStates(
+ integrationsParsers,
+ integrationsUrlKeys
+ )
+ const activeCategory = category || null
const counts = new Map()
for (const i of integrations) {
@@ -51,7 +61,9 @@ export function IntegrationGrid({ integrations }: IntegrationGridProps) {
type='search'
placeholder='Search integrations, tools, or triggers…'
value={query}
- onChange={(e) => setQuery(e.target.value)}
+ onChange={(e) =>
+ setParams({ q: e.target.value }, { limitUrlUpdates: debounce(SEARCH_DEBOUNCE_MS) })
+ }
aria-label='Search integrations'
/>
@@ -60,7 +72,7 @@ export function IntegrationGrid({ integrations }: IntegrationGridProps) {