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:
Anso
2026-05-03 01:17:02 -04:00
committed by GitHub
parent b9ada7f50b
commit 1f8ce773ff
34 changed files with 196 additions and 806 deletions
+23 -22
View File
@@ -984,27 +984,28 @@ export default function ResourcesView() {
/>
)}
<div className="flex items-center gap-2 pr-3">
<div className="flex items-center gap-0.5 bg-muted/50 rounded-lg p-0.5">
<button
onClick={() => setNetworkViewMode('list')}
className={cn(
'px-2.5 py-1 rounded-md text-xs font-medium transition-all duration-200',
networkViewMode === 'list' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
)}
>
List
</button>
<button
onClick={() => setNetworkViewMode('topology')}
className={cn(
'px-2.5 py-1 rounded-md text-xs font-medium transition-all duration-200 flex items-center gap-1',
networkViewMode === 'topology' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
)}
>
Topology
{!isPaid && <Badge variant="outline" className="text-[9px] h-4 px-1 border-brand/30 text-brand">Skipper</Badge>}
</button>
</div>
{isPaid && (
<div className="flex items-center gap-0.5 bg-muted/50 rounded-lg p-0.5">
<button
onClick={() => setNetworkViewMode('list')}
className={cn(
'px-2.5 py-1 rounded-md text-xs font-medium transition-all duration-200',
networkViewMode === 'list' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
)}
>
List
</button>
<button
onClick={() => setNetworkViewMode('topology')}
className={cn(
'px-2.5 py-1 rounded-md text-xs font-medium transition-all duration-200 flex items-center gap-1',
networkViewMode === 'topology' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'
)}
>
Topology
</button>
</div>
)}
{isAdmin && networkViewMode === 'list' && (
<Button
variant="outline"
@@ -1021,7 +1022,7 @@ export default function ResourcesView() {
{networkViewMode === 'topology' ? (
<div className="p-4">
<PaidGate featureName="Network Topology">
<PaidGate>
<CapabilityGate capability="network-topology" featureName="Network Topology">
<LazyBoundary>
<Suspense fallback={