mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-19 06:46:23 +00:00
fix(licensing): rename variant values to skipper/admiral and store resolved type (#379)
Rename internal variant values from 'personal'/'team' to 'skipper'/'admiral', aligning code with user-facing tier names. Variant type is now resolved once at activation/validation and stored in DB via license_variant_type, instead of string-matching the Lemon Squeezy variant_name on every read. Also captures variant_id for future lookups. Pre-existing installs auto-migrate on first getVariant() call.
This commit is contained in:
@@ -20,7 +20,7 @@ export function AdmiralGate({ children, featureName = 'This feature' }: AdmiralG
|
||||
const { isPaid, license } = useLicense();
|
||||
const [dismissed, setDismissed] = useState(isDismissedFromStorage);
|
||||
|
||||
if (isPaid && license?.variant === 'team') return <>{children}</>;
|
||||
if (isPaid && license?.variant === 'admiral') return <>{children}</>;
|
||||
|
||||
if (dismissed) {
|
||||
return (
|
||||
|
||||
@@ -223,7 +223,7 @@ export default function EditorLayout() {
|
||||
if (isPaid && isAdmin) {
|
||||
items.push({ value: 'auto-updates', label: 'Auto-Update', icon: RefreshCw });
|
||||
}
|
||||
if (isPaid && license?.variant === 'team') {
|
||||
if (isPaid && license?.variant === 'admiral') {
|
||||
if (isAdmin) items.push({ value: 'host-console', label: 'Console', icon: Terminal });
|
||||
if (can('system:audit')) items.push({ value: 'audit-log', label: 'Audit', icon: ScrollText });
|
||||
if (isAdmin) items.push({ value: 'scheduled-ops', label: 'Schedules', icon: Clock });
|
||||
|
||||
@@ -303,7 +303,7 @@ export function SettingsModal({ isOpen, onClose, initialSection }: SettingsModal
|
||||
}
|
||||
};
|
||||
|
||||
const isAdmiral = isPaid && license?.variant === 'team';
|
||||
const isAdmiral = isPaid && license?.variant === 'admiral';
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||
|
||||
@@ -11,15 +11,14 @@ interface TierBadgeProps {
|
||||
|
||||
const tierConfig = {
|
||||
community: { icon: Globe, label: 'Community' },
|
||||
paid: { icon: Compass, label: 'Skipper' },
|
||||
team: { icon: ShipWheel, label: 'Admiral' },
|
||||
skipper: { icon: Compass, label: 'Skipper' },
|
||||
admiral: { icon: ShipWheel, label: 'Admiral' },
|
||||
} as const;
|
||||
|
||||
function resolveTier(tier: LicenseTier, variant: LicenseVariant, status: LicenseStatus) {
|
||||
// Only show Team badge for active team licenses, not trials
|
||||
// (trials default to team variant to unlock all features)
|
||||
if (tier === 'paid' && variant === 'team' && status === 'active') return tierConfig.team;
|
||||
if (tier === 'paid') return tierConfig.paid;
|
||||
// Only show Admiral badge for active admiral licenses (trials default to skipper)
|
||||
if (tier === 'paid' && variant === 'admiral' && status === 'active') return tierConfig.admiral;
|
||||
if (tier === 'paid') return tierConfig.skipper;
|
||||
return tierConfig.community;
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ export function DeveloperSection({ settings, onSettingChange, onSave, isSaving,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isPaid && license?.variant === 'team' && (
|
||||
{isPaid && license?.variant === 'admiral' && (
|
||||
<div className="flex items-center justify-between gap-4 pt-4 border-t border-glass-border">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-base">Audit Log Retention</Label>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { apiFetch } from '@/lib/api';
|
||||
|
||||
function getTierDisplayName(tier?: string, variant?: string | null, status?: string): string {
|
||||
if (tier === 'paid' && variant === 'team' && status === 'active') return 'Sencho Admiral';
|
||||
if (tier === 'paid' && variant === 'admiral' && status === 'active') return 'Sencho Admiral';
|
||||
if (tier === 'paid') return 'Sencho Skipper';
|
||||
return 'Sencho Community';
|
||||
}
|
||||
@@ -42,9 +42,9 @@ export function LicenseSection() {
|
||||
}
|
||||
};
|
||||
|
||||
const isAdmiral = isPaid && license?.variant === 'team' && license?.status === 'active';
|
||||
const isAdmiral = isPaid && license?.variant === 'admiral' && license?.status === 'active';
|
||||
const showSkipperCard = !isPaid || license?.status === 'trial';
|
||||
const showUpgradeCards = !isAdmiral && (showSkipperCard || (license?.variant === 'personal' && license?.status === 'active'));
|
||||
const showUpgradeCards = !isAdmiral && (showSkipperCard || (license?.variant === 'skipper' && license?.status === 'active'));
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -210,9 +210,9 @@ export function LicenseSection() {
|
||||
<p className="text-xs text-muted-foreground">For teams managing shared infrastructure.</p>
|
||||
<ul className="space-y-1.5">
|
||||
{[
|
||||
...(license?.variant === 'personal' ? ['Everything in Skipper'] : ['Everything in Community']),
|
||||
...(license?.variant === 'skipper' ? ['Everything in Skipper'] : ['Everything in Community']),
|
||||
'Unlimited accounts & scoped RBAC',
|
||||
...(license?.variant !== 'personal' ? ['Fleet View, webhooks & labels', 'Atomic deployments & backups'] : []),
|
||||
...(license?.variant !== 'skipper' ? ['Fleet View, webhooks & labels', 'Atomic deployments & backups'] : []),
|
||||
'SSO, audit log & host console',
|
||||
'API tokens & private registries',
|
||||
'Scheduled operations',
|
||||
|
||||
@@ -49,17 +49,17 @@ export function SupportSection() {
|
||||
Priority Support <TierBadge />
|
||||
</h4>
|
||||
<div className="grid gap-3">
|
||||
<a href={license?.variant === 'team' ? 'mailto:support@sencho.io' : 'mailto:licensing@sencho.io'}
|
||||
<a href={license?.variant === 'admiral' ? 'mailto:support@sencho.io' : 'mailto:licensing@sencho.io'}
|
||||
className="flex items-center gap-3 p-3 rounded-lg border border-glass-border hover:bg-muted/50 transition-colors">
|
||||
<div className="w-9 h-9 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
||||
<Mail className="w-4 h-4" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium">
|
||||
{license?.variant === 'team' ? 'Priority Email Support' : 'Email Support'}
|
||||
{license?.variant === 'admiral' ? 'Priority Email Support' : 'Email Support'}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{license?.variant === 'team'
|
||||
{license?.variant === 'admiral'
|
||||
? 'Direct support with responses within 24 hours'
|
||||
: 'Reach our support team directly'}
|
||||
</p>
|
||||
|
||||
@@ -267,7 +267,7 @@ export function UsersSection() {
|
||||
<SelectContent>
|
||||
<SelectItem value="admin">Admin</SelectItem>
|
||||
<SelectItem value="viewer">Viewer</SelectItem>
|
||||
{isPaid && license?.variant === 'team' && (
|
||||
{isPaid && license?.variant === 'admiral' && (
|
||||
<>
|
||||
<SelectItem value="deployer">Deployer</SelectItem>
|
||||
<SelectItem value="node-admin">Node Admin</SelectItem>
|
||||
@@ -306,7 +306,7 @@ export function UsersSection() {
|
||||
</div>
|
||||
|
||||
{/* Scoped Permissions (Admiral, editing only) */}
|
||||
{editingUser && isPaid && license?.variant === 'team' && (
|
||||
{editingUser && isPaid && license?.variant === 'admiral' && (
|
||||
<div className="border border-glass-border rounded-lg p-4 space-y-3 mt-4">
|
||||
<h4 className="text-sm font-medium">Scoped Permissions</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user