import { useState } from 'react'
import {
Box,
Divider,
LinearProgress,
TextField,
} from '@mui/material'
import { CircleCheck, XCircle, KeyRound, Sparkles } from 'lucide-react'
import { Led, PhosphorText, PhysicalButton, MetalCard } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roFontMono, d3roTypo, d3roRadius } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
import { PREMIUM_MODEL_LIMITS } from '@d3ro/core/constants'
import { useLicenseState } from '../hooks/useLicenseState'
export function LicenseTab(): React.ReactElement {
const { t } = useI18n()
const {
licenseInfo, usage, comparison, cloud,
currentTier, isFree, isPro,
handleUpgrade, handleOpenBilling, openCloudSettings,
} = useLicenseState()
return (
{/* ── 현재 플랜 + 계정 상태 ── */}
{t('license.currentTier')}
{cloud.authenticated && cloud.userEmail && (
{cloud.userEmail}
)}
{/* ── 14일 Reverse-Trial 상태 배너 (활성화 시) ── */}
{licenseInfo?.isTrial && licenseInfo.trialExpiresAt && (
14-Day Reverse Trial (Pro+)
{Math.max(0, Math.ceil((licenseInfo.trialExpiresAt - Date.now()) / (24 * 60 * 60 * 1000)))}일 남음 — 만료 시 100% 로컬 무료 모드로 안전하게 전환됩니다.
)}
{/* ── 구독 관리 / 업그레이드 ── */}
{/* ── 오프라인 라이선스 키 등록 ── */}
{/* ── 일일 사용량 ── */}
{/* ── 티어 비교표 ── */}
)
}
// ── 하위 컴포넌트 ──────────────────────────────────────
function TierLabel({ tier, t }: { tier: string; t: (k: string) => string }): React.ReactElement {
const color = tier === 'enterprise' || tier === 'team'
? d3roPalette.accent.main
: tier === 'pro_plus'
? d3roPalette.tag.purple
: tier === 'pro'
? d3roPalette.tag.green
: d3roPalette.accent.main
const label = tier === 'enterprise'
? 'Enterprise'
: tier === 'team'
? 'Team'
: tier === 'pro_plus'
? t('license.proPlus')
: tier === 'pro'
? t('license.pro')
: t('license.free')
return {label}
}
function LicenseKeySection({ currentTier }: { currentTier: string }): React.ReactElement {
const [licenseKeyInput, setLicenseKeyInput] = useState('')
const [feedback, setFeedback] = useState<{ success: boolean; message: string } | null>(null)
const [loading, setLoading] = useState(false)
const handleActivateKey = async () => {
if (!licenseKeyInput.trim()) return
setLoading(true)
setFeedback(null)
try {
const res = await window.electronAPI.license.activate({ licenseKey: licenseKeyInput.trim() })
if (res.success && res.data.success) {
setFeedback({ success: true, message: res.data.message || '라이센스가 성공적으로 활성화되었습니다.' })
setLicenseKeyInput('')
} else {
setFeedback({ success: false, message: (res.success && res.data.message) ? res.data.message : '유효하지 않은 라이센스 키입니다.' })
}
} catch (err) {
setFeedback({ success: false, message: `활성화 실패: ${err}` })
} finally {
setLoading(false)
}
}
const handleDeactivate = async () => {
setLoading(true)
try {
await window.electronAPI.license.deactivate()
setFeedback({ success: true, message: '라이센스가 비활성화되어 Free 티어로 전환되었습니다.' })
} finally {
setLoading(false)
}
}
return (
오프라인 라이센스 키 활성화 (Ed25519)
발급받은 Ed25519 디지털 서명 라이센스 키 (D3RO-LIC-...)를 입력하세요.
setLicenseKeyInput(e.target.value)}
disabled={loading}
sx={{
fontFamily: d3roFontMono,
'& .MuiOutlinedInput-root': {
bgcolor: d3roPalette.bg.inset,
borderRadius: d3roRadius.button,
fontSize: d3roTypo.small.size,
fontFamily: d3roFontMono,
color: d3roPalette.text.primary,
'& fieldset': { borderColor: d3roPalette.border.subtle },
'&:hover fieldset': { borderColor: d3roPalette.accent.main },
},
}}
/>
등록
{currentTier !== 'free' && (
해제
)}
{feedback && (
{feedback.message}
)}
)
}
interface SubscriptionSectionProps {
t: (k: string) => string
authenticated: boolean
isFree: boolean
isPro: boolean
currentTier: string
onUpgrade: (tier: 'pro' | 'pro_plus') => void
onManage: () => void
onSignIn: () => void
}
function SubscriptionSection({ t, authenticated, isFree, isPro, onUpgrade, onManage, onSignIn }: SubscriptionSectionProps): React.ReactElement {
if (!authenticated) {
return (
{t('license.signInRequired')}
{t('license.signInToUpgrade')}
)
}
if (isFree) {
return (
{t('license.subscribe')}
{t('license.upgradeToProDesc')}
)
}
if (isPro) {
return (
{t('license.subscriptionManage')}
)
}
if (currentTier === 'team') {
return (
{t('license.subscriptionManage')}
)
}
if (currentTier === 'enterprise') {
return (
{t('license.subscriptionManage')}
)
}
// Pro+
return (
{t('license.subscriptionManage')}
)
}
function ActivePlanIndicator({ label, color }: { label: string; color: string }): React.ReactElement {
return (
{label}
)
}
function UpgradeButton({ tier, t, onUpgrade }: { tier: 'pro' | 'pro_plus'; t: (k: string) => string; onUpgrade: (t: 'pro' | 'pro_plus') => void }): React.ReactElement {
const planLabel = tier === 'pro_plus' ? t('license.proPlusPlan') : t('license.proPlan')
return (
onUpgrade(tier)} sx={{ width: '100%' }}>
{t('license.upgrade')} — {planLabel}
)
}
function UsageSection({ t, usage, currentTier }: { t: (k: string) => string; usage: Array<{ feature: string; used: number; limit: number; remaining: number }>; currentTier: string }): React.ReactElement | null {
if (usage.length === 0) return null
return (
<>
{t('license.dailyUsage')}
{usage.map((q) => (
))}
{/* Premium 모델별 쿼터 */}
{PREMIUM_MODEL_LIMITS[currentTier as keyof typeof PREMIUM_MODEL_LIMITS]?.length > 0 && (
<>
{t('license.premiumQuota')}
{PREMIUM_MODEL_LIMITS[currentTier as keyof typeof PREMIUM_MODEL_LIMITS].map((m) => (
{t(m.i18nKey)}
{m.limit === -1
? t('license.unlimited')
: `${m.limit}/${t(m.period === 'weekly' ? 'license.quotaWeekly' : 'license.quotaDaily')}`}
{m.limit > 0 && (
)}
))}
>
)}
>
)
}
function QuotaBar({ t, feature, used, limit, remaining }: { t: (k: string, p?: Record) => string; feature: string; used: number; limit: number; remaining: number }): React.ReactElement {
return (
{t(`license.feature.${feature}`)}
{limit === -1
? t('license.unlimited')
: t('license.quotaUsed', { used: String(used), limit: String(limit) })}
{limit > 0 && (
= limit ? d3roPalette.tag.red : d3roPalette.accent.main,
borderRadius: d3roRadius.xs,
},
}}
/>
)}
)
}
function ComparisonTable({ t, comparison }: { t: (k: string) => string; comparison: Array<{ feature: string; featureLabel: string; free: boolean | string; pro: boolean | string; proPlus: boolean | string }> }): React.ReactElement | null {
if (comparison.length === 0) return null
return (
<>
{t('license.tierComparison')}
| {''} |
{t('license.free')} |
{t('license.pro')} |
{t('license.proPlus')} |
{comparison.map((row) => (
| {t(row.featureLabel)} |
|
|
|
))}
>
)
}
function TierCell({ value }: { value: boolean | string }): React.ReactElement {
if (value === true) {
return
}
if (value === false) {
return
}
return (
{value}
)
}