import { type ReactNode, useState } from 'react'; import { Compass } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useLicense } from '@/context/LicenseContext'; interface PaidGateProps { children: ReactNode; featureName?: string; // Inline compact lock for list items (e.g. a single SSO provider card). Skips // the full-page upsell and dismiss timer; always renders the blurred + pill style. compact?: boolean; } const DISMISS_KEY = 'sencho-upgrade-prompt-dismissed'; const DISMISS_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours (session-like) function isDismissedFromStorage(): boolean { const dismissedAt = localStorage.getItem(DISMISS_KEY); return !!dismissedAt && Date.now() - parseInt(dismissedAt, 10) < DISMISS_DURATION_MS; } export function PaidGate({ children, featureName = 'This feature', compact = false }: PaidGateProps) { const { isPaid } = useLicense(); const [dismissed, setDismissed] = useState(isDismissedFromStorage); if (isPaid) return <>{children}; // Inline lock for list items (single SSO provider card, etc.). Renders the // children blurred behind a small pill so the surrounding list keeps its // shape; the IP exposure is minor because the children are tiny inline UI, // and the visual continuity is intentional. Dismissal does not apply here. if (compact) { return (
{children}
Upgrade to unlock {featureName}
); } // Post-dismissal placeholder for full-page consumers. Renders only the // pill so the lazy-loaded children behind a paid view never mount during // the dismissal window. Clicking the pill clears the dismissal flag and // restores the full upsell card so users who dismissed accidentally or // want to revisit pricing have a way back without clearing localStorage. if (dismissed) { return (
); } return (

{featureName} requires a paid license

Unlock features like fleet management, viewer accounts, one-click Google / GitHub / Okta SSO, and more with a Skipper or Admiral license. For enterprise pricing or questions, contact{' '} licensing@sencho.io.

); }