// LockCard v2 — landing-and-mode-orchestration-redesign decision 3. // // Covers the Chat tab answer area in two states: // variant = 'anonymous' → 🔒 + Google login CTA + 「先用語意搜尋」secondary link // variant = 'quota_exhausted' → ⏳ + Apply-for-quota CTA + 「先用語意搜尋」secondary link // // Copy is sourced from i18n.jsx UI_STRINGS (lockcard_anon_* / lockcard_quota_*) // and is the definitive 定版 — must NOT contain「免費」「N 次」「重置」「自動恢復」 // or any concrete reset-time language (design.md non-goal §3). // // Props: // variant : 'anonymous' | 'quota_exhausted' (unknown → renders fallback) // lang : 'zh' | 'en' // onLogin : called when anon CTA clicked // onApplyQuota : called when quota_exhausted CTA clicked (opens QuotaApplyModal) // onTrySemantic : called when「先用語意搜尋」secondary link clicked // // Test ids (per task 5.4): // data-testid="lockcard" (root) // data-testid="lockcard-cta-primary" (main button) // data-testid="lockcard-cta-semantic" (secondary link) const LockCard = ({ variant, lang, onLogin, onApplyQuota, onTrySemantic }) => { const t = lang === 'zh'; if (variant !== 'anonymous' && variant !== 'quota_exhausted') { // Defensive fallback per design Failure Modes — render minimal「請登入」 return (
{t ? '請登入後繼續使用對話模式。' : 'Please sign in to use chat mode.'}
); } const isAnon = variant === 'anonymous'; const emoji = uiString(isAnon ? 'lockcard_anon_emoji' : 'lockcard_quota_emoji', lang); const title = uiString(isAnon ? 'lockcard_anon_title' : 'lockcard_quota_title', lang); const body = uiString(isAnon ? 'lockcard_anon_body' : 'lockcard_quota_body', lang); const ctaLabel = uiString(isAnon ? 'lockcard_anon_cta' : 'lockcard_quota_cta', lang); const secPrefix = uiString('lockcard_anon_secondary_prefix', lang); const secLink = uiString('lockcard_anon_secondary_link', lang); const secSuffix = uiString('lockcard_anon_secondary_suffix', lang); const handlePrimary = () => { if (isAnon) { onLogin && onLogin(); } else { onApplyQuota && onApplyQuota(); } }; return (
{emoji}

{title}

{body}

{ctaLabel}
{secPrefix} {secSuffix}
); }; Object.assign(window, { LockCard });