// src/renderer/components/LicenseModal.tsx // 구독 기반 라이선스 모달 — 클라우드 인증 + Payple 결제 연동 import { Dialog, DialogTitle, DialogContent, Box, IconButton, Divider, LinearProgress, } from '@mui/material' import { X, CircleCheck, XCircle } from 'lucide-react' import { MetalCard, PhosphorText, Led, PhysicalButton } from '@d3ro/ui/components/ds' import { d3roPalette, d3roTypo, d3roRadius, d3roShadow, d3roFontMono } from '@d3ro/ui/theme' import { useI18n } from '@d3ro/i18n' import { PREMIUM_MODEL_LIMITS } from '@d3ro/core/constants' import { useLicenseState } from '../hooks/useLicenseState' interface LicenseModalProps { open: boolean onClose: () => void } export function LicenseModal({ open, onClose }: LicenseModalProps): React.ReactElement { const { t } = useI18n() const { cloud, usage, comparison, currentTier, isFree, isPro, handleUpgrade, handleOpenBilling, openCloudSettings, } = useLicenseState() return ( {t('license.title')} {/* ── 현재 티어 ── */} {t('license.currentTier')} {cloud.authenticated && cloud.userEmail && ( {cloud.userEmail} )} {/* ── 구독 관리 ── */} { onClose(); openCloudSettings() }} /> {/* ── 사용량 ── */} {usage.length > 0 && ( {t('license.dailyUsage')} {usage.map((q) => ( {t(`license.feature.${q.feature}` as Parameters[0])} {q.limit === -1 ? t('license.unlimited') : t('license.quotaUsed', { used: String(q.used), limit: String(q.limit) })} {q.limit > 0 && ( = q.limit ? d3roPalette.tag.red : d3roPalette.accent.main, borderRadius: d3roRadius.xs, }, }} /> )} ))} {/* 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 as Parameters[0])} {m.limit === -1 ? t('license.unlimited') : `${m.limit}/${t(m.period === 'weekly' ? 'license.quotaWeekly' : 'license.quotaDaily')}`} {m.limit > 0 && ( )} ))} > )} )} {/* ── 티어 비교표 ── */} {comparison.length > 0 && ( {t('license.tierComparison')} {''} {t('license.free')} {t('license.pro')} {t('license.proPlus')} {comparison.map((row) => ( {t(row.featureLabel as Parameters[0])} ))} )} ) } // ── 공유 하위 컴포넌트 ────────────────────────────────── 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} } interface SubscriptionSectionProps { t: (k: string) => string authenticated: boolean isFree: boolean isPro: boolean 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')} ) } 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 TierCell({ value }: { value: boolean | string }): React.ReactElement { if (value === true) { return } if (value === false) { return } return ( {value} ) }