Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/sim/app/(landing)/blog/[slug]/share-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
>
<Share2 className='h-4 w-4' />
<Share2 className='size-4' />
<span>Share</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem onSelect={handleCopyLink}>
<Duplicate className='h-4 w-4' />
<Duplicate className='size-4' />
{copied ? 'Copied!' : 'Copy link'}
</DropdownMenuItem>
<DropdownMenuItem onSelect={handleShareTwitter}>
<XIcon className='h-4 w-4' />
<XIcon className='size-4' />
Share on X
</DropdownMenuItem>
<DropdownMenuItem onSelect={handleShareLinkedIn}>
<LinkedInIcon className='h-4 w-4' />
<LinkedInIcon className='size-4' />
Share on LinkedIn
</DropdownMenuItem>
</DropdownMenuContent>
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useEffect, useMemo, useState } from 'react'
import { useEffect, useState } from 'react'
import {
Loader,
Modal,
Expand Down Expand Up @@ -69,7 +69,7 @@ export function AuthModal({ children, defaultView = 'login', source }: AuthModal
const [view, setView] = useState<AuthView>(defaultView)
const [providerStatus, setProviderStatus] = useState<ProviderStatus | null>(null)
const [socialLoading, setSocialLoading] = useState<'github' | 'google' | 'microsoft' | null>(null)
const brand = useMemo(() => getBrandConfig(), [])
const brand = getBrandConfig()

useEffect(() => {
fetchProviderStatus().then(setProviderStatus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function LandingFAQ({ faqs }: LandingFAQProps) {
</span>
<ChevronDown
className={cn(
'h-3 w-3 shrink-0 text-[var(--text-muted)] transition-transform duration-200',
'size-3 shrink-0 text-[var(--text-muted)] transition-transform duration-200',
isOpen ? 'rotate-180' : 'rotate-0'
)}
aria-hidden='true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({
onClick={() => setToolsExpanded((p) => !p)}
className='flex cursor-pointer items-center gap-2'
>
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
<Blimp className='h-[16px] w-[16px] text-[var(--text-icon)]' />
<div className='flex size-[16px] flex-shrink-0 items-center justify-center'>
<Blimp className='size-[16px] text-[var(--text-icon)]' />
</div>
<span className='text-[var(--text-body)] text-sm'>Sim</span>
<ChevronDown
Expand All @@ -218,9 +218,7 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({
<div className='overflow-hidden'>
<div className='flex flex-col gap-1.5 pt-0.5'>
<ToolCallRow
icon={
<Table className='h-[15px] w-[15px] text-[var(--text-muted)]' />
}
icon={<Table className='size-[15px] text-[var(--text-muted)]' />}
title='Read Customer Leads'
/>
</div>
Expand Down Expand Up @@ -343,7 +341,7 @@ export const LandingPreviewHome = memo(function LandingPreviewHome({
function ToolCallRow({ icon, title }: { icon: React.ReactNode; title: string }) {
return (
<div className='flex items-center gap-[8px] pl-[24px]'>
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>{icon}</div>
<div className='flex size-[16px] flex-shrink-0 items-center justify-center'>{icon}</div>
<span className='text-[13px] text-[var(--text-muted)]'>{title}</span>
</div>
)
Expand Down Expand Up @@ -390,7 +388,7 @@ function MiniTablePanel() {
return (
<div className='flex h-full w-full flex-col bg-[var(--surface-2)]'>
<div className='flex items-center gap-2 border-[var(--border)] border-b px-3 py-2'>
<Table className='h-[14px] w-[14px] text-[var(--text-icon)]' />
<Table className='size-[14px] text-[var(--text-icon)]' />
<span className='font-medium text-[var(--text-primary)] text-sm'>Customer Leads</span>
</div>
<div className='min-h-0 flex-1 overflow-auto'>
Expand All @@ -410,7 +408,7 @@ function MiniTablePanel() {
className='border-[var(--border-1)] border-r border-b bg-[var(--surface-1)] p-0 text-left'
>
<div className='flex items-center gap-1 px-2 py-1.5'>
<Icon className='h-3 w-3 shrink-0 text-[var(--text-icon)]' />
<Icon className='size-3 shrink-0 text-[var(--text-icon)]' />
<span className='font-medium text-[11px] text-[var(--text-primary)]'>
{col.label}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export function LandingPreviewLogs() {
)}
>
{label}
{sortKey === key && <ArrowUpDown className='h-[10px] w-[10px] opacity-60' />}
{sortKey === key && <ArrowUpDown className='size-[10px] opacity-60' />}
</button>
</th>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
]

Expand Down Expand Up @@ -219,7 +219,7 @@ export const PreviewBlockNode = memo(function PreviewBlockNode({
<span className='flex min-w-0 flex-1 items-center justify-end gap-2 font-normal text-[14px] text-[var(--text-primary)]'>
{ModelIcon && (
<ModelIcon
className={`inline-block flex-shrink-0 text-[var(--text-primary)] ${modelEntry.size ?? 'h-[14px] w-[14px]'}`}
className={`inline-block flex-shrink-0 text-[var(--text-primary)] ${modelEntry.size ?? 'size-[14px]'}`}
/>
)}
<span className='truncate'>{row.value}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,13 @@ export function LandingPreview({
}: LandingPreviewProps) {
const [activeView, setActiveView] = useState<SidebarView>(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)

const demoIndexRef = useRef(0)
const demoTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const autoCycleActiveRef = useRef(true)
const isDesktopRef = useRef(true)

const clearDemoTimer = useCallback(() => {
if (demoTimerRef.current) {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -188,8 +184,7 @@ export function LandingPreview({
setAutoTypeHome(false)
setActiveWorkflowId(id)
setActiveView('workflow')
animationKeyRef.current += 1
setAnimationKey(animationKeyRef.current)
setAnimationKey((k) => k + 1)
},
[stopAutoCycle]
)
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
/>
Expand Down Expand Up @@ -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'
/>
Expand Down Expand Up @@ -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'
/>
Expand Down
10 changes: 9 additions & 1 deletion apps/sim/app/(landing)/integrations/(shell)/page.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -87,7 +89,13 @@ export const metadata: Metadata = {
alternates: { canonical: `${baseUrl}/integrations` },
}

export default function IntegrationsPage() {
export default async function IntegrationsPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
await integrationsSearchParamsCache.parse(searchParams)

const breadcrumbJsonLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,8 +22,11 @@ interface IntegrationGridProps {
}

export function IntegrationGrid({ integrations }: IntegrationGridProps) {
const [query, setQuery] = useState('')
const [activeCategory, setActiveCategory] = useState<string | null>(null)
const [{ q: query, category }, setParams] = useQueryStates(
integrationsParsers,
integrationsUrlKeys
)
const activeCategory = category || null

const counts = new Map<string, number>()
for (const i of integrations) {
Expand Down Expand Up @@ -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'
/>
</div>
Expand All @@ -60,7 +72,7 @@ export function IntegrationGrid({ integrations }: IntegrationGridProps) {
<div className='mb-6 flex flex-wrap gap-2 px-6'>
<button
type='button'
onClick={() => setActiveCategory(null)}
onClick={() => setParams({ category: '' })}
className={`${PILL_BASE} ${activeCategory === null ? PILL_ACTIVE : PILL_INACTIVE}`}
>
All
Expand All @@ -69,7 +81,7 @@ export function IntegrationGrid({ integrations }: IntegrationGridProps) {
<button
key={cat}
type='button'
onClick={() => setActiveCategory(activeCategory === cat ? null : cat)}
onClick={() => setParams({ category: activeCategory === cat ? '' : cat })}
className={`${PILL_BASE} ${activeCategory === cat ? PILL_ACTIVE : PILL_INACTIVE}`}
>
{formatIntegrationType(cat)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ interface IntegrationIconProps extends HTMLAttributes<HTMLElement> {
name: string
/** Optional icon component. When absent, renders the first letter of `name`. */
Icon?: ComponentType<SVGProps<SVGSVGElement>> | null
/** Tailwind size + rounding classes for the container. Default: `h-10 w-10 rounded-xl` */
/** Tailwind size + rounding classes for the container. Default: `size-10 rounded-xl` */
className?: string
/** Tailwind size classes for the icon SVG. Default: `h-5 w-5` */
/** Tailwind size classes for the icon SVG. Default: `size-5` */
iconClassName?: string
/** Tailwind text-size class for the fallback letter. Default: `text-[15px]` */
fallbackClassName?: string
Expand All @@ -28,7 +28,7 @@ export function IntegrationIcon({
name,
Icon,
className,
iconClassName = 'h-5 w-5',
iconClassName = 'size-5',
fallbackClassName = 'text-[15px]',
as: Tag = 'div',
...rest
Expand Down
30 changes: 30 additions & 0 deletions apps/sim/app/(landing)/integrations/search-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createSearchParamsCache, parseAsString } from 'nuqs/server'

/**
* Co-located, typed URL query params for the integrations catalog. Shared by the
* client grid (`useQueryStates`) and the server page (`integrationsSearchParamsCache`)
* so the filtered view is server-rendered for shareable, crawlable `?category=`/`?q=`
* URLs — the same SSR pattern the blog index uses.
*
* - `q` is the search filter; its URL write is debounced on the setter, never
* written per keystroke.
* - `category` filters by integration type (`''` = all).
*/
export const integrationsParsers = {
q: parseAsString.withDefault(''),
category: parseAsString.withDefault(''),
}

/** Filter/search view-state: clean URLs, no back-stack churn. */
export const integrationsUrlKeys = {
history: 'replace',
clearOnDefault: true,
} as const

/**
* Parsing this in the page's server component opts the route into dynamic
* rendering, which populates `useSearchParams` during the server render — so the
* client grid's `useQueryStates` filters server-side and the initial HTML ships
* the filtered catalog (crawlable, shareable), with no post-hydration swap.
*/
export const integrationsSearchParamsCache = createSearchParamsCache(integrationsParsers)
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default async function ModelPage({
<ProviderIcon
provider={provider}
className='size-16 rounded-xl'
iconClassName='h-8 w-8'
iconClassName='size-8'
/>
<div>
<p className='mb-0.5 text-[var(--text-muted)] text-xs uppercase tracking-[0.1em]'>
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/models/(shell)/[provider]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export default async function ProviderModelsPage({
<ProviderIcon
provider={provider}
className='size-12 rounded-xl'
iconClassName='h-6 w-6'
iconClassName='size-6'
/>
<h1
id='provider-heading'
Expand Down
10 changes: 9 additions & 1 deletion apps/sim/app/(landing)/models/(shell)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import type { SearchParams } from 'nuqs/server'
import { SITE_URL } from '@/lib/core/utils/urls'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
import { ModelComparisonCharts } from '@/app/(landing)/models/components/model-comparison-charts'
Expand All @@ -7,6 +8,7 @@ import {
FeaturedModelCard,
FeaturedProviderCard,
} from '@/app/(landing)/models/components/model-primitives'
import { modelsSearchParamsCache } from '@/app/(landing)/models/search-params'
import {
ALL_CATALOG_MODELS,
getPricingBounds,
Expand Down Expand Up @@ -80,7 +82,13 @@ export const metadata: Metadata = {
},
}

export default function ModelsPage() {
export default async function ModelsPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
await modelsSearchParamsCache.parse(searchParams)

const flatModels = MODEL_PROVIDERS_WITH_CATALOGS.flatMap((provider) =>
provider.models.map((model) => ({ provider, model }))
)
Expand Down
Loading
Loading