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,53 @@
'use client'
import { ChipSwitch, ChipTag } from '@sim/emcn'
import { ANNUAL_DISCOUNT_RATE } from '@/lib/billing/constants'
/**
* Props for {@link BillingPeriodToggle}.
*/
export interface BillingPeriodToggleProps {
/** Whether the annual billing period is currently selected. */
isAnnual: boolean
/** Invoked with the next selection when a segment is clicked. */
onChange: (isAnnual: boolean) => void
/** Optional additional classes merged onto the container. */
className?: string
}
/**
* Discount label derived from the real billing constant so it stays in sync if
* the rate changes (e.g. `0.15` renders as `-15%`).
*/
const DISCOUNT_LABEL = `-${Math.round(ANNUAL_DISCOUNT_RATE * 100)}%`
type Period = 'annual' | 'monthly'
/**
* Annual / monthly billing-period segmented switch. Built on {@link ChipSwitch}
* with an inline brand-tinted discount badge on the annual segment.
*/
export function BillingPeriodToggle({ isAnnual, onChange, className }: BillingPeriodToggleProps) {
return (
<ChipSwitch<Period>
aria-label='Billing period'
value={isAnnual ? 'annual' : 'monthly'}
onChange={(next) => onChange(next === 'annual')}
className={className}
options={[
{
value: 'annual',
label: (
<>
Annual
<ChipTag variant='mono' className={isAnnual ? undefined : 'text-inherit'}>
{DISCOUNT_LABEL}
</ChipTag>
</>
),
},
{ value: 'monthly', label: 'Monthly' },
]}
/>
)
}
@@ -0,0 +1 @@
export { BillingPeriodToggle, type BillingPeriodToggleProps } from './billing-period-toggle'
@@ -0,0 +1,207 @@
import {
CREDIT_TIERS,
CREDITS_PER_DOLLAR,
DAILY_REFRESH_RATE,
DEFAULT_FREE_CREDITS,
} from '@/lib/billing/constants'
/**
* Looks up a credit tier by name, so a column binds to the right tier even if
* {@link CREDIT_TIERS} is reordered or a tier is inserted.
*/
function tierByName(name: (typeof CREDIT_TIERS)[number]['name']) {
const tier = CREDIT_TIERS.find((t) => t.name === name)
if (!tier) throw new Error(`comparison-data: no credit tier named "${name}"`)
return tier
}
const PRO_TIER = tierByName('Pro')
const MAX_TIER = tierByName('Max')
/** Formats a credit count with thousands separators (e.g. 25000 → "25,000"). */
const formatCredits = (credits: number): string => credits.toLocaleString('en-US')
/** Daily refresh credits for a plan: 1% of plan dollars/day, in credits. */
const dailyRefreshCredits = (dollars: number): number =>
Math.round(dollars * DAILY_REFRESH_RATE * CREDITS_PER_DOLLAR)
/** A brand icon rendered in a cell instead of a check/em-dash/text. */
export interface CellIcon {
/** Icon identifier resolved to a component by the table renderer. */
icon: 'slack'
}
/** Cell value for the comparison table. */
export type CellValue = string | boolean | CellIcon
/** Shared Slack-availability cell. */
const SLACK: CellIcon = { icon: 'slack' }
/** Names of the four plan columns — used as a discriminated union for type-safe plan selection. */
export type PlanName = 'Free' | 'Pro' | 'Max' | 'Enterprise'
/** A single feature row inside a section. */
export interface ComparisonRow {
/** Row label displayed in the left column. */
label: string
/**
* Values for [Free, Pro, Max, Enterprise].
* `true` renders a check icon; `false` renders a muted em-dash.
* Strings render as-is with tabular-nums styling.
*/
values: [CellValue, CellValue, CellValue, CellValue]
}
/** A labelled group of comparison rows. */
export interface ComparisonSection {
/** Section header label. */
title: string
rows: ComparisonRow[]
}
/** Column metadata for the four plan headers. */
export interface PlanColumn {
/** Plan name — matches the {@link PlanName} discriminant. */
name: PlanName
/**
* Price display. Pass `null` to signal "use runtime price from state"
* (handled in the component for Pro and Max).
*/
staticPrice: string | null
}
/** Ordered plan columns — indices match `ComparisonRow.values`. */
export const PLAN_COLUMNS: PlanColumn[] = [
{ name: 'Free', staticPrice: '$0' },
{ name: 'Pro', staticPrice: null },
{ name: 'Max', staticPrice: null },
{ name: 'Enterprise', staticPrice: 'Custom' },
]
/** Full comparison dataset. */
export const COMPARISON_SECTIONS: ComparisonSection[] = [
{
title: 'Credits & pricing',
rows: [
{
label: 'Monthly credits',
values: [
formatCredits(DEFAULT_FREE_CREDITS * CREDITS_PER_DOLLAR),
formatCredits(PRO_TIER.credits),
formatCredits(MAX_TIER.credits),
'Custom',
],
},
{
label: 'Daily refresh',
values: [
false,
`+${formatCredits(dailyRefreshCredits(PRO_TIER.dollars))}`,
`+${formatCredits(dailyRefreshCredits(MAX_TIER.dollars))}`,
'Custom',
],
},
],
},
{
title: 'Workspaces & teams',
rows: [
{
label: 'Workspaces',
values: ['1', '3', 'Unlimited', 'Unlimited'],
},
{
label: 'Invite teammates',
values: [false, true, true, true],
},
],
},
{
title: 'Rate limits (runs/min)',
rows: [
{
label: 'Sync executions',
values: ['50', '150', '300', 'Custom'],
},
{
label: 'Async executions',
values: ['200', '1,000', '2,500', 'Custom'],
},
{
label: 'API endpoint',
values: ['0', '100', '200', 'Custom'],
},
],
},
{
title: 'Execution timeouts',
rows: [
{
label: 'Sync timeout',
values: ['5 min', '50 min', '50 min', 'Custom'],
},
{
label: 'Async timeout',
values: ['90 min', '90 min', '90 min', 'Custom'],
},
],
},
{
title: 'Storage & data',
rows: [
{
label: 'File storage',
values: ['5 GB', '50 GB', '500 GB', 'Custom'],
},
{
label: 'Max tables',
values: ['5', '100', '1,000', 'Custom'],
},
{
label: 'Max rows per table',
values: ['50,000', '100,000', '500,000', 'Custom'],
},
{
label: 'Log retention',
values: ['30 days', 'Unlimited', 'Unlimited', 'Unlimited'],
},
],
},
{
title: 'Features',
rows: [
{
label: 'Sim Mailer (Inbox)',
values: [false, false, true, true],
},
{
label: 'KB Live Sync',
values: [false, false, true, true],
},
{
label: 'Slack Connect',
values: [false, false, SLACK, SLACK],
},
{
label: 'Access Control',
values: [false, false, false, true],
},
{
label: 'SSO',
values: [false, false, false, true],
},
{
label: 'SOC2 Compliance',
values: [false, false, false, true],
},
{
label: 'Self Hosting',
values: [false, false, false, true],
},
{
label: 'Dedicated Support',
values: [false, false, false, true],
},
],
},
]
@@ -0,0 +1,243 @@
'use client'
import { chipVariants, cn } from '@sim/emcn'
import { SlackIcon } from '@/components/icons'
import { BillingPeriodToggle } from '@/app/workspace/[workspaceId]/upgrade/components/billing-period-toggle/billing-period-toggle'
import {
type CellValue,
COMPARISON_SECTIONS,
PLAN_COLUMNS,
type PlanName,
} from '@/app/workspace/[workspaceId]/upgrade/components/comparison-table/comparison-data'
/** Maps a cell-icon identifier to its brand icon component. */
const CELL_ICONS = { slack: SlackIcon } as const
/**
* Resolved CTA for a plan column, mirroring the upgrade-page plan cards so the
* table and cards stay in lockstep (same label, variant, and disabled state).
*/
export interface ComparisonPlanCta {
label: string
variant: 'primary' | 'border-shadow'
onClick: () => void
disabled?: boolean
}
/**
* Props for {@link ComparisonTable}.
*/
export interface ComparisonTableProps {
/**
* Resolved Pro price string, e.g. `"$29"`.
* Sourced from the page-level `proPrice` derived from `useUpgradeState`.
*/
proPrice: string
/**
* Resolved Max price string, e.g. `"$79"`.
* Sourced from the page-level `maxPrice` derived from `useUpgradeState`.
*/
maxPrice: string
/**
* Whether annual billing is currently selected.
* Shared with the page-level {@link BillingPeriodToggle} via a single state source.
*/
isAnnual: boolean
/**
* Invoked when the in-table billing toggle changes.
* Should point to the same setter as the page-level toggle.
*/
onIsAnnualChange: (isAnnual: boolean) => void
/**
* Resolved CTA per plan column, mirroring the upgrade-page plan cards. Plans
* without an entry (e.g. Free) render no button.
*/
ctas: Partial<Record<PlanName, ComparisonPlanCta>>
}
/**
* Inline check icon — matches the card-level `CheckIcon` shape.
*/
function CheckIcon() {
return (
<svg
width='14'
height='14'
viewBox='0 0 14 14'
fill='none'
aria-hidden='true'
className='size-[14px] flex-shrink-0'
>
<path
d='M2.5 7L5.5 10L11.5 4'
stroke='currentColor'
strokeWidth='1.5'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
)
}
/**
* Renders a single cell value: `true` → check icon, `false` → em-dash, string → text.
*/
function Cell({ value }: { value: CellValue }) {
if (value === true) {
return (
<span className='flex justify-center text-[var(--text-primary)]'>
<CheckIcon />
</span>
)
}
if (value === false) {
return (
<span className='flex select-none justify-center text-[var(--text-muted)] text-base'></span>
)
}
if (typeof value === 'object') {
const Icon = CELL_ICONS[value.icon]
return (
<span className='flex justify-center'>
<Icon className='size-[14px] flex-shrink-0' />
</span>
)
}
return (
<span className='block text-center text-[var(--text-primary)] text-small tabular-nums'>
{value}
</span>
)
}
/**
* Full plan-comparison table. Renders all sections from {@link COMPARISON_SECTIONS}
* mapped over generically — no per-cell copy-paste. Prices for Pro and Max are
* supplied as props so they stay in sync with the billing-period toggle in the
* parent page.
*
* The top-left cell contains a "Compare plans" heading, a subtitle, and a
* {@link BillingPeriodToggle} that shares state with the page-level toggle via
* the `isAnnual` / `onIsAnnualChange` props.
*
* @example
* ```tsx
* <ComparisonTable
* proPrice={`$${proPrice}`}
* maxPrice={`$${maxPrice}`}
* isAnnual={state.isAnnual}
* onIsAnnualChange={state.setIsAnnual}
* ctas={{ Pro: proCta, Max: maxCta, Enterprise: enterpriseCta }}
* />
* ```
*/
export function ComparisonTable({
proPrice,
maxPrice,
isAnnual,
onIsAnnualChange,
ctas,
}: ComparisonTableProps) {
const runtimePrices: Partial<Record<PlanName, string>> = {
Pro: proPrice,
Max: maxPrice,
}
return (
<div className='w-full overflow-x-auto rounded-xl border border-[var(--border-1)]'>
{/* CSS grid: 1 label col + 4 equal plan cols */}
<div className='grid min-w-[640px] grid-cols-[1fr_repeat(4,minmax(0,1fr))]'>
{/* ── Column headers ── */}
{/* Top-left cell: title, subtitle, and billing toggle */}
<div className='flex h-full flex-col justify-between gap-3 border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-4'>
<div className='flex flex-col gap-0.5'>
<span className='font-medium text-[var(--text-primary)] text-base'>Compare plans</span>
<span className='text-[var(--text-muted)] text-small'>Find the right plan for you</span>
</div>
<BillingPeriodToggle isAnnual={isAnnual} onChange={onIsAnnualChange} />
</div>
{PLAN_COLUMNS.map((col) => {
const price = runtimePrices[col.name] ?? col.staticPrice ?? ''
const cta = ctas[col.name]
return (
<div
key={col.name}
className='flex flex-col items-center gap-1 bg-[var(--surface-2)] px-3 py-4 text-center'
>
<span className='font-medium text-[var(--text-primary)] text-base'>{col.name}</span>
<span className='font-medium text-[var(--text-primary)] text-md tabular-nums'>
{price}
</span>
{cta && (
<button
type='button'
onClick={cta.onClick}
disabled={cta.disabled}
aria-label={`${cta.label}${col.name}`}
className={cn(
chipVariants({ variant: cta.variant, fullWidth: true, flush: true }),
'mt-2 w-full justify-center'
)}
>
{cta.label}
</button>
)}
</div>
)
})}
{/* ── Sections ── */}
{COMPARISON_SECTIONS.map((section, sectionIdx) => (
<div key={section.title} className='contents'>
{/* Section header row — split so the left-column separator stays continuous */}
<div
className={cn(
'border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2',
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
)}
>
<span className='font-medium text-[var(--text-primary)] text-small'>
{section.title}
</span>
</div>
<div
className={cn(
'col-span-4 bg-[var(--surface-2)]',
sectionIdx > 0 && 'border-[var(--border-1)] border-t'
)}
/>
{/* Feature rows */}
{section.rows.map((row, rowIdx) => (
<div key={row.label} className='contents'>
{/* Label */}
<div
className={cn(
'flex items-center border-[var(--border)] border-r bg-[var(--surface-1)] px-4 py-2.5',
rowIdx < section.rows.length - 1 && 'border-[var(--border-1)] border-b'
)}
>
<span className='text-[var(--text-body)] text-small'>{row.label}</span>
</div>
{/* Plan cells */}
{row.values.map((value, colIdx) => (
<div
key={PLAN_COLUMNS[colIdx].name}
className={cn(
'flex items-center justify-center bg-[var(--surface-2)] px-3 py-2.5',
rowIdx < section.rows.length - 1 && 'border-[var(--border-1)] border-b'
)}
>
<Cell value={value} />
</div>
))}
</div>
))}
</div>
))}
</div>
</div>
)
}
@@ -0,0 +1,2 @@
export type { CellValue, ComparisonRow, ComparisonSection, PlanColumn } from './comparison-data'
export { ComparisonTable, type ComparisonTableProps } from './comparison-table'
@@ -0,0 +1,4 @@
export { BillingPeriodToggle, type BillingPeriodToggleProps } from './billing-period-toggle'
export { ComparisonTable, type ComparisonTableProps } from './comparison-table'
export type { PlanName } from './comparison-table/comparison-data'
export { UpgradePlanCard, type UpgradePlanCardProps } from './plan-card'
@@ -0,0 +1 @@
export { UpgradePlanCard, type UpgradePlanCardProps } from './plan-card'
@@ -0,0 +1,143 @@
'use client'
import { Check, ChipTag, Credit, chipVariants, cn, Info, RefreshCw } from '@sim/emcn'
/**
* Props for {@link UpgradePlanCard}.
*/
export interface UpgradePlanCardProps {
/** Plan name, e.g. `"Pro"`. */
name: string
/** Headline price, e.g. `"$29"` or `"Custom"`. */
price: string
/** Small line under the price, e.g. `"per user/month, billed annually"`. */
priceSubtext?: string
/** Optional discount pill rendered next to the price, e.g. `"20% off"`. */
discountLabel?: string
/** Short description below the plan name, e.g. `"For growing teams"`. */
segmentLabel: string
/**
* Monthly credit allocation shown prominently under the CTA, e.g. `"6,000 credits/mo"` or `"Custom"`.
* When omitted the credits/refresh block is not rendered.
*/
credits?: string
/**
* Daily refresh allocation shown below the credit amount, e.g. `"+50/day refresh"`.
* Only rendered when {@link UpgradePlanCardProps.credits} is also set.
*/
refresh?: string
/** Feature bullet list — each row renders with a check icon. */
features: readonly string[]
/** CTA label. */
buttonText: string
/** CTA click handler. */
onButtonClick: () => void
/** Whether the CTA is disabled. */
buttonDisabled?: boolean
/** When set, the CTA renders with the primary chip variant. */
highlighted?: boolean
/** Optional pill rendered in the top-right corner, e.g. `"Your plan"`. */
bannerText?: string
/** Extra outer classes. */
className?: string
}
/**
* Vertical pricing card styled with workspace surface, border, and text tokens.
* CTA reuses {@link chipVariants} so it picks up the platform's standard 30px
* pill chrome (primary filled when highlighted, neutral filled otherwise).
*
* When `credits` is supplied, a prominent stats block using the same `Credit`
* icon as the home-page chip is rendered directly below the CTA, followed by a
* solid `1px` divider (matching the integrations/skills section separator) before
* the bullet feature list. Pass `credits` on every card tier, including Enterprise
* (which uses `"Custom"` for both `credits` and `refresh`).
*/
export function UpgradePlanCard({
name,
price,
priceSubtext,
discountLabel,
segmentLabel,
credits,
refresh,
features,
buttonText,
onButtonClick,
buttonDisabled,
highlighted,
bannerText,
className,
}: UpgradePlanCardProps) {
return (
<article
className={cn(
'flex h-full flex-col gap-4 rounded-xl border border-[var(--border-1)] bg-[var(--surface-2)] p-5',
className
)}
>
<div className='flex flex-col gap-4'>
<div className='flex items-start justify-between gap-2'>
<h3 className='font-medium text-[24px] text-[var(--text-primary)]'>{name}</h3>
{bannerText && <ChipTag variant='gray'>{bannerText}</ChipTag>}
</div>
<div className='flex flex-col'>
<div className='flex items-center gap-2'>
<span className='font-medium text-[20px] text-[var(--text-primary)] tabular-nums'>
{price}
</span>
{discountLabel && <ChipTag variant='mono'>{discountLabel}</ChipTag>}
</div>
<p className='text-[var(--text-muted)] text-base'>{priceSubtext ?? '\u00A0'}</p>
</div>
<button
type='button'
onClick={onButtonClick}
disabled={buttonDisabled}
className={cn(
chipVariants({
variant: highlighted ? 'primary' : 'border-shadow',
fullWidth: true,
flush: true,
}),
'w-full justify-center'
)}
>
{buttonText}
</button>
{/* Credits + refresh stats block — omitted on plans without a fixed credit amount */}
{credits && (
<div className='flex flex-col gap-1.5'>
<div className='flex items-center gap-1.5'>
<Credit className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
<span className='text-[var(--text-body)] text-sm'>{credits}</span>
<Info>1 workflow run = 1 credit. Inference usage consumes credits separately.</Info>
</div>
{refresh && (
<div className='flex items-center gap-1.5'>
<RefreshCw className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
<span className='text-[var(--text-body)] text-sm'>{refresh}</span>
</div>
)}
</div>
)}
</div>
{/* Section header + divider matching integrations/skills separator language */}
<div className='flex flex-col'>
<span className='pl-0.5 text-[var(--text-muted)] text-small'>{segmentLabel}</span>
<div className='mt-[9px] mb-3 h-px bg-[var(--border)]' />
<ul className='flex flex-col gap-2'>
{features.map((feature) => (
<li key={feature} className='flex items-center gap-2'>
<Check className='size-[16px] flex-shrink-0 text-[var(--text-icon)]' />
<span className='text-[var(--text-body)] text-sm'>{feature}</span>
</li>
))}
</ul>
</div>
</article>
)
}