Files
ok_r/components/init-wizard.tsx
karlcarlo 7948b1afc7 feat: 实现用户登录认证与权限控制系统
- 新增用户登录页面与API端点(/api/auth/login、/api/auth/logout、/api/auth/me)
- 引入基于密码哈希的认证机制,支持默认管理员账号登录
- 创建访问控制模块(lib/access-control.ts),实现基于角色的权限验证
- 在现有API端点中集成权限检查,限制非管理员用户只能访问其所属项目数据
- 更新应用布局,要求用户登录后才能访问主界面
- 扩展用户表结构,增加用户名和密码哈希字段
- 调整项目、KR、目标、会议记录等资源的查询逻辑,根据用户权限过滤数据
- 改进管理页面(指标、模板)的删除操作,使用确认对话框替代直接确认
- 优化“我的KR”页面,为管理员提供全员KR概览视图
- 修复.gitignore文件中的拼写错误(.trae → .travis)
- 添加数据库迁移脚本,支持任务直接关联项目、禁用RLS、添加求助字段等
- 配置Next.js以支持独立输出和子路径部署
2026-01-28 12:57:36 +08:00

107 lines
3.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 { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Target, Loader2, Sparkles, CheckCircle } from 'lucide-react';
interface InitWizardProps {
onComplete: () => void;
}
export function InitWizard({ onComplete }: InitWizardProps) {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const handleInit = async () => {
setIsLoading(true);
setError(null);
try {
const res = await fetch('/api/seed', { method: 'POST' });
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || '初始化失败');
}
if (data.ownerId) {
localStorage.setItem('currentUserId', data.ownerId);
}
setSuccess(true);
setTimeout(() => {
onComplete();
}, 1500);
} catch (err) {
setError(err instanceof Error ? err.message : '初始化失败');
} finally {
setIsLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 p-4">
<Card className="w-full max-w-lg">
<CardHeader className="text-center space-y-4">
<div className="mx-auto w-16 h-16 bg-teal-100 rounded-full flex items-center justify-center">
<Target className="h-8 w-8 text-teal-600" />
</div>
<CardTitle className="text-2xl">欢迎使用 兜爸目标管理</CardTitle>
<CardDescription className="text-base">
系统检测到数据库为空,点击下方按钮初始化演示数据,开始体验完整功能
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{success ? (
<div className="flex flex-col items-center gap-3 py-4 text-emerald-600">
<CheckCircle className="h-12 w-12" />
<p className="font-medium">初始化成功!正在跳转...</p>
</div>
) : (
<>
<div className="bg-slate-50 rounded-lg p-4 space-y-2 text-sm text-slate-600">
<p className="font-medium text-slate-800">演示数据包含:</p>
<ul className="space-y-1 list-disc list-inside">
<li>3 位用户(老板、项目经理、工程师)</li>
<li>2 个项目及成员配置</li>
<li>1 个阶段目标(Objective)</li>
<li>多个执行目标(KR)含不同周期</li>
<li>历史打卡记录和会议纪要</li>
<li>指标库和模板库</li>
</ul>
</div>
{error && (
<div className="bg-rose-50 text-rose-700 rounded-lg p-3 text-sm">
{error}
</div>
)}
<Button
onClick={handleInit}
disabled={isLoading}
className="w-full bg-teal-600 hover:bg-teal-700"
size="lg"
>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
正在初始化...
</>
) : (
<>
<Sparkles className="mr-2 h-4 w-4" />
生成演示数据
</>
)}
</Button>
</>
)}
</CardContent>
</Card>
</div>
);
}