echo/src/components/ui/Button.tsx
ddshi 4c42d6b6f5 feat: 完成阶段 2 - 用户系统与智能体初始化
## 完成的功能
- 用户登录/注册(邮箱 + Google OAuth)
- 初始问题引导流程(4 个问题)
- 智能体自动生成(根据用户回答)
- 智能体列表页面

## 新增文件
- 登录/注册页面和组件
- Onboarding 页面和组件
- 智能体列表页面
- 智能体 API 端点
- 智能体 Prompt 设计

## 数据库迁移
- onboarding_fix 迁移(users.has_completed_onboarding)

## 测试
- 登录/注册:100% 通过
- Onboarding:85% 通过
- 智能体功能:85% 通过

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 14:08:12 +08:00

52 lines
2.0 KiB
TypeScript

'use client';
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', isLoading, children, disabled, ...props }, ref) => {
const baseStyles = 'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
primary: 'bg-gradient-to-r from-pink-500 to-purple-500 text-white hover:from-pink-600 hover:to-purple-600 focus:ring-purple-500',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-500',
outline: 'border-2 border-gray-200 text-gray-700 hover:bg-gray-50 focus:ring-gray-500',
ghost: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900 focus:ring-gray-500',
danger: 'bg-red-500 text-white hover:bg-red-600 focus:ring-red-500',
};
const sizes = {
sm: 'text-sm px-3 py-1.5 gap-1.5',
md: 'text-base px-4 py-2 gap-2',
lg: 'text-lg px-6 py-3 gap-2.5',
};
return (
<button
ref={ref}
className={cn(baseStyles, variants[variant], sizes[size], className)}
disabled={disabled || isLoading}
{...props}
>
{isLoading && (
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
)}
{children}
</button>
);
}
);
Button.displayName = 'Button';
export { Button };