Co-authored-by: Michael Chihlas <michael@resolutionflow.com> Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
112 lines
3.3 KiB
TypeScript
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
|