170 lines
5.8 KiB
TypeScript
170 lines
5.8 KiB
TypeScript
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import { cn } from '@/lib/ui-styles';
|
||
|
||
interface ActivationModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
onSuccess?: () => void;
|
||
}
|
||
|
||
export default function ActivationModal({ isOpen, onClose, onSuccess }: ActivationModalProps) {
|
||
const [activationCode, setActivationCode] = useState('');
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
if (!isOpen) return null;
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
setError(null);
|
||
|
||
const code = activationCode.trim().toUpperCase();
|
||
if (!code) {
|
||
setError('请输入激活码');
|
||
return;
|
||
}
|
||
|
||
// 简单格式验证
|
||
if (!code.startsWith('FLNXI-')) {
|
||
setError('激活码格式不正确');
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
|
||
try {
|
||
const response = await fetch('/api/license/activate', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ activationCode: code }),
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
onSuccess?.();
|
||
onClose();
|
||
// 刷新页面以更新许可证状态
|
||
window.location.reload();
|
||
} else {
|
||
setError(data.error || '激活失败,请检查激活码是否正确');
|
||
}
|
||
} catch (err) {
|
||
setError('网络错误,请稍后重试');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handlePurchase = () => {
|
||
// 跳转到官网购买页面
|
||
window.open('https://www.flnxi.com/pricing', '_blank');
|
||
};
|
||
|
||
return (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||
{/* 背景遮罩 */}
|
||
<div
|
||
className="absolute inset-0 bg-black/45"
|
||
onClick={onClose}
|
||
/>
|
||
|
||
{/* 弹窗内容 */}
|
||
<div className="relative bg-white dark:bg-slate-800 rounded-xl shadow-lg border border-slate-200 dark:border-slate-700 w-full max-w-md mx-4 overflow-hidden">
|
||
{/* 头部 */}
|
||
<div className="px-6 py-4 border-b border-slate-200 dark:border-slate-700">
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">
|
||
激活许可证
|
||
</h3>
|
||
<button
|
||
onClick={onClose}
|
||
className="p-1 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors"
|
||
>
|
||
<svg className="w-5 h-5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 内容 */}
|
||
<form onSubmit={handleSubmit} className="p-6 space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||
激活码
|
||
</label>
|
||
<input
|
||
type="text"
|
||
value={activationCode}
|
||
onChange={(e) => setActivationCode(e.target.value.toUpperCase())}
|
||
placeholder="FLNXI-PRO-XXXX-XXXX-XXXX-XXXX"
|
||
className={cn(
|
||
'w-full px-4 py-3 rounded-xl border text-sm font-mono',
|
||
'bg-slate-50 dark:bg-slate-900',
|
||
'border-slate-200 dark:border-slate-700',
|
||
'text-slate-900 dark:text-white',
|
||
'placeholder:text-slate-400 dark:placeholder:text-slate-500',
|
||
'focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent',
|
||
'transition-all'
|
||
)}
|
||
autoFocus
|
||
/>
|
||
<p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
|
||
激活码格式:FLNXI-PRO-XXXX-XXXX-XXXX-XXXX
|
||
</p>
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="px-4 py-3 rounded-xl bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800">
|
||
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
||
</div>
|
||
)}
|
||
|
||
<div className="flex gap-3">
|
||
<button
|
||
type="submit"
|
||
disabled={loading}
|
||
className={cn(
|
||
'flex-1 px-4 py-3 rounded-xl font-medium text-sm transition-all',
|
||
'bg-indigo-600 hover:bg-indigo-700 text-white',
|
||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||
)}
|
||
>
|
||
{loading ? (
|
||
<span className="flex items-center justify-center gap-2">
|
||
<svg className="w-4 h-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||
</svg>
|
||
激活中...
|
||
</span>
|
||
) : (
|
||
'立即激活'
|
||
)}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
{/* 底部 */}
|
||
<div className="px-6 py-4 bg-slate-50 dark:bg-slate-900/50 border-t border-slate-200 dark:border-slate-700">
|
||
<div className="flex items-center justify-between">
|
||
<p className="text-sm text-slate-500 dark:text-slate-400">
|
||
还没有激活码?
|
||
</p>
|
||
<button
|
||
type="button"
|
||
onClick={handlePurchase}
|
||
className="text-sm font-medium text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 transition-colors"
|
||
>
|
||
前往购买 →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|