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 */} + + ) +} + +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 */} - - ) -} - -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 ? ( + + ) : ( + + )} +
    +
    +
    + ) +} + +/** + * 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 ? ( - - ) : ( - - )} -
    -
    -
    - ) -} +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 {