chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,87 @@
import type { ComponentType, SVGProps } from 'react'
import { memo } from 'react'
import Link from 'next/link'
import type { Integration } from '@/lib/integrations'
import { ChevronArrow } from '@/app/(landing)/components/chevron-arrow'
import { IntegrationIcon } from '@/app/(landing)/integrations/components/integration-icon'
const HOVER_BG = 'transition-colors hover:bg-[var(--surface-hover)]' as const
interface IntegrationItemProps {
integration: Integration
IconComponent?: ComponentType<SVGProps<SVGSVGElement>>
}
/**
* Featured integration card - matches blog featured post pattern.
* Used in flex rows separated by border-l dividers.
*/
export function IntegrationCard({ integration, IconComponent }: IntegrationItemProps) {
const { slug, name, description, bgColor } = integration
return (
<Link
href={`/integrations/${slug}`}
className={`group/link flex flex-1 flex-col gap-4 border-[var(--border)] border-t p-6 first:border-t-0 sm:border-t-0 sm:border-l sm:first:border-l-0 ${HOVER_BG}`}
>
<IntegrationIcon
bgColor={bgColor}
name={name}
Icon={IconComponent}
className='size-10 rounded-xl border border-[var(--border-1)]'
aria-hidden='true'
/>
<div className='flex flex-col gap-2'>
<h3 className='text-[var(--text-primary)] text-lg leading-tight tracking-[-0.01em]'>
{name}
</h3>
<p className='line-clamp-2 text-[var(--text-muted)] text-sm leading-[150%]'>
{description}
</p>
</div>
</Link>
)
}
/**
* Integration list row - matches blog remaining post pattern.
* Each row followed by an h-px divider.
*/
export const IntegrationRow = memo(function IntegrationRow({
integration,
IconComponent,
}: IntegrationItemProps) {
const { slug, name, description, bgColor } = integration
return (
<>
<Link
href={`/integrations/${slug}`}
className={`group/link flex items-center gap-4 px-6 py-4 ${HOVER_BG}`}
aria-label={`${name} integration`}
>
<IntegrationIcon
bgColor={bgColor}
name={name}
Icon={IconComponent}
className='size-8 shrink-0 rounded-xl border border-[var(--border-1)]'
iconClassName='size-4'
fallbackClassName='text-[13px]'
aria-hidden='true'
/>
<div className='flex min-w-0 flex-1 flex-col gap-0.5'>
<h3 className='text-[14px] text-[var(--text-primary)] leading-snug tracking-[-0.02em]'>
{name}
</h3>
<p className='line-clamp-1 hidden text-[12px] text-[var(--text-muted)] leading-[150%] sm:block'>
{description}
</p>
</div>
<ChevronArrow />
</Link>
<div className='h-px w-full bg-[var(--border)]' />
</>
)
})
@@ -0,0 +1,127 @@
'use client'
import { useMemo } 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
const PILL_ACTIVE = 'bg-[var(--surface-active)]' as const
const PILL_INACTIVE = 'hover:bg-[var(--surface-hover)]' as const
interface IntegrationGridProps {
integrations: readonly Integration[]
}
export function IntegrationGrid({ integrations }: IntegrationGridProps) {
const [{ q: query, category }, setParams] = useQueryStates(
integrationsParsers,
integrationsUrlKeys
)
const activeCategory = category || null
// Category facets and a per-integration lowercased search index, derived once
// from the (stable) integration list instead of rebuilt on every keystroke.
// The index keeps each searchable field as its own entry so matching stays
// identical to a per-field `includes` (no cross-field boundary matches).
const { availableCategories, searchIndex } = useMemo(() => {
const counts = new Map<string, number>()
const searchIndex = new Map<string, string[]>()
for (const i of integrations) {
if (i.integrationType) {
counts.set(i.integrationType, (counts.get(i.integrationType) || 0) + 1)
}
searchIndex.set(i.type, [
i.name.toLowerCase(),
i.description.toLowerCase(),
...i.operations.flatMap((op) => [op.name.toLowerCase(), op.description.toLowerCase()]),
...i.triggers.map((t) => t.name.toLowerCase()),
])
}
return {
availableCategories: Array.from(counts.entries())
.sort((a, b) => b[1] - a[1])
.map(([key]) => key),
searchIndex,
}
}, [integrations])
const q = query.trim().toLowerCase()
const filtered = useMemo(
() =>
integrations.filter((i) => {
if (activeCategory && i.integrationType !== activeCategory) return false
if (!q) return true
return searchIndex.get(i.type)?.some((field) => field.includes(q)) ?? false
}),
[integrations, searchIndex, q, activeCategory]
)
return (
<div>
<div className='mb-6 flex flex-col gap-4 px-6 sm:flex-row sm:items-center'>
<div className='max-w-[480px] flex-1'>
<ChipInput
icon={Search}
type='search'
placeholder='Search integrations, tools, or triggers…'
value={query}
onChange={(e) =>
setParams({ q: e.target.value }, { limitUrlUpdates: debounce(SEARCH_DEBOUNCE_MS) })
}
aria-label='Search integrations'
/>
</div>
</div>
<div className='mb-6 flex flex-wrap gap-2 px-6'>
<button
type='button'
onClick={() => setParams({ category: '' })}
className={`${PILL_BASE} ${activeCategory === null ? PILL_ACTIVE : PILL_INACTIVE}`}
>
All
</button>
{availableCategories.map((cat) => (
<button
key={cat}
type='button'
onClick={() => setParams({ category: activeCategory === cat ? '' : cat })}
className={`${PILL_BASE} ${activeCategory === cat ? PILL_ACTIVE : PILL_INACTIVE}`}
>
{formatIntegrationType(cat)}
</button>
))}
</div>
<div className='h-px w-full bg-[var(--border)]' />
{filtered.length === 0 ? (
<p className='py-12 text-center text-[15px] text-[var(--text-muted)]'>
No integrations found
{query ? <> for &ldquo;{query}&rdquo;</> : null}
{activeCategory ? <> in {formatIntegrationType(activeCategory)}</> : null}
</p>
) : (
<div>
{filtered.map((integration) => (
<IntegrationRow
key={integration.type}
integration={integration}
IconComponent={blockTypeToIconMap[integration.type]}
/>
))}
</div>
)}
</div>
)
}
@@ -0,0 +1,51 @@
import type { ComponentType, ElementType, HTMLAttributes, SVGProps } from 'react'
import { cn } from '@sim/emcn'
import { getTileIconColorClass } from '@/blocks/icon-color'
interface IntegrationIconProps extends HTMLAttributes<HTMLElement> {
bgColor: string
/** Integration name - used for the fallback initial letter. */
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: `size-10 rounded-xl` */
className?: string
/** 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
/** Rendered HTML element. Default: `div` */
as?: ElementType
}
/**
* Colored icon box used across integration listing and detail pages.
* Renders an integration icon over a brand-colored background, falling back
* to the integration's initial letter when no icon is available.
*/
export function IntegrationIcon({
bgColor,
name,
Icon,
className,
iconClassName = 'size-5',
fallbackClassName = 'text-[15px]',
as: Tag = 'div',
...rest
}: IntegrationIconProps) {
return (
<Tag
className={cn('flex shrink-0 items-center justify-center', className)}
style={{ background: bgColor }}
{...rest}
>
{Icon ? (
<Icon className={cn(iconClassName, getTileIconColorClass(bgColor))} />
) : (
<span className={cn('leading-none', getTileIconColorClass(bgColor), fallbackClassName)}>
{name.charAt(0)}
</span>
)}
</Tag>
)
}
@@ -0,0 +1,155 @@
'use client'
import { useCallback, useState } from 'react'
import {
Chip,
ChipModal,
ChipModalBody,
ChipModalError,
ChipModalField,
ChipModalFooter,
ChipModalHeader,
} from '@sim/emcn'
import { requestJson } from '@/lib/api/client/request'
import { integrationRequestContract } from '@/lib/api/contracts/common'
type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error'
export function RequestIntegrationModal() {
const [open, setOpen] = useState(false)
const [status, setStatus] = useState<SubmitStatus>('idle')
const [integrationName, setIntegrationName] = useState('')
const [email, setEmail] = useState('')
const [useCase, setUseCase] = useState('')
const resetForm = useCallback(() => {
setIntegrationName('')
setEmail('')
setUseCase('')
setStatus('idle')
}, [])
const handleOpenChange = useCallback(
(nextOpen: boolean) => {
setOpen(nextOpen)
if (!nextOpen) resetForm()
},
[resetForm]
)
const handleSubmit = useCallback(async () => {
if (!integrationName.trim() || !email.trim()) return
setStatus('submitting')
try {
await requestJson(integrationRequestContract, {
body: {
integrationName: integrationName.trim(),
email: email.trim(),
useCase: useCase.trim() || undefined,
},
})
setStatus('success')
setTimeout(() => setOpen(false), 1500)
} catch {
setStatus('error')
}
}, [integrationName, email, useCase])
const canSubmit = integrationName.trim() && email.trim() && status === 'idle'
return (
<>
<Chip className='border border-[var(--border-1)]' onClick={() => setOpen(true)}>
Request an integration
</Chip>
<ChipModal open={open} onOpenChange={handleOpenChange} srTitle='Request an Integration'>
<ChipModalHeader onClose={() => handleOpenChange(false)}>
Request an Integration
</ChipModalHeader>
<ChipModalBody>
{status === 'success' ? (
<div className='flex flex-col items-center gap-3 py-6 text-center'>
<div className='flex size-10 items-center justify-center rounded-full bg-[var(--brand-accent)]/10'>
<svg
className='size-5 text-[var(--brand-accent)]'
viewBox='0 0 24 24'
fill='none'
stroke='currentColor'
strokeWidth={2}
strokeLinecap='round'
strokeLinejoin='round'
>
<polyline points='20 6 9 17 4 12' />
</svg>
</div>
<p className='text-[14px] text-[var(--text-primary)]'>
Request submitted. We&apos;ll follow up at <span>{email}</span>.
</p>
</div>
) : (
<>
<ChipModalField
type='input'
title='Integration name'
value={integrationName}
onChange={(value) => setIntegrationName(value)}
placeholder='e.g. Stripe, HubSpot, Snowflake'
maxLength={200}
autoComplete='off'
required
/>
<ChipModalField
type='email'
title='Your email'
value={email}
onChange={(value) => setEmail(value)}
placeholder='you@company.com'
autoComplete='email'
required
/>
<ChipModalField
type='textarea'
title={
<>
Use case <span className='text-[var(--text-subtle)]'>(optional)</span>
</>
}
value={useCase}
onChange={(value) => setUseCase(value)}
placeholder='What would you automate with this integration?'
rows={3}
maxLength={2000}
/>
{status === 'error' && (
<ChipModalError>Something went wrong. Please try again.</ChipModalError>
)}
</>
)}
</ChipModalBody>
{status === 'success' ? (
<ChipModalFooter
onCancel={() => handleOpenChange(false)}
primaryAction={{ label: 'Done', onClick: () => handleOpenChange(false) }}
/>
) : (
<ChipModalFooter
onCancel={() => setOpen(false)}
cancelDisabled={status === 'submitting'}
primaryAction={{
label: status === 'submitting' ? 'Submitting...' : 'Submit request',
onClick: handleSubmit,
disabled: !canSubmit && status !== 'error',
}}
/>
)}
</ChipModal>
</>
)
}