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
@@ -1,7 +1,5 @@
import { X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { TierBadge } from '@/components/TierBadge';
import type { BulkAction } from '@/hooks/useBulkStackActions';
interface SidebarBulkBarProps {
@@ -29,24 +27,8 @@ export function SidebarBulkBar({ selectedCount, isPaid, onAction, onClear }: Sid
<Button variant="outline" size="sm" className="h-6 px-2 text-[10px] font-mono" onClick={() => onAction('start')}>Start</Button>
<Button variant="outline" size="sm" className="h-6 px-2 text-[10px] font-mono" onClick={() => onAction('stop')}>Stop</Button>
<Button variant="outline" size="sm" className="h-6 px-2 text-[10px] font-mono" onClick={() => onAction('restart')}>Restart</Button>
{isPaid ? (
{isPaid && (
<Button variant="outline" size="sm" className="h-6 px-2 text-[10px] font-mono" onClick={() => onAction('update')}>Update</Button>
) : (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Button variant="outline" size="sm" className="h-6 px-2 text-[10px] font-mono gap-1 pointer-events-none opacity-60" disabled>
Update
<TierBadge tier="paid" />
</Button>
</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<p>Bulk update requires a Skipper license</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
</div>