mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(pricing): route Talk to sales CTA to demo request form instead of signup (#5602)
- pricing page's enterprise card was labeled "Talk to sales" but linked to /signup for every card, sending visitors to self-serve signup instead of the demo-request flow every other "Contact sales" CTA on the site uses - resolveCta now keys off the CTA's sales intent to pick the right href - extracted the /signup and /demo route literals (previously hardcoded independently in 6 files) into a single shared apps/sim/app/(landing)/constants.ts so no CTA can drift to the wrong destination again - hoisted the static per-column comparison sections out of render and deduped the annual-discount price math in pricing-plans.tsx
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { ChipLink } from '@sim/emcn'
|
||||
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
|
||||
|
||||
/**
|
||||
* Landing pre-footer CTA - the page's final conversion band. A tall, centered
|
||||
@@ -28,10 +29,10 @@ export function Cta() {
|
||||
Build your first agent today.
|
||||
</h2>
|
||||
<div className='flex items-center gap-1'>
|
||||
<ChipLink variant='primary' href='/signup'>
|
||||
<ChipLink variant='primary' href={SIGNUP_HREF}>
|
||||
Get started
|
||||
</ChipLink>
|
||||
<ChipLink href='/demo' className='border border-[var(--border-1)]'>
|
||||
<ChipLink href={DEMO_HREF} className='border border-[var(--border-1)]'>
|
||||
Contact sales
|
||||
</ChipLink>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChipLink, cn } from '@sim/emcn'
|
||||
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
|
||||
|
||||
/**
|
||||
* Hero-scale sizing shared by both CTAs - one step up from the navbar's 30px
|
||||
@@ -24,11 +25,11 @@ const CTA_SIZE = 'h-[36px] px-[0.571em] text-[15px] [&>span]:[font-size:inherit]
|
||||
export function HeroCta() {
|
||||
return (
|
||||
<div className='flex items-center gap-2 max-sm:w-full max-sm:flex-col max-sm:items-stretch'>
|
||||
<ChipLink variant='primary' href='/demo' className={CTA_SIZE}>
|
||||
<ChipLink variant='primary' href={DEMO_HREF} className={CTA_SIZE}>
|
||||
Request a demo
|
||||
</ChipLink>
|
||||
<ChipLink
|
||||
href='/signup'
|
||||
href={SIGNUP_HREF}
|
||||
prefetch={false}
|
||||
className={cn(CTA_SIZE, 'border border-[var(--border-1)] max-sm:justify-center')}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
NAVBAR_GLASS_SURFACE,
|
||||
useNavbarFrost,
|
||||
} from '@/app/(landing)/components/navbar/components/navbar-shell'
|
||||
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
|
||||
|
||||
/**
|
||||
* Mobile navigation - the `< lg` counterpart to the desktop nav clusters.
|
||||
@@ -70,7 +71,7 @@ export function MobileNav({ stars }: MobileNavProps) {
|
||||
|
||||
return (
|
||||
<div className='ml-auto flex items-center gap-2 lg:hidden'>
|
||||
<ChipLink variant='primary' href='/signup' prefetch={false}>
|
||||
<ChipLink variant='primary' href={SIGNUP_HREF} prefetch={false}>
|
||||
Sign up
|
||||
</ChipLink>
|
||||
<button
|
||||
@@ -166,7 +167,7 @@ export function MobileNav({ stars }: MobileNavProps) {
|
||||
</ChipLink>
|
||||
<ChipLink
|
||||
variant='primary'
|
||||
href='/demo'
|
||||
href={DEMO_HREF}
|
||||
fullWidth
|
||||
flush
|
||||
className='h-[40px] justify-center [&>span]:flex-none'
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
NavMenuChip,
|
||||
SimWordmark,
|
||||
} from '@/app/(landing)/components/navbar/components'
|
||||
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
|
||||
|
||||
/**
|
||||
* Landing navbar.
|
||||
@@ -87,10 +88,10 @@ export function Navbar({ stars, logoOnly = false }: NavbarProps) {
|
||||
<ChipLink href='/login' prefetch={false}>
|
||||
Log in
|
||||
</ChipLink>
|
||||
<ChipLink variant='border' href='/demo'>
|
||||
<ChipLink variant='border' href={DEMO_HREF}>
|
||||
Contact sales
|
||||
</ChipLink>
|
||||
<ChipLink variant='primary' href='/signup' prefetch={false}>
|
||||
<ChipLink variant='primary' href={SIGNUP_HREF} prefetch={false}>
|
||||
Sign up
|
||||
</ChipLink>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Shared landing-page CTA destinations. Every "Get started"/"Sign up" CTA
|
||||
* across the marketing site funnels to signup; every "Talk to sales"/
|
||||
* "Contact sales" CTA funnels to the demo-request form. Centralized here so
|
||||
* no CTA can drift to the wrong destination independently.
|
||||
*/
|
||||
export const SIGNUP_HREF = '/signup'
|
||||
export const DEMO_HREF = '/demo'
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@/app/(landing)/components/solutions-page/components'
|
||||
import { SOLUTIONS_SPACING } from '@/app/(landing)/components/solutions-page/constants'
|
||||
import type { SolutionsPageConfig } from '@/app/(landing)/components/solutions-page/types'
|
||||
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
|
||||
import { EnterpriseFeatureGrid } from '@/app/(landing)/enterprise/components/enterprise-feature-grid'
|
||||
import { EnterprisePlatformLoop } from '@/app/(landing)/enterprise/components/enterprise-platform-loop'
|
||||
import {
|
||||
@@ -84,7 +85,7 @@ const ENTERPRISE_CONFIG: SolutionsPageConfig = {
|
||||
title: 'Build, Deploy, and Manage Enterprise AI Agents in One Workspace',
|
||||
subtitle:
|
||||
'Build enterprise AI agents, ship to production, and manage every version from one workspace.',
|
||||
cta: { label: 'Start building', href: '/signup' },
|
||||
cta: { label: 'Start building', href: SIGNUP_HREF },
|
||||
cards: [
|
||||
{
|
||||
title: 'Build visually or with code',
|
||||
@@ -111,7 +112,7 @@ const ENTERPRISE_CONFIG: SolutionsPageConfig = {
|
||||
title: 'Governance and Security for Enterprise AI Agents',
|
||||
subtitle:
|
||||
'Security, approvals, and controls keep enterprise AI agents trusted in production.',
|
||||
cta: { label: 'See security', href: '/demo' },
|
||||
cta: { label: 'See security', href: DEMO_HREF },
|
||||
cards: [
|
||||
{
|
||||
title: 'Control who can do what',
|
||||
@@ -138,7 +139,7 @@ const ENTERPRISE_CONFIG: SolutionsPageConfig = {
|
||||
id: 'deploy',
|
||||
title: 'Deploy Enterprise Workflow Agents with Confidence',
|
||||
subtitle: 'Stage, observe, and version workflow agents before they reach production.',
|
||||
cta: { label: 'Explore deployment', href: '/signup' },
|
||||
cta: { label: 'Explore deployment', href: SIGNUP_HREF },
|
||||
cards: [
|
||||
{
|
||||
title: 'Stage before you ship',
|
||||
@@ -162,7 +163,7 @@ const ENTERPRISE_CONFIG: SolutionsPageConfig = {
|
||||
title: 'Built for Enterprise Teams',
|
||||
subtitle:
|
||||
'Built for the teams that own enterprise AI agents across IT, operations, and engineering.',
|
||||
cta: { label: 'Talk to sales', href: '/demo' },
|
||||
cta: { label: 'Talk to sales', href: DEMO_HREF },
|
||||
cards: [
|
||||
{
|
||||
title: 'IT and platform teams',
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ReactNode } from 'react'
|
||||
import { useQueryStates } from 'nuqs'
|
||||
import { getUpgradeCardCta, type PlanTier, type UpgradeCardId } from '@/lib/billing/client'
|
||||
import { ANNUAL_DISCOUNT_RATE, CREDIT_TIERS } from '@/lib/billing/constants'
|
||||
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
|
||||
import {
|
||||
PricingCard,
|
||||
type PricingCardCta,
|
||||
@@ -18,14 +19,12 @@ import {
|
||||
|
||||
/**
|
||||
* A public visitor has no subscription, so each card's canonical label and
|
||||
* variant resolves against the `free` tier. On this page every CTA funnels to
|
||||
* sign-up regardless of plan.
|
||||
* variant resolves against the `free` tier. On this page every self-serve CTA
|
||||
* funnels to sign-up regardless of plan; the enterprise CTA routes to the
|
||||
* demo-request form instead.
|
||||
*/
|
||||
const VISITOR_TIER: PlanTier = 'free'
|
||||
|
||||
/** Every CTA on the public pricing page funnels logged-out visitors to sign-up. */
|
||||
const SIGNUP_HREF = '/signup'
|
||||
|
||||
/** This section's rows render on the public cards without their group header. */
|
||||
const HEADERLESS_SECTION_TITLE = 'Credits & pricing'
|
||||
|
||||
@@ -52,13 +51,27 @@ function sectionsForColumn(col: number): PricingCardSection[] {
|
||||
}))
|
||||
}
|
||||
|
||||
/** The four card columns (Free, Pro, Max, Enterprise) transposed once at module load - the source data is static, so this never needs to be redone per render. */
|
||||
const SECTIONS_BY_COLUMN = [0, 1, 2, 3].map(sectionsForColumn)
|
||||
|
||||
/** Round a dollar amount down to the annual-billing discounted price. */
|
||||
function annualPrice(dollars: number): number {
|
||||
return Math.round(dollars * (1 - ANNUAL_DISCOUNT_RATE))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a card's canonical label and variant from the free-tier matrix; the
|
||||
* CTA always funnels logged-out visitors to sign-up.
|
||||
* Resolve a card's canonical label and variant from the free-tier matrix. A
|
||||
* `sales` intent ("Talk to sales") routes to the demo-request form, matching
|
||||
* every other "Contact sales" CTA on the landing site; every other intent
|
||||
* funnels logged-out visitors to sign-up.
|
||||
*/
|
||||
function resolveCta(card: UpgradeCardId): PricingCardCta {
|
||||
const cta = getUpgradeCardCta(VISITOR_TIER, card)
|
||||
return { label: cta.label, variant: cta.variant, href: SIGNUP_HREF }
|
||||
return {
|
||||
label: cta.label,
|
||||
variant: cta.variant,
|
||||
href: cta.intent === 'sales' ? DEMO_HREF : SIGNUP_HREF,
|
||||
}
|
||||
}
|
||||
|
||||
const FREE_CTA: PricingCardCta = {
|
||||
@@ -86,12 +99,8 @@ export function PricingPlans({ heading }: PricingPlansProps) {
|
||||
const setIsAnnual = (next: boolean) => setParams({ billing: next ? 'annual' : 'monthly' })
|
||||
|
||||
const discountPct = Math.round(ANNUAL_DISCOUNT_RATE * 100)
|
||||
const proPrice = isAnnual
|
||||
? Math.round(CREDIT_TIERS[0].dollars * (1 - ANNUAL_DISCOUNT_RATE))
|
||||
: CREDIT_TIERS[0].dollars
|
||||
const maxPrice = isAnnual
|
||||
? Math.round(CREDIT_TIERS[1].dollars * (1 - ANNUAL_DISCOUNT_RATE))
|
||||
: CREDIT_TIERS[1].dollars
|
||||
const proPrice = isAnnual ? annualPrice(CREDIT_TIERS[0].dollars) : CREDIT_TIERS[0].dollars
|
||||
const maxPrice = isAnnual ? annualPrice(CREDIT_TIERS[1].dollars) : CREDIT_TIERS[1].dollars
|
||||
const priceSubtext = isAnnual
|
||||
? 'per user/month, billed annually'
|
||||
: 'per user/month, billed monthly'
|
||||
@@ -114,7 +123,7 @@ export function PricingPlans({ heading }: PricingPlansProps) {
|
||||
price='$0'
|
||||
priceSubtext='Free forever'
|
||||
cta={FREE_CTA}
|
||||
sections={sectionsForColumn(0)}
|
||||
sections={SECTIONS_BY_COLUMN[0]}
|
||||
/>
|
||||
<PricingCard
|
||||
name='Pro'
|
||||
@@ -122,7 +131,7 @@ export function PricingPlans({ heading }: PricingPlansProps) {
|
||||
discountLabel={discountLabel}
|
||||
priceSubtext={priceSubtext}
|
||||
cta={proCta}
|
||||
sections={sectionsForColumn(1)}
|
||||
sections={SECTIONS_BY_COLUMN[1]}
|
||||
/>
|
||||
<PricingCard
|
||||
name='Max'
|
||||
@@ -130,14 +139,14 @@ export function PricingPlans({ heading }: PricingPlansProps) {
|
||||
discountLabel={discountLabel}
|
||||
priceSubtext={priceSubtext}
|
||||
cta={maxCta}
|
||||
sections={sectionsForColumn(2)}
|
||||
sections={SECTIONS_BY_COLUMN[2]}
|
||||
/>
|
||||
<PricingCard
|
||||
name='Enterprise'
|
||||
price='Custom'
|
||||
priceSubtext='Tailored to your team'
|
||||
cta={enterpriseCta}
|
||||
sections={sectionsForColumn(3)}
|
||||
sections={SECTIONS_BY_COLUMN[3]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user