flnxi-Wu/app/activate/page.tsx
WuYongJie ba98bf22cc feat: 添加许可证系统、Agent部署模块和Harbor镜像管理
- 新增许可证服务器 (license-server/)
- 新增 Agent 部署模块 (flnxi-agent/)
- 添加许可证验证和激活页面
- 添加 Harbor 镜像同步脚本
- 更新素材系统支持许可证验证
2025-12-15 21:19:46 +08:00

154 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function ActivatePage() {
const router = useRouter();
const [licenseKey, setLicenseKey] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// 格式化许可证密钥输入(自动添加横杠)
const formatLicenseKey = (value: string) => {
const cleaned = value.toUpperCase().replace(/[^A-Z0-9]/g, '');
const parts = [];
for (let i = 0; i < cleaned.length && i < 16; i += 4) {
parts.push(cleaned.slice(i, i + 4));
}
return parts.join('-');
};
const handleKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setLicenseKey(formatLicenseKey(e.target.value));
setError('');
};
// 激活许可证
const handleActivate = async () => {
if (!licenseKey || licenseKey.length !== 19) {
setError('请输入完整的许可证密钥');
return;
}
setLoading(true);
setError('');
try {
const res = await fetch('/api/license/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ licenseKey }),
});
const data = await res.json();
if (data.success) {
router.push('/');
} else {
setError(data.error || '激活失败,请检查许可证密钥');
}
} catch {
setError('网络错误,请检查网络连接');
} finally {
setLoading(false);
}
};
// 免费试用
const handleFreeTrial = async () => {
setLoading(true);
setError('');
try {
const res = await fetch('/api/license/activate-free', {
method: 'POST',
});
const data = await res.json();
if (data.success) {
router.push('/');
} else {
setError(data.error || '激活失败');
}
} catch {
setError('网络错误,请检查网络连接');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="bg-white rounded-lg shadow-lg p-8">
{/* Logo */}
<div className="text-center mb-8">
<h1 className="text-2xl font-bold text-gray-900">FLNXI 素材系统</h1>
<p className="text-gray-500 mt-2">请激活您的许可证</p>
</div>
{/* 许可证输入 */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
许可证密钥
</label>
<input
type="text"
value={licenseKey}
onChange={handleKeyChange}
placeholder="XXXX-XXXX-XXXX-XXXX"
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-center text-lg tracking-wider font-mono"
maxLength={19}
disabled={loading}
/>
</div>
{/* 错误提示 */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg text-red-600 text-sm">
{error}
</div>
)}
{/* 激活按钮 */}
<button
onClick={handleActivate}
disabled={loading || licenseKey.length !== 19}
className="w-full py-3 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
>
{loading ? '激活中...' : '激活许可证'}
</button>
{/* 分隔线 */}
<div className="my-6 flex items-center">
<div className="flex-1 border-t border-gray-200"></div>
<span className="px-4 text-sm text-gray-400">或</span>
<div className="flex-1 border-t border-gray-200"></div>
</div>
{/* 免费试用 */}
<button
onClick={handleFreeTrial}
disabled={loading}
className="w-full py-3 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 disabled:cursor-not-allowed transition-colors"
>
免费试用
</button>
{/* 免费版说明 */}
<p className="mt-4 text-center text-xs text-gray-400">
免费版限制:10 个用户、100 个素材
</p>
</div>
{/* 联系信息 */}
<p className="mt-6 text-center text-sm text-gray-500">
需要购买许可证?请联系我们
</p>
</div>
</div>
);
}