mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-31 20:58:04 +00:00
feat(ui): hide paid features from community-tier dashboard (#891)
* feat(ui): hide paid features from community-tier dashboard Community installs render only the features they can use. Tier-locked sections, lock badges, upsell cards, and "Upgrade" buttons no longer appear anywhere except the License page in Settings, which is the single discoverable upgrade path. Concretely: - PaidGate and AdmiralGate now render null for non-qualifying tiers instead of upsell cards. - SectionGate (settings) hides tier-locked sections entirely. - Settings sidebar and command palette filter out items the operator cannot reach. - Configuration Status widget on the dashboard drops the Automation section for community and hides any locked rows in remaining sections. - Fleet > Status node cards drop locked summary rows. - Stack action menu, sidebar bulk bar, file upload / download, scan comparison, network topology toggle, node label picker all hide for community instead of showing disabled affordances or "Upgrade" literal text. - Removes tierUpsell, TierLockChip, and useDismissalState (no longer referenced). Backend tier guards remain authoritative; this changes UI discovery only. * test(e2e): assert upload control is absent in community tier The community-clean-ui change removes the "Upgrade to unlock upload" pill from the file explorer. Update the matching e2e assertion to verify the upload control is not rendered, instead of waiting for a pill that no longer exists.
This commit is contained in:
@@ -1,65 +1,18 @@
|
||||
import { Compass } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useLicense } from '@/context/LicenseContext';
|
||||
import { useDismissalState } from '@/hooks/useDismissalState';
|
||||
import {
|
||||
CompactBlurredLock,
|
||||
DismissedPill,
|
||||
FullUpsellCard,
|
||||
type TierGateProps,
|
||||
} from './tierUpsell';
|
||||
|
||||
const DISMISS_KEY = 'sencho-upgrade-prompt-dismissed';
|
||||
|
||||
/**
|
||||
* Gate for any paid-tier feature (Skipper or Admiral). Composes the
|
||||
* shared tier-upsell primitives in `./tierUpsell` according to the
|
||||
* current state:
|
||||
* Thin wrapper that renders its children only for licensees on a paid
|
||||
* plan (Skipper or Admiral). Community-tier users see nothing in this
|
||||
* slot. Backend tier guards (`requirePaid`) remain the authoritative
|
||||
* enforcement; this component only controls UI visibility.
|
||||
*
|
||||
* isPaid render children (unlocked).
|
||||
* compact blurred children + small pill (inline list items).
|
||||
* dismissed pill-only placeholder for 24h, click to restore.
|
||||
* default full upsell card with View Plans CTA.
|
||||
*
|
||||
* The compact branch retains the blurred-children render because it is
|
||||
* used for tiny inline UI where the visual continuity is intentional.
|
||||
* The dismissed and default branches do not render children, so any
|
||||
* lazy chunks behind the gate are never fetched on those paths.
|
||||
* Use only when wrapping a discrete fragment that has no neighboring
|
||||
* context for Community users. Where possible, prefer a parent-level
|
||||
* `useLicense().isPaid` check that lifts the visibility decision out
|
||||
* of the rendering tree entirely.
|
||||
*/
|
||||
export function PaidGate({ children, featureName = 'This feature', compact = false }: TierGateProps) {
|
||||
export function PaidGate({ children }: { children: ReactNode }) {
|
||||
const { isPaid } = useLicense();
|
||||
const { dismissed, dismiss, restore } = useDismissalState(DISMISS_KEY);
|
||||
|
||||
if (isPaid) return <>{children}</>;
|
||||
|
||||
const pillText = `Upgrade to unlock ${featureName}`;
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<CompactBlurredLock icon={Compass} pillText={pillText}>
|
||||
{children}
|
||||
</CompactBlurredLock>
|
||||
);
|
||||
}
|
||||
|
||||
if (dismissed) {
|
||||
return <DismissedPill icon={Compass} pillText={pillText} onClick={restore} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<FullUpsellCard
|
||||
icon={Compass}
|
||||
title={`${featureName} requires a paid license`}
|
||||
body={
|
||||
<>
|
||||
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{' '}
|
||||
<a href="mailto:licensing@sencho.io" className="text-brand hover:underline">licensing@sencho.io</a>.
|
||||
</>
|
||||
}
|
||||
ctaIcon={Compass}
|
||||
ctaLabel="View Plans"
|
||||
ctaHref="https://sencho.io/pricing"
|
||||
onDismiss={dismiss}
|
||||
/>
|
||||
);
|
||||
return isPaid ? <>{children}</> : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user