Files
resolutionflow/frontend/src/components/common/UpgradePrompt.tsx
Michael Chihlas f1be3abcc5
Some checks failed
CI / e2e (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / backend (push) Has been cancelled
Mirror to GitHub / mirror (push) Has been cancelled
feat: self-serve signup Phase 2 (frontend cutover) (#162)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com>
Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
2026-05-07 18:42:20 +00:00

112 lines
3.3 KiB
TypeScript

import { Lock, Sparkles } from 'lucide-react'
import { Link } from 'react-router-dom'
import { cn } from '@/lib/utils'
interface UpgradePromptProps {
feature: string
className?: string
}
interface FeatureMeta {
/** Display name shown in the prompt heading. */
displayName: string
/** Plan that unlocks this feature. */
requiredPlan: string
/** Optional one-line value pitch. */
description?: string
}
/**
* Mapping from feature flag key to display metadata.
*
* v1: small inline table maintained here. If this grows, lift to
* `frontend/src/lib/featureCatalog.ts` and source from a backend endpoint.
*
* Keys must match `feature_flags.flag_key` on the backend.
*/
const FEATURE_CATALOG: Record<string, FeatureMeta> = {
psa_integration: {
displayName: 'PSA Integration',
requiredPlan: 'Pro',
description: 'Sync tickets and assets with your PSA in real time.',
},
kb_accelerator: {
displayName: 'Knowledge Base Accelerator',
requiredPlan: 'Pro',
description: 'Auto-generate troubleshooting flows from your existing KB.',
},
ai_builder: {
displayName: 'AI Builder',
requiredPlan: 'Pro',
description: 'Generate decision trees from natural-language prompts.',
},
branching_logic: {
displayName: 'Branching Logic',
requiredPlan: 'Pro',
},
custom_branding: {
displayName: 'Custom Branding',
requiredPlan: 'Pro',
},
api_access: {
displayName: 'API Access',
requiredPlan: 'Pro',
},
sso: {
displayName: 'Single Sign-On',
requiredPlan: 'Enterprise',
},
}
/** Humanize an unknown feature key for the fallback display name. */
function humanizeFeatureKey(key: string): string {
return key
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ')
}
/**
* Standardized "this feature is on Pro" affordance.
*
* Renders a locked panel with a CTA that routes to the plan-selection page.
* The actual gating is enforced server-side via `require_feature` — this is UX.
*/
export function UpgradePrompt({ feature, className }: UpgradePromptProps) {
const meta = FEATURE_CATALOG[feature]
const displayName = meta?.displayName ?? humanizeFeatureKey(feature)
const requiredPlan = meta?.requiredPlan ?? 'Pro'
const description = meta?.description
return (
<div
className={cn(
'flex flex-col items-center justify-center gap-3 rounded-lg border border-default bg-white/[0.04] px-6 py-10 text-center',
className,
)}
data-testid="upgrade-prompt"
>
<div className="flex h-10 w-10 items-center justify-center rounded-full border border-default bg-elevated text-muted-foreground">
<Lock className="h-4 w-4" aria-hidden="true" />
</div>
<div className="space-y-1">
<h3 className="text-base font-semibold text-heading">
{displayName} is available on {requiredPlan}
</h3>
{description && (
<p className="max-w-md text-sm text-muted-foreground">{description}</p>
)}
</div>
<Link
to="/account/billing/select-plan"
className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
>
<Sparkles className="h-4 w-4" aria-hidden="true" />
Upgrade to {requiredPlan}
</Link>
</div>
)
}
export default UpgradePrompt