fix(license): shared invalid/grace banners + instance copy
Surface expired/over_instance/revoked red banners with CTAs via a shared LicenseStatusBanners used by settings and ProGate. Hide seats in product UI; keep HMAC dual-path and never silent-clear keys.
This commit is contained in:
1 parent
af0298e994
commit
62d9b81a70
10 files changed
+265
-45
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { memo, useState } from 'react'
|
||||
import { memo, useRef, useState } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Lock, Sparkles } from 'lucide-react'
|
||||
@@ -6,16 +6,23 @@ import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { LicenseStatusBanners } from '@/components/LicenseStatusBanners'
|
||||
import { licenseApi } from '@/api/license'
|
||||
import { useLicense } from '@/hooks/useLicense'
|
||||
|
||||
/** Pro 功能升级引导卡:免费版进入 Pro 页面时整页替代展示 */
|
||||
export const ProGate = memo(function ProGate({ featureKey }: { featureKey: string }) {
|
||||
const { t } = useTranslation('license')
|
||||
const { reload } = useLicense()
|
||||
const { pro, info, reload } = useLicense()
|
||||
const navigate = useNavigate()
|
||||
const [key, setKey] = useState('')
|
||||
const [activating, setActivating] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const focusKey = () => {
|
||||
inputRef.current?.focus()
|
||||
inputRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
}
|
||||
|
||||
const activate = async () => {
|
||||
if (!key.trim()) return
|
||||
@@ -33,6 +40,7 @@ export const ProGate = memo(function ProGate({ featureKey }: { featureKey: strin
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl space-y-4 pt-8">
|
||||
<LicenseStatusBanners info={info} pro={pro} onAction={focusKey} />
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
@@ -50,6 +58,7 @@ export const ProGate = memo(function ProGate({ featureKey }: { featureKey: strin
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* 价卡暂留;product/plan 建好后再对齐 trial/年付/永久 */}
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div className="rounded-lg border p-4">
|
||||
<p className="text-sm font-semibold">{t('pricing.personalTitle')}</p>
|
||||
@@ -63,7 +72,13 @@ export const ProGate = memo(function ProGate({ featureKey }: { featureKey: strin
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Input value={key} onChange={e => setKey(e.target.value)} placeholder={t('gate.keyPlaceholder')} className="font-mono text-sm" />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={key}
|
||||
onChange={e => setKey(e.target.value)}
|
||||
placeholder={t('gate.keyPlaceholder')}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
<Button onClick={() => void activate()} disabled={activating || !key.trim()}>{t('gate.activate')}</Button>
|
||||
<Button variant="outline" onClick={() => navigate('/settings')}>{t('gate.toSettings')}</Button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user