diff --git a/apps/sim/app/(auth)/components/status-page-layout.tsx b/apps/sim/app/(auth)/components/status-page-layout.tsx
index 7e353e0f19d..9d62e776e44 100644
--- a/apps/sim/app/(auth)/components/status-page-layout.tsx
+++ b/apps/sim/app/(auth)/components/status-page-layout.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import type { ReactNode } from 'react'
import AuthBackground from '@/app/(auth)/components/auth-background'
import Navbar from '@/app/(home)/components/navbar/navbar'
diff --git a/apps/sim/app/(home)/components/enterprise/components/access-control-panel.tsx b/apps/sim/app/(home)/components/enterprise/components/access-control-panel.tsx
new file mode 100644
index 00000000000..26d648540a0
--- /dev/null
+++ b/apps/sim/app/(home)/components/enterprise/components/access-control-panel.tsx
@@ -0,0 +1,188 @@
+'use client'
+
+import { useRef, useState } from 'react'
+import { motion, useInView } from 'framer-motion'
+import { PROVIDER_DEFINITIONS } from '@/providers/models'
+
+interface PermissionFeature {
+ name: string
+ key: string
+ defaultEnabled: boolean
+ providerId?: string
+}
+
+interface PermissionCategory {
+ label: string
+ color: string
+ features: PermissionFeature[]
+}
+
+const PERMISSION_CATEGORIES: PermissionCategory[] = [
+ {
+ label: 'Providers',
+ color: '#FA4EDF',
+ features: [
+ { key: 'openai', name: 'OpenAI', defaultEnabled: true, providerId: 'openai' },
+ { key: 'anthropic', name: 'Anthropic', defaultEnabled: true, providerId: 'anthropic' },
+ { key: 'google', name: 'Google', defaultEnabled: false, providerId: 'google' },
+ { key: 'xai', name: 'xAI', defaultEnabled: true, providerId: 'xai' },
+ ],
+ },
+ {
+ label: 'Workspace',
+ color: '#2ABBF8',
+ features: [
+ { key: 'knowledge-base', name: 'Knowledge Base', defaultEnabled: true },
+ { key: 'tables', name: 'Tables', defaultEnabled: true },
+ { key: 'copilot', name: 'Copilot', defaultEnabled: false },
+ { key: 'environment', name: 'Environment', defaultEnabled: false },
+ ],
+ },
+ {
+ label: 'Tools',
+ color: '#33C482',
+ features: [
+ { key: 'mcp-tools', name: 'MCP Tools', defaultEnabled: true },
+ { key: 'custom-tools', name: 'Custom Tools', defaultEnabled: false },
+ { key: 'skills', name: 'Skills', defaultEnabled: true },
+ { key: 'invitations', name: 'Invitations', defaultEnabled: true },
+ ],
+ },
+]
+
+const INITIAL_ACCESS_STATE = Object.fromEntries(
+ PERMISSION_CATEGORIES.flatMap((category) =>
+ category.features.map((feature) => [feature.key, feature.defaultEnabled])
+ )
+)
+
+function CheckboxIcon({ checked, color }: { checked: boolean; color: string }) {
+ return (
+
+ )
+}
+
+function ProviderPreviewIcon({ providerId }: { providerId?: string }) {
+ if (!providerId) return null
+
+ const ProviderIcon = PROVIDER_DEFINITIONS[providerId]?.icon
+ if (!ProviderIcon) return null
+
+ return (
+
+ )
+}
+
+export function AccessControlPanel() {
+ const ref = useRef(null)
+ const isInView = useInView(ref, { once: true, margin: '-40px' })
+ const [accessState, setAccessState] = useState>(INITIAL_ACCESS_STATE)
+
+ return (
+
+
+ {PERMISSION_CATEGORIES.map((category, catIdx) => {
+ const offsetBefore = PERMISSION_CATEGORIES.slice(0, catIdx).reduce(
+ (sum, c) => sum + c.features.length,
+ 0
+ )
+
+ return (
+
0 ? 'mt-4' : ''}>
+
+ {category.label}
+
+
+ {category.features.map((feature, featIdx) => {
+ const enabled = accessState[feature.key]
+
+ return (
+
+ setAccessState((prev) => ({ ...prev, [feature.key]: !prev[feature.key] }))
+ }
+ whileTap={{ scale: 0.98 }}
+ >
+
+
+
+ {feature.name}
+
+
+ )
+ })}
+
+
+ )
+ })}
+
+
+ {/* Desktop -- categorized grid */}
+
+ {PERMISSION_CATEGORIES.map((category, catIdx) => (
+
0 ? 'mt-4' : ''}>
+
+ {category.label}
+
+
+ {category.features.map((feature, featIdx) => {
+ const enabled = accessState[feature.key]
+ const currentIndex =
+ PERMISSION_CATEGORIES.slice(0, catIdx).reduce(
+ (sum, c) => sum + c.features.length,
+ 0
+ ) + featIdx
+
+ return (
+
+ setAccessState((prev) => ({ ...prev, [feature.key]: !prev[feature.key] }))
+ }
+ whileTap={{ scale: 0.98 }}
+ >
+
+
+
+ {feature.name}
+
+
+ )
+ })}
+
+
+ ))}
+
+
+ )
+}
diff --git a/apps/sim/app/(home)/components/enterprise/components/audit-log-preview.tsx b/apps/sim/app/(home)/components/enterprise/components/audit-log-preview.tsx
new file mode 100644
index 00000000000..bbf703426e5
--- /dev/null
+++ b/apps/sim/app/(home)/components/enterprise/components/audit-log-preview.tsx
@@ -0,0 +1,226 @@
+'use client'
+
+import { useEffect, useRef, useState } from 'react'
+import { AnimatePresence, motion } from 'framer-motion'
+
+/** Consistent color per actor -- same pattern as Collaboration section cursors. */
+const ACTOR_COLORS: Record = {
+ 'Sarah K.': '#2ABBF8',
+ 'Sid G.': '#33C482',
+ 'Theo L.': '#FA4EDF',
+ 'Abhay K.': '#FFCC02',
+ 'Danny S.': '#FF6B35',
+}
+
+/** Left accent bar opacity by recency -- newest is brightest. */
+const ACCENT_OPACITIES = [0.75, 0.5, 0.35, 0.22, 0.12, 0.05] as const
+
+interface LogEntry {
+ id: number
+ actor: string
+ /** Matches the `description` field stored by recordAudit() */
+ description: string
+ resourceType: string
+ /** Unix ms timestamp of when this entry was "received" */
+ insertedAt: number
+}
+
+function formatTimeAgo(insertedAt: number): string {
+ const elapsed = Date.now() - insertedAt
+ if (elapsed < 8_000) return 'just now'
+ if (elapsed < 60_000) return `${Math.floor(elapsed / 1000)}s ago`
+ return `${Math.floor(elapsed / 60_000)}m ago`
+}
+
+/**
+ * Entry templates using real description strings from the actual recordAudit()
+ * calls across the codebase (e.g. `Added BYOK key for openai`,
+ * `Invited alex@acme.com to workspace as member`).
+ */
+const ENTRY_TEMPLATES: Omit[] = [
+ { actor: 'Sarah K.', description: 'Deployed workflow "Email Triage"', resourceType: 'workflow' },
+ {
+ actor: 'Sid G.',
+ description: 'Invited alex@acme.com to workspace as member',
+ resourceType: 'member',
+ },
+ { actor: 'Theo L.', description: 'Added BYOK key for openai', resourceType: 'byok_key' },
+ { actor: 'Sarah K.', description: 'Created workflow "Invoice Parser"', resourceType: 'workflow' },
+ {
+ actor: 'Abhay K.',
+ description: 'Created permission group "Engineering"',
+ resourceType: 'permission_group',
+ },
+ { actor: 'Danny S.', description: 'Created API key "Production Key"', resourceType: 'api_key' },
+ {
+ actor: 'Theo L.',
+ description: 'Changed permissions for sam@acme.com to editor',
+ resourceType: 'member',
+ },
+ { actor: 'Sarah K.', description: 'Uploaded file "Q3_Report.pdf"', resourceType: 'file' },
+ {
+ actor: 'Sid G.',
+ description: 'Created credential set "Prod Keys"',
+ resourceType: 'credential_set',
+ },
+ {
+ actor: 'Abhay K.',
+ description: 'Created knowledge base "Internal Docs"',
+ resourceType: 'knowledge_base',
+ },
+ { actor: 'Danny S.', description: 'Updated environment variables', resourceType: 'environment' },
+ {
+ actor: 'Sarah K.',
+ description: 'Added tool "search_web" to MCP server',
+ resourceType: 'mcp_server',
+ },
+ { actor: 'Sid G.', description: 'Created webhook "Stripe Payment"', resourceType: 'webhook' },
+ { actor: 'Theo L.', description: 'Deployed chat "Support Assistant"', resourceType: 'chat' },
+ { actor: 'Abhay K.', description: 'Created table "Lead Tracker"', resourceType: 'table' },
+ { actor: 'Danny S.', description: 'Revoked API key "Staging Key"', resourceType: 'api_key' },
+ {
+ actor: 'Sarah K.',
+ description: 'Duplicated workflow "Data Enrichment"',
+ resourceType: 'workflow',
+ },
+ {
+ actor: 'Sid G.',
+ description: 'Removed member theo@acme.com from workspace',
+ resourceType: 'member',
+ },
+ {
+ actor: 'Theo L.',
+ description: 'Updated knowledge base "Product Docs"',
+ resourceType: 'knowledge_base',
+ },
+ { actor: 'Abhay K.', description: 'Created folder "Finance Workflows"', resourceType: 'folder' },
+ {
+ actor: 'Danny S.',
+ description: 'Uploaded document "onboarding-guide.pdf"',
+ resourceType: 'document',
+ },
+ {
+ actor: 'Sarah K.',
+ description: 'Updated credential set "Prod Keys"',
+ resourceType: 'credential_set',
+ },
+ {
+ actor: 'Sid G.',
+ description: 'Added member abhay@acme.com to permission group "Engineering"',
+ resourceType: 'permission_group',
+ },
+ { actor: 'Theo L.', description: 'Locked workflow "Customer Sync"', resourceType: 'workflow' },
+]
+
+const INITIAL_OFFSETS_MS = [0, 20_000, 75_000, 180_000, 360_000, 600_000]
+
+interface AuditRowProps {
+ entry: LogEntry
+ index: number
+}
+
+function AuditRow({ entry, index }: AuditRowProps) {
+ const color = ACTOR_COLORS[entry.actor] ?? '#F6F6F6'
+ const accentOpacity = ACCENT_OPACITIES[index] ?? 0.04
+ const timeAgo = formatTimeAgo(entry.insertedAt)
+
+ return (
+
+ {/* Left accent bar -- brightness encodes recency */}
+
+
+ {/* Row content */}
+
+ {/* Actor avatar */}
+
+
+ {entry.actor[0]}
+
+
+
+ {/* Time */}
+
+ {timeAgo}
+
+
+
+ {entry.actor}
+
+ ·
+ {entry.description}
+
+
+
+
+ )
+}
+
+export function AuditLogPreview() {
+ const counterRef = useRef(ENTRY_TEMPLATES.length)
+ const templateIndexRef = useRef(6 % ENTRY_TEMPLATES.length)
+
+ const now = Date.now()
+ const [entries, setEntries] = useState(() =>
+ ENTRY_TEMPLATES.slice(0, 6).map((t, i) => ({
+ ...t,
+ id: i,
+ insertedAt: now - INITIAL_OFFSETS_MS[i],
+ }))
+ )
+ const [, tick] = useState(0)
+
+ useEffect(() => {
+ const addInterval = setInterval(() => {
+ const template = ENTRY_TEMPLATES[templateIndexRef.current]
+ templateIndexRef.current = (templateIndexRef.current + 1) % ENTRY_TEMPLATES.length
+
+ setEntries((prev) => [
+ { ...template, id: counterRef.current++, insertedAt: Date.now() },
+ ...prev.slice(0, 5),
+ ])
+ }, 2600)
+
+ // Refresh time labels every 5s so "just now" ages to "Xs ago"
+ const tickInterval = setInterval(() => tick((n) => n + 1), 5_000)
+
+ return () => {
+ clearInterval(addInterval)
+ clearInterval(tickInterval)
+ }
+ }, [])
+
+ return (
+
+
+ {entries.map((entry, index) => (
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/apps/sim/app/(home)/components/enterprise/enterprise.tsx b/apps/sim/app/(home)/components/enterprise/enterprise.tsx
index c6b8e9916c2..08aab8bbf5b 100644
--- a/apps/sim/app/(home)/components/enterprise/enterprise.tsx
+++ b/apps/sim/app/(home)/components/enterprise/enterprise.tsx
@@ -12,127 +12,14 @@
* - `` checklist of features (SSO, RBAC, audit logs, SLA, on-premise deployment)
* as an atomic answer block for "What enterprise features does Sim offer?".
*/
-'use client'
-import { useEffect, useRef, useState } from 'react'
-import { AnimatePresence, motion, useInView } from 'framer-motion'
import Image from 'next/image'
import Link from 'next/link'
import { Badge, ChevronDown } from '@/components/emcn'
import { Lock } from '@/components/emcn/icons'
import { GithubIcon } from '@/components/icons'
-import { PROVIDER_DEFINITIONS } from '@/providers/models'
-
-/** Consistent color per actor — same pattern as Collaboration section cursors. */
-const ACTOR_COLORS: Record = {
- 'Sarah K.': '#2ABBF8',
- 'Sid G.': '#33C482',
- 'Theo L.': '#FA4EDF',
- 'Abhay K.': '#FFCC02',
- 'Danny S.': '#FF6B35',
-}
-
-/** Left accent bar opacity by recency — newest is brightest. */
-const ACCENT_OPACITIES = [0.75, 0.5, 0.35, 0.22, 0.12, 0.05] as const
-
-interface LogEntry {
- id: number
- actor: string
- /** Matches the `description` field stored by recordAudit() */
- description: string
- resourceType: string
- /** Unix ms timestamp of when this entry was "received" */
- insertedAt: number
-}
-
-function formatTimeAgo(insertedAt: number): string {
- const elapsed = Date.now() - insertedAt
- if (elapsed < 8_000) return 'just now'
- if (elapsed < 60_000) return `${Math.floor(elapsed / 1000)}s ago`
- return `${Math.floor(elapsed / 60_000)}m ago`
-}
-
-/**
- * Entry templates using real description strings from the actual recordAudit()
- * calls across the codebase (e.g. `Added BYOK key for openai`,
- * `Invited alex@acme.com to workspace as member`).
- */
-const ENTRY_TEMPLATES: Omit[] = [
- { actor: 'Sarah K.', description: 'Deployed workflow "Email Triage"', resourceType: 'workflow' },
- {
- actor: 'Sid G.',
- description: 'Invited alex@acme.com to workspace as member',
- resourceType: 'member',
- },
- { actor: 'Theo L.', description: 'Added BYOK key for openai', resourceType: 'byok_key' },
- { actor: 'Sarah K.', description: 'Created workflow "Invoice Parser"', resourceType: 'workflow' },
- {
- actor: 'Abhay K.',
- description: 'Created permission group "Engineering"',
- resourceType: 'permission_group',
- },
- { actor: 'Danny S.', description: 'Created API key "Production Key"', resourceType: 'api_key' },
- {
- actor: 'Theo L.',
- description: 'Changed permissions for sam@acme.com to editor',
- resourceType: 'member',
- },
- { actor: 'Sarah K.', description: 'Uploaded file "Q3_Report.pdf"', resourceType: 'file' },
- {
- actor: 'Sid G.',
- description: 'Created credential set "Prod Keys"',
- resourceType: 'credential_set',
- },
- {
- actor: 'Abhay K.',
- description: 'Created knowledge base "Internal Docs"',
- resourceType: 'knowledge_base',
- },
- { actor: 'Danny S.', description: 'Updated environment variables', resourceType: 'environment' },
- {
- actor: 'Sarah K.',
- description: 'Added tool "search_web" to MCP server',
- resourceType: 'mcp_server',
- },
- { actor: 'Sid G.', description: 'Created webhook "Stripe Payment"', resourceType: 'webhook' },
- { actor: 'Theo L.', description: 'Deployed chat "Support Assistant"', resourceType: 'chat' },
- { actor: 'Abhay K.', description: 'Created table "Lead Tracker"', resourceType: 'table' },
- { actor: 'Danny S.', description: 'Revoked API key "Staging Key"', resourceType: 'api_key' },
- {
- actor: 'Sarah K.',
- description: 'Duplicated workflow "Data Enrichment"',
- resourceType: 'workflow',
- },
- {
- actor: 'Sid G.',
- description: 'Removed member theo@acme.com from workspace',
- resourceType: 'member',
- },
- {
- actor: 'Theo L.',
- description: 'Updated knowledge base "Product Docs"',
- resourceType: 'knowledge_base',
- },
- { actor: 'Abhay K.', description: 'Created folder "Finance Workflows"', resourceType: 'folder' },
- {
- actor: 'Danny S.',
- description: 'Uploaded document "onboarding-guide.pdf"',
- resourceType: 'document',
- },
- {
- actor: 'Sarah K.',
- description: 'Updated credential set "Prod Keys"',
- resourceType: 'credential_set',
- },
- {
- actor: 'Sid G.',
- description: 'Added member abhay@acme.com to permission group "Engineering"',
- resourceType: 'permission_group',
- },
- { actor: 'Theo L.', description: 'Locked workflow "Customer Sync"', resourceType: 'workflow' },
-]
-
-const INITIAL_OFFSETS_MS = [0, 20_000, 75_000, 180_000, 360_000, 600_000]
+import { AccessControlPanel } from '@/app/(home)/components/enterprise/components/access-control-panel'
+import { AuditLogPreview } from '@/app/(home)/components/enterprise/components/audit-log-preview'
const MARQUEE_KEYFRAMES = `
@keyframes marquee {
@@ -161,300 +48,6 @@ const FEATURE_TAGS = [
'Audit Logs',
] as const
-interface AuditRowProps {
- entry: LogEntry
- index: number
-}
-
-function AuditRow({ entry, index }: AuditRowProps) {
- const color = ACTOR_COLORS[entry.actor] ?? '#F6F6F6'
- const accentOpacity = ACCENT_OPACITIES[index] ?? 0.04
- const timeAgo = formatTimeAgo(entry.insertedAt)
-
- return (
-
- {/* Left accent bar — brightness encodes recency */}
-
-
- {/* Row content */}
-
- {/* Actor avatar */}
-
-
- {entry.actor[0]}
-
-
-
- {/* Time */}
-
- {timeAgo}
-
-
-
- {entry.actor}
-
- ·
- {entry.description}
-
-
-
-
- )
-}
-
-function AuditLogPreview() {
- const counterRef = useRef(ENTRY_TEMPLATES.length)
- const templateIndexRef = useRef(6 % ENTRY_TEMPLATES.length)
-
- const now = Date.now()
- const [entries, setEntries] = useState(() =>
- ENTRY_TEMPLATES.slice(0, 6).map((t, i) => ({
- ...t,
- id: i,
- insertedAt: now - INITIAL_OFFSETS_MS[i],
- }))
- )
- const [, tick] = useState(0)
-
- useEffect(() => {
- const addInterval = setInterval(() => {
- const template = ENTRY_TEMPLATES[templateIndexRef.current]
- templateIndexRef.current = (templateIndexRef.current + 1) % ENTRY_TEMPLATES.length
-
- setEntries((prev) => [
- { ...template, id: counterRef.current++, insertedAt: Date.now() },
- ...prev.slice(0, 5),
- ])
- }, 2600)
-
- // Refresh time labels every 5s so "just now" ages to "Xs ago"
- const tickInterval = setInterval(() => tick((n) => n + 1), 5_000)
-
- return () => {
- clearInterval(addInterval)
- clearInterval(tickInterval)
- }
- }, [])
-
- return (
-
-
- {entries.map((entry, index) => (
-
-
-
- ))}
-
-
- )
-}
-
-interface PermissionFeature {
- name: string
- key: string
- defaultEnabled: boolean
- providerId?: string
-}
-
-interface PermissionCategory {
- label: string
- color: string
- features: PermissionFeature[]
-}
-
-const PERMISSION_CATEGORIES: PermissionCategory[] = [
- {
- label: 'Providers',
- color: '#FA4EDF',
- features: [
- { key: 'openai', name: 'OpenAI', defaultEnabled: true, providerId: 'openai' },
- { key: 'anthropic', name: 'Anthropic', defaultEnabled: true, providerId: 'anthropic' },
- { key: 'google', name: 'Google', defaultEnabled: false, providerId: 'google' },
- { key: 'xai', name: 'xAI', defaultEnabled: true, providerId: 'xai' },
- ],
- },
- {
- label: 'Workspace',
- color: '#2ABBF8',
- features: [
- { key: 'knowledge-base', name: 'Knowledge Base', defaultEnabled: true },
- { key: 'tables', name: 'Tables', defaultEnabled: true },
- { key: 'copilot', name: 'Copilot', defaultEnabled: false },
- { key: 'environment', name: 'Environment', defaultEnabled: false },
- ],
- },
- {
- label: 'Tools',
- color: '#33C482',
- features: [
- { key: 'mcp-tools', name: 'MCP Tools', defaultEnabled: true },
- { key: 'custom-tools', name: 'Custom Tools', defaultEnabled: false },
- { key: 'skills', name: 'Skills', defaultEnabled: true },
- { key: 'invitations', name: 'Invitations', defaultEnabled: true },
- ],
- },
-]
-
-const INITIAL_ACCESS_STATE = Object.fromEntries(
- PERMISSION_CATEGORIES.flatMap((category) =>
- category.features.map((feature) => [feature.key, feature.defaultEnabled])
- )
-)
-
-function CheckboxIcon({ checked, color }: { checked: boolean; color: string }) {
- return (
-
- )
-}
-
-function ProviderPreviewIcon({ providerId }: { providerId?: string }) {
- if (!providerId) return null
-
- const ProviderIcon = PROVIDER_DEFINITIONS[providerId]?.icon
- if (!ProviderIcon) return null
-
- return (
-
- )
-}
-
-function AccessControlPanel() {
- const ref = useRef(null)
- const isInView = useInView(ref, { once: true, margin: '-40px' })
- const [accessState, setAccessState] = useState>(INITIAL_ACCESS_STATE)
-
- return (
-
-
- {PERMISSION_CATEGORIES.map((category, catIdx) => {
- const offsetBefore = PERMISSION_CATEGORIES.slice(0, catIdx).reduce(
- (sum, c) => sum + c.features.length,
- 0
- )
-
- return (
-
0 ? 'mt-4' : ''}>
-
- {category.label}
-
-
- {category.features.map((feature, featIdx) => {
- const enabled = accessState[feature.key]
-
- return (
-
- setAccessState((prev) => ({ ...prev, [feature.key]: !prev[feature.key] }))
- }
- whileTap={{ scale: 0.98 }}
- >
-
-
-
- {feature.name}
-
-
- )
- })}
-
-
- )
- })}
-
-
- {/* Desktop — categorized grid */}
-
- {PERMISSION_CATEGORIES.map((category, catIdx) => (
-
0 ? 'mt-4' : ''}>
-
- {category.label}
-
-
- {category.features.map((feature, featIdx) => {
- const enabled = accessState[feature.key]
- const currentIndex =
- PERMISSION_CATEGORIES.slice(0, catIdx).reduce(
- (sum, c) => sum + c.features.length,
- 0
- ) + featIdx
-
- return (
-
- setAccessState((prev) => ({ ...prev, [feature.key]: !prev[feature.key] }))
- }
- whileTap={{ scale: 0.98 }}
- >
-
-
-
- {feature.name}
-
-
- )
- })}
-
-
- ))}
-
-
- )
-}
-
function TrustStrip() {
return (
@@ -482,7 +75,7 @@ function TrustStrip() {
- {/* Open Source — center */}
+ {/* Open Source -- center */}
>
+ text: string
+}
+
+interface PricingTier {
+ name: string
+ tier: string
+ price: string
+ features: PricingFeature[]
+ ctaText: string
+ featured?: boolean
+}
+
+const FREE_PLAN_FEATURES: PricingFeature[] = [
+ { icon: DollarSign, text: '1,000 credits (trial)' },
+ { icon: HardDrive, text: '5GB file storage' },
+ { icon: Timer, text: '5 min execution limit' },
+ { icon: Database, text: 'Limited log retention' },
+ { icon: Code2, text: 'CLI/SDK Access' },
+]
+
+const PRO_LANDING_FEATURES: PricingFeature[] = [
+ { icon: DollarSign, text: '6,000 credits/mo' },
+ { icon: RefreshCw, text: '+50 daily refresh credits' },
+ { icon: Zap, text: '150 runs/min (sync)' },
+ { icon: Timer, text: '50 min sync execution limit' },
+ { icon: HardDrive, text: '50GB file storage' },
+]
+
+const MAX_LANDING_FEATURES: PricingFeature[] = [
+ { icon: DollarSign, text: '25,000 credits/mo' },
+ { icon: RefreshCw, text: '+200 daily refresh credits' },
+ { icon: Zap, text: '300 runs/min (sync)' },
+ { icon: Timer, text: '50 min sync execution limit' },
+ { icon: HardDrive, text: '500GB file storage' },
+]
+
+const pricingTiers: PricingTier[] = [
+ {
+ name: 'COMMUNITY',
+ tier: 'Free',
+ price: 'Free',
+ features: FREE_PLAN_FEATURES,
+ ctaText: 'Get Started',
+ },
+ {
+ name: 'PRO',
+ tier: 'Pro',
+ price: '$25/mo',
+ features: PRO_LANDING_FEATURES,
+ ctaText: 'Get Started',
+ featured: true,
+ },
+ {
+ name: 'MAX',
+ tier: 'Max',
+ price: '$100/mo',
+ features: MAX_LANDING_FEATURES,
+ ctaText: 'Get Started',
+ },
+ {
+ name: 'ENTERPRISE',
+ tier: 'Enterprise',
+ price: 'Custom',
+ features: ENTERPRISE_PLAN_FEATURES,
+ ctaText: 'Contact Sales',
+ },
+]
+
+function PricingCard({
+ tier,
+ isBeforeFeatured,
+}: {
+ tier: PricingTier
+ isBeforeFeatured?: boolean
+}) {
+ const [isHovered, setIsHovered] = useState(false)
+ const router = useRouter()
+
+ const handleCtaClick = () => {
+ logger.info(`Pricing CTA clicked: ${tier.name}`)
+
+ if (tier.ctaText === 'Contact Sales') {
+ window.open('https://form.typeform.com/to/jqCO12pF', '_blank')
+ } else {
+ router.push('/signup')
+ }
+ }
+
+ return (
+
+
+
+
+
+ {tier.name}
+
+
+
+
+ {tier.price}
+
+
+
+
+ {tier.features.map((feature, idx) => (
+
+
+
+ {feature.text}
+
+
+ ))}
+
+
+
+
+ {tier.featured ? (
+
setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#E8E8E8] bg-gradient-to-b from-[#F8F8F8] to-white px-3 py-[6px] font-medium text-[#6F3DFA] text-[14px] shadow-[inset_0_2px_4px_0_rgba(255,255,255,0.9)] transition-all'
+ >
+
+ {tier.ctaText}
+
+ {isHovered ? (
+
+ ) : (
+
+ )}
+
+
+
+ ) : (
+
setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#343434] bg-gradient-to-b from-[#060606] to-[#323232] px-3 py-[6px] font-medium text-[14px] text-white shadow-[inset_0_1.25px_2.5px_0_#9B77FF] transition-all'
+ >
+
+ {tier.ctaText}
+
+ {isHovered ? (
+
+ ) : (
+
+ )}
+
+
+
+ )}
+
+
+
+ )
+}
+
+/**
+ * Pricing grid with all tier cards. Rendered as a client component because
+ * the tier data contains component references (icon functions) which are
+ * not serializable across the RSC boundary.
+ */
+export function PricingGrid() {
+ return (
+
+ {pricingTiers.map((tier, index) => {
+ const nextTier = pricingTiers[index + 1]
+ const isBeforeFeatured = nextTier?.featured
+ return
+ })}
+
+ )
+}
diff --git a/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx b/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx
index 9467651525e..3ef4acc7bfc 100644
--- a/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx
+++ b/apps/sim/app/(landing)/components/landing-pricing/landing-pricing.tsx
@@ -1,226 +1,4 @@
-'use client'
-
-import type { ComponentType, SVGProps } from 'react'
-import { useState } from 'react'
-import { createLogger } from '@sim/logger'
-import type { LucideIcon } from 'lucide-react'
-import {
- ArrowRight,
- ChevronRight,
- Code2,
- Database,
- DollarSign,
- HardDrive,
- RefreshCw,
- Timer,
- Zap,
-} from 'lucide-react'
-import { useRouter } from 'next/navigation'
-import { cn } from '@/lib/core/utils/cn'
-import { ENTERPRISE_PLAN_FEATURES } from '@/app/workspace/[workspaceId]/settings/components/subscription/plan-configs'
-
-const logger = createLogger('LandingPricing')
-
-interface PricingFeature {
- icon: LucideIcon | ComponentType>
- text: string
-}
-
-interface PricingTier {
- name: string
- tier: string
- price: string
- features: PricingFeature[]
- ctaText: string
- featured?: boolean
-}
-
-const FREE_PLAN_FEATURES: PricingFeature[] = [
- { icon: DollarSign, text: '1,000 credits (trial)' },
- { icon: HardDrive, text: '5GB file storage' },
- { icon: Timer, text: '5 min execution limit' },
- { icon: Database, text: 'Limited log retention' },
- { icon: Code2, text: 'CLI/SDK Access' },
-]
-
-const PRO_LANDING_FEATURES: PricingFeature[] = [
- { icon: DollarSign, text: '6,000 credits/mo' },
- { icon: RefreshCw, text: '+50 daily refresh credits' },
- { icon: Zap, text: '150 runs/min (sync)' },
- { icon: Timer, text: '50 min sync execution limit' },
- { icon: HardDrive, text: '50GB file storage' },
-]
-
-const MAX_LANDING_FEATURES: PricingFeature[] = [
- { icon: DollarSign, text: '25,000 credits/mo' },
- { icon: RefreshCw, text: '+200 daily refresh credits' },
- { icon: Zap, text: '300 runs/min (sync)' },
- { icon: Timer, text: '50 min sync execution limit' },
- { icon: HardDrive, text: '500GB file storage' },
-]
-
-const pricingTiers: PricingTier[] = [
- {
- name: 'COMMUNITY',
- tier: 'Free',
- price: 'Free',
- features: FREE_PLAN_FEATURES,
- ctaText: 'Get Started',
- },
- {
- name: 'PRO',
- tier: 'Pro',
- price: '$25/mo',
- features: PRO_LANDING_FEATURES,
- ctaText: 'Get Started',
- featured: true,
- },
- {
- name: 'MAX',
- tier: 'Max',
- price: '$100/mo',
- features: MAX_LANDING_FEATURES,
- ctaText: 'Get Started',
- },
- {
- name: 'ENTERPRISE',
- tier: 'Enterprise',
- price: 'Custom',
- features: ENTERPRISE_PLAN_FEATURES,
- ctaText: 'Contact Sales',
- },
-]
-
-function PricingCard({
- tier,
- index,
- isBeforeFeatured,
-}: {
- tier: PricingTier
- index: number
- isBeforeFeatured?: boolean
-}) {
- const [isHovered, setIsHovered] = useState(false)
- const router = useRouter()
-
- const handleCtaClick = () => {
- logger.info(`Pricing CTA clicked: ${tier.name}`)
-
- if (tier.ctaText === 'Contact Sales') {
- window.open('https://form.typeform.com/to/jqCO12pF', '_blank')
- } else {
- router.push('/signup')
- }
- }
-
- return (
-
-
-
-
-
- {tier.name}
-
-
-
-
- {tier.price}
-
-
-
-
- {tier.features.map((feature, idx) => (
-
-
-
- {feature.text}
-
-
- ))}
-
-
-
-
- {tier.featured ? (
-
setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
- className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#E8E8E8] bg-gradient-to-b from-[#F8F8F8] to-white px-3 py-[6px] font-medium text-[#6F3DFA] text-[14px] shadow-[inset_0_2px_4px_0_rgba(255,255,255,0.9)] transition-all'
- >
-
- {tier.ctaText}
-
- {isHovered ? (
-
- ) : (
-
- )}
-
-
-
- ) : (
-
setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
- className='group inline-flex w-full items-center justify-center gap-2 rounded-[10px] border border-[#343434] bg-gradient-to-b from-[#060606] to-[#323232] px-3 py-[6px] font-medium text-[14px] text-white shadow-[inset_0_1.25px_2.5px_0_#9B77FF] transition-all'
- >
-
- {tier.ctaText}
-
- {isHovered ? (
-
- ) : (
-
- )}
-
-
-
- )}
-
-
-
- )
-}
+import { PricingGrid } from '@/app/(landing)/components/landing-pricing/components/pricing-card'
/**
* Landing page pricing section displaying tiered pricing plans
@@ -230,20 +8,7 @@ export default function LandingPricing() {
Pricing Plans
-
- {pricingTiers.map((tier, index) => {
- const nextTier = pricingTiers[index + 1]
- const isBeforeFeatured = nextTier?.featured
- return (
-
- )
- })}
-
+
)
diff --git a/apps/sim/app/chat/components/loading-state/loading-state.tsx b/apps/sim/app/chat/components/loading-state/loading-state.tsx
index 8cfd0406b7c..814f626b81d 100644
--- a/apps/sim/app/chat/components/loading-state/loading-state.tsx
+++ b/apps/sim/app/chat/components/loading-state/loading-state.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { Skeleton } from '@/components/emcn'
export function ChatLoadingState() {
diff --git a/apps/sim/app/form/[identifier]/components/loading-state.tsx b/apps/sim/app/form/[identifier]/components/loading-state.tsx
index 1e3656e5186..e64b77b0f17 100644
--- a/apps/sim/app/form/[identifier]/components/loading-state.tsx
+++ b/apps/sim/app/form/[identifier]/components/loading-state.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { Skeleton } from '@/components/emcn'
import AuthBackground from '@/app/(auth)/components/auth-background'
diff --git a/apps/sim/app/form/[identifier]/components/thank-you-screen.tsx b/apps/sim/app/form/[identifier]/components/thank-you-screen.tsx
index 829f7b9edfd..2e617c7fa1e 100644
--- a/apps/sim/app/form/[identifier]/components/thank-you-screen.tsx
+++ b/apps/sim/app/form/[identifier]/components/thank-you-screen.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { CheckCircle2 } from 'lucide-react'
interface ThankYouScreenProps {
diff --git a/apps/sim/app/invite/components/layout.tsx b/apps/sim/app/invite/components/layout.tsx
index 1d6ce1fd1d0..63000b53758 100644
--- a/apps/sim/app/invite/components/layout.tsx
+++ b/apps/sim/app/invite/components/layout.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { SupportFooter } from '@/app/(auth)/components/support-footer'
import Navbar from '@/app/(home)/components/navbar/navbar'
diff --git a/apps/sim/app/templates/layout-client.tsx b/apps/sim/app/templates/layout-client.tsx
index f49b81c6c67..e89327ab2d4 100644
--- a/apps/sim/app/templates/layout-client.tsx
+++ b/apps/sim/app/templates/layout-client.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { season } from '@/app/_styles/fonts/season/season'
export default function TemplatesLayoutClient({ children }: { children: React.ReactNode }) {
diff --git a/apps/sim/app/workspace/[workspaceId]/components/conversation-list-item.tsx b/apps/sim/app/workspace/[workspaceId]/components/conversation-list-item.tsx
index 587e4e6b384..9c3c10fd675 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/conversation-list-item.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/conversation-list-item.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import type { ReactNode } from 'react'
import { Blimp } from '@/components/emcn'
import { cn } from '@/lib/core/utils/cn'
diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx
index 26358376026..63b3ee1cddc 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/owner-cell/owner-cell.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import type { ResourceCell } from '@/app/workspace/[workspaceId]/components/resource/resource'
import type { WorkspaceMember } from '@/hooks/queries/workspace'
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/chat-message-attachments.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/chat-message-attachments.tsx
index 40e31a8a9b5..e39d3a0dd37 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/chat-message-attachments.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/chat-message-attachments.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { getDocumentIcon } from '@/components/icons/document-icons'
import { cn } from '@/lib/core/utils/cn'
import type { ChatMessageAttachment } from '../types'
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx
index 43ddaebb7c9..05a91c3a364 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { PillsRing } from '@/components/emcn'
import type { ToolCallStatus } from '../../../../types'
import { getToolIcon } from '../../utils'
diff --git a/apps/sim/app/workspace/[workspaceId]/layout.tsx b/apps/sim/app/workspace/[workspaceId]/layout.tsx
index 5abbde90ab6..45a92298f32 100644
--- a/apps/sim/app/workspace/[workspaceId]/layout.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/layout.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { ToastProvider } from '@/components/emcn'
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
import { ProviderModelsLoader } from '@/app/workspace/[workspaceId]/providers/provider-models-loader'
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/credentials/credentials.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/credentials/credentials.tsx
index 4765ebe1196..237f5c2114d 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/credentials/credentials.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/credentials/credentials.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { CredentialsManager } from '@/app/workspace/[workspaceId]/settings/components/credentials/credentials-manager'
export function Credentials() {
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations.tsx
index 9b03b2da1a1..1c577e92c5a 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { IntegrationsManager } from '@/app/workspace/[workspaceId]/settings/components/integrations/integrations-manager'
export function Integrations() {
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx
index b9acc723288..983cd6383e2 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import type { ReactNode } from 'react'
import { splitReferenceSegment } from '@/lib/workflows/sanitization/references'
import { normalizeName, REFERENCE } from '@/executor/constants'
diff --git a/apps/sim/components/emcn/components/badge/badge.tsx b/apps/sim/components/emcn/components/badge/badge.tsx
index 837bb85fe4e..61d4b53530a 100644
--- a/apps/sim/components/emcn/components/badge/badge.tsx
+++ b/apps/sim/components/emcn/components/badge/badge.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import type * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/core/utils/cn'
diff --git a/apps/sim/components/emcn/components/breadcrumb/breadcrumb.tsx b/apps/sim/components/emcn/components/breadcrumb/breadcrumb.tsx
index ae2167f2915..747efbabeba 100644
--- a/apps/sim/components/emcn/components/breadcrumb/breadcrumb.tsx
+++ b/apps/sim/components/emcn/components/breadcrumb/breadcrumb.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import type * as React from 'react'
import { ChevronRight } from 'lucide-react'
import Link from 'next/link'
diff --git a/apps/sim/components/emcn/components/table/table.tsx b/apps/sim/components/emcn/components/table/table.tsx
index 11012778f8b..d5f3c996a9b 100644
--- a/apps/sim/components/emcn/components/table/table.tsx
+++ b/apps/sim/components/emcn/components/table/table.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import * as React from 'react'
import { cn } from '@/lib/core/utils/cn'
diff --git a/apps/sim/components/ui/search-highlight.tsx b/apps/sim/components/ui/search-highlight.tsx
index 9a1322f8e86..0c9e7cf1758 100644
--- a/apps/sim/components/ui/search-highlight.tsx
+++ b/apps/sim/components/ui/search-highlight.tsx
@@ -1,5 +1,3 @@
-'use client'
-
interface SearchHighlightProps {
text: string
searchQuery: string
diff --git a/apps/sim/lib/blog/code.tsx b/apps/sim/lib/blog/code.tsx
index fd405f7feee..412a2566553 100644
--- a/apps/sim/lib/blog/code.tsx
+++ b/apps/sim/lib/blog/code.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import { Code } from '@/components/emcn'
interface CodeBlockProps {