diff --git a/.github/workflows/basic-checks.yml b/.github/workflows/basic-checks.yml index 62f4904..adba6e7 100644 --- a/.github/workflows/basic-checks.yml +++ b/.github/workflows/basic-checks.yml @@ -30,6 +30,8 @@ jobs: run: | cd apps/web pnpm install --frozen-lockfile + env: + DATABASE_URL: 'postgresql://test:test@localhost:5432/test' - name: 🔍 检查 Web 应用 run: | diff --git a/apps/web/.env.example b/apps/web/.env.example index c26755f..cfa4cf8 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,17 +1,174 @@ -# NextAuth 配置 -AUTH_SECRET="your-auth-secret-here" # 使用 `npx auth secret` 生成 -NEXTAUTH_URL="http://localhost:8800" - -# GitHub OAuth 配置 -# 1. 访问 https://github.com/settings/applications/new -# 2. 创建新的 OAuth App -# 3. Authorization callback URL: http://localhost:8800/api/auth/callback/github -GITHUB_CLIENT_ID="your-github-client-id" -GITHUB_CLIENT_SECRET="your-github-client-secret" - -# 应用配置 -NEXT_PUBLIC_DOMAIN=localhost:8800 -NEXT_PUBLIC_NODE_ENV=development - -# API 配置 -NEXT_PUBLIC_API_URL="http://localhost:8890" # 后端 API 地址 +# Better-Auth 环境变量配置 + +## 必需的环境变量 + +### Better-Auth 认证 + +```bash +# Better-Auth 密钥(必需) +# 用于加密 session 和签名 tokens +# 生成方法: npx @better-auth/cli secret 或 openssl rand -base64 32 +BETTER_AUTH_SECRET=your-secret-key-here + +# 数据库连接(必需) +DATABASE_URL=postgresql://user:password@localhost:5432/database_name +``` + +### GitHub OAuth + +```bash +# GitHub OAuth 应用配置(必需) +# 从 https://github.com/settings/developers 获取 +GITHUB_CLIENT_ID=your_github_client_id +GITHUB_CLIENT_SECRET=your_github_client_secret +``` + +--- + +## 可选的环境变量 + +### Better-Auth 配置 + +```bash +# Better-Auth 基础 URL(可选,默认 http://localhost:8800) +# 生产环境需要设置为实际域名 +BETTER_AUTH_URL=https://your-domain.com + +# 客户端可见的 Better-Auth URL(可选) +# 通常与 BETTER_AUTH_URL 相同 +NEXT_PUBLIC_BETTER_AUTH_URL=https://your-domain.com +``` + +--- + +## 开发环境配置示例 + +创建 `apps/web/.env.local` 文件: + +```bash +# .env.local - 不要提交到版本控制 + +# Better-Auth +BETTER_AUTH_SECRET=dev-secret-change-in-production +BETTER_AUTH_URL=http://localhost:8800 +NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:8800 + +# 数据库 +DATABASE_URL=postgresql://user:password@localhost:5432/telos + +# GitHub OAuth +GITHUB_CLIENT_ID=your_github_client_id +GITHUB_CLIENT_SECRET=your_github_client_secret +``` + +--- + +## 生产环境配置示例 + +### 1. 生成生产密钥 + +```bash +npx @better-auth/cli secret +# 或 +openssl rand -base64 32 +``` + +### 2. 设置环境变量 + +```bash +# Better-Auth +BETTER_AUTH_SECRET=<生成的随机密钥> +BETTER_AUTH_URL=https://your-domain.com +NEXT_PUBLIC_BETTER_AUTH_URL=https://your-domain.com + +# 数据库(使用生产数据库) +DATABASE_URL=postgresql://prod_user:prod_password@prod-db:5432/telos + +# GitHub OAuth(使用生产 OAuth 应用) +GITHUB_CLIENT_ID=<生产环境的 Client ID> +GITHUB_CLIENT_SECRET=<生产环境的 Client Secret> +``` + +--- + +## GitHub OAuth 应用配置 + +### 本地开发 + +1. 访问 https://github.com/settings/developers +2. 点击 "New OAuth App" +3. 配置如下: + - **Application name**: Telos (Development) + - **Homepage URL**: `http://localhost:8800` + - **Authorization callback URL**: `http://localhost:8800/api/auth/callback/github` +4. 创建后获取 `Client ID` 和 `Client Secret` + +### 生产环境 + +1. 同上,创建新的 OAuth App +2. 配置: + - **Homepage URL**: `https://your-domain.com` + - **Authorization callback URL**: `https://your-domain.com/api/auth/callback/github` + - **Enable Device Flow**: 可选 + +--- + +## 数据库配置 + +### PostgreSQL 连接字符串格式 + +```bash +# 标准格式 +postgresql://username:password@host:port/database + +# 示例 +postgresql://telos_user:telos_pass@localhost:5432/telos + +# 使用 socket(如果支持) +postgresql://username:password@/database?host=/var/run/postgresql +``` + +### Docker PostgreSQL + +如果使用 Docker 运行 PostgreSQL: + +```yaml +# docker-compose.yml +services: + postgres: + image: postgres:14 + environment: + POSTGRES_USER: telos_user + POSTGRES_PASSWORD: telos_pass + POSTGRES_DB: telos + ports: + - "5432:5432" + +# 对应的 DATABASE_URL +DATABASE_URL=postgresql://telos_user:telos_pass@localhost:5432/telos +``` + +--- + +## 安全注意事项 + +1. **永远不要将 `.env.local` 提交到版本控制** + ```bash + # .gitignore + .env.local + .env.*.local + ``` + +2. **生产环境的 secret 必须使用强随机密钥** + ```bash + # 生成 32 字节的随机密钥 + openssl rand -base64 32 + ``` + +3. **GitHub Client Secret 要妥善保管** + - 不要在前端代码中使用 + - 不要在日志中打印 + +4. **使用不同的 OAuth 应用区分开发和生产环境** + - 开发环境使用 localhost 回调 + - 生产环境使用正式域名回调 diff --git a/apps/web/TURBOPACK_MDX_MIGRATION.md b/apps/web/TURBOPACK_MDX_MIGRATION.md deleted file mode 100644 index e85d534..0000000 --- a/apps/web/TURBOPACK_MDX_MIGRATION.md +++ /dev/null @@ -1,262 +0,0 @@ -# Turbopack MDX 迁移文档 - -## 概述 - -为了支持 Next.js 16 的 Turbopack 模式,我们已将 MDX 配置从服务端插件迁移到客户端渲染方案。 - -## 变更内容 - -### 已移除的包 - -- ❌ `rehype-prism-plus` - 服务端代码高亮 -- ❌ `remark-gfm` - GitHub Flavored Markdown 插件 -- ❌ `rehype-highlight` - 尝试的替代方案 -- ❌ `highlight.js` - 服务端高亮库 - -### 新增的包 - -- ✅ `react-syntax-highlighter` - 客户端代码高亮 -- ✅ `@types/react-syntax-highlighter` - TypeScript 类型定义 - -### 新增的组件 - -#### 1. CodeBlock 组件 - -**位置**: `src/components/mdx-components/CodeBlock.tsx` - -**功能**: - -- 客户端代码语法高亮 -- 支持 190+ 编程语言 -- 使用 Prism 引擎和 oneDark 主题 -- 自动检测代码块语言 - -**使用方式**: -MDX 中的所有代码块自动使用此组件: - -\`\`\`typescript -// 代码会自动高亮 -const hello = "world" -\`\`\` - -#### 2. Table 组件系列 - -**位置**: `src/components/mdx-components/Table.tsx` - -**功能**: - -- 完整的表格支持(替代 GFM 表格) -- 响应式设计 -- 暗色模式支持 -- Hover 效果 - -**包含组件**: - -- `Table` - 表格容器 -- `TableHead` - 表头 -- `TableBody` - 表体 -- `TableRow` - 行 -- `TableCell` - 单元格 -- `TableHeader` - 标题单元格 - -**使用方式**: -Markdown 表格语法自动转换: - -```markdown -| 列1 | 列2 | 列3 | -| --- | --- | --- | -| A | B | C | -``` - -### 3. 任务列表样式 - -**位置**: `src/styles/globals.css` - -**功能**: - -- 将 checkbox 转换为 emoji 图标 -- ✅ 已完成任务 -- ⬜ 未完成任务 - -**使用方式**: - -```markdown -- [x] 已完成的任务 -- [ ] 未完成的任务 -``` - -## 配置变更 - -### next.config.ts - -```typescript -// 之前:使用服务端插件(不兼容 Turbopack) -const withMDX = createMDX({ - extension: /\.(md|mdx)$/, - options: { - remarkPlugins: [remarkGfm], - rehypePlugins: [rehypePrismPlus], - }, -}) - -// 现在:空配置(兼容 Turbopack) -const withMDX = createMDX({ - extension: /\.(md|mdx)$/, - options: { - remarkPlugins: [], - rehypePlugins: [], - }, -}) -``` - -## 功能对比 - -| 功能 | 之前(服务端) | 现在(客户端) | 状态 | -| --------- | -------------------- | --------------------------- | -------- | -| 代码高亮 | ✅ rehype-prism-plus | ✅ react-syntax-highlighter | 完全兼容 | -| 表格支持 | ✅ remark-gfm | ✅ 自定义 Table 组件 | 完全兼容 | -| 任务列表 | ✅ remark-gfm | ✅ CSS 样式 | 完全兼容 | -| Turbopack | ❌ 不兼容 | ✅ 完全兼容 | 已解决 | -| SEO | ✅ 服务端渲染 | ⚠️ 客户端渲染 | 轻微影响 | -| 首次加载 | ✅ 立即显示 | ⚠️ 可能闪烁 | 可接受 | -| 包大小 | ✅ 较小 | ⚠️ 稍大 | 可接受 | - -## 性能影响 - -### 优势 - -- ✅ Turbopack 开发速度显著提升 -- ✅ 热更新更快 -- ✅ 编译时间减少 - -### 劣势 - -- ⚠️ 首次页面加载可能有短暂的代码高亮延迟 -- ⚠️ JavaScript 包大小增加约 50-100KB(已压缩) -- ⚠️ 代码块内容对搜索引擎不完全友好(但标题和正文内容不受影响) - -## 开发指南 - -### 启动开发服务器 - -使用 Turbopack(推荐): - -```bash -pnpm dev:turbo -``` - -使用标准 Webpack(备选): - -```bash -pnpm dev -``` - -### 编写 MDX 内容 - -所有现有的 MDX 语法保持不变: - -1. **代码块** - 自动高亮 - -```typescript -const example = 'works' -``` - -2. **表格** - 自动渲染 - - | A | B | - | --- | --- | - | 1 | 2 | - -3. **任务列表** - 自动转换为图标 - -- [x] 完成 -- [ ] 待办 - -### 自定义主题 - -修改代码高亮主题: - -```typescript -// CodeBlock.tsx -import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' - -// 可选主题: -// - tomorrow -// - vs -// - vscDarkPlus -// - atomDark -// - dracula -``` - -## 故障排查 - -### 问题:代码块没有高亮 - -**解决方案**: - -1. 确保代码块指定了语言:\`\`\`typescript -2. 清除 Next.js 缓存:`rm -rf .next` -3. 重启开发服务器 - -### 问题:表格样式不正确 - -**解决方案**: - -1. 确保表格语法正确(必须有分隔行) -2. 检查 Tailwind CSS 是否正确加载 -3. 清除浏览器缓存 - -### 问题:任务列表显示为 checkbox - -**解决方案**: - -1. 确保 `globals.css` 已导入 -2. 检查是否在 `.prose` 类容器中 -3. 验证 CSS 选择器优先级 - -## 未来改进 - -可能的优化方向: - -1. **混合方案**:首屏使用服务端渲染,后续使用客户端 -2. **按需加载**:仅在需要时加载语法高亮器 -3. **Web Worker**:在后台线程中进行高亮处理 -4. **等待 Turbopack 支持**:关注 Next.js 官方的 MDX 插件支持进展 - -## Turbopack 限制 - -### 不支持动态 MDX 导入 - -Turbopack 不支持动态路径的 MDX 导入: - -```typescript -// ❌ 不支持 -const postModule = await import(`@/content/blog/${slug}.mdx`) - -// ✅ 必须使用静态导入 -import * as post1 from '@/content/blog/post-1.mdx' -import * as post2 from '@/content/blog/post-2.mdx' - -const BLOG_POST_MODULES = { - 'post-1': post1, - 'post-2': post2, -} as const -``` - -**解决方案**: 使用静态导入 + 映射表(参见 `src/lib/blog.ts`) - -**添加新文章时**: 需要手动在 `blog.ts` 中添加对应的 import 语句 - -## 相关链接 - -- [react-syntax-highlighter 文档](https://github.com/react-syntax-highlighter/react-syntax-highlighter) -- [Next.js 16 Turbopack 文档](https://nextjs.org/docs/architecture/turbopack) -- [MDX 官方文档](https://mdxjs.com/) -- [Turbopack 动态导入限制](https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack) - -## 版本信息 - -- Next.js: 16.0.0 -- React: 19.2.0 -- react-syntax-highlighter: ^16.0.0 -- 迁移日期: 2025-10-28 diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts deleted file mode 100644 index a3e4680..0000000 --- a/apps/web/next-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// -/// -import './.next/dev/types/routes.d.ts' - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index f172fb1..2235922 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,6 +4,7 @@ "private": true, "type": "module", "scripts": { + "postinstall": "prisma generate", "dev": "next dev -p 8800", "dev:webpack": "next dev --webpack -p 8800", "build": "next build", @@ -19,11 +20,12 @@ "build-storybook": "storybook build" }, "dependencies": { - "@auth/core": "^0.34.3", + "@prisma/client": "^7.2.0", "@hookform/resolvers": "^5.1.1", "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "16.0.0", + "@prisma/adapter-pg": "^7.2.0", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-aspect-ratio": "^1.1.7", @@ -55,6 +57,7 @@ "@studio-freight/lenis": "^1.0.42", "@tailwindcss/typography": "^0.5.19", "@xyflow/react": "^12.8.1", + "better-auth": "^1.4.16", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -68,7 +71,6 @@ "lucide-react": "^0.523.0", "motion": "^12.23.26", "next": "16.1.1", - "next-auth": "^4.24.13", "next-intl": "^4.4.0", "next-themes": "^0.4.6", "nextjs-toploader": "^3.8.16", @@ -94,6 +96,7 @@ "zustand": "^5.0.6" }, "devDependencies": { + "@better-auth/cli": "^1.4.16", "@chromatic-com/storybook": "^4.1.1", "@next/bundle-analyzer": "16.0.0", "@next/eslint-plugin-next": "16.0.0", diff --git a/apps/web/prisma.config.ts b/apps/web/prisma.config.ts new file mode 100644 index 0000000..246db07 --- /dev/null +++ b/apps/web/prisma.config.ts @@ -0,0 +1,13 @@ +import 'dotenv/config' +import { defineConfig, env } from 'prisma/config' + +export default defineConfig({ + schema: 'prisma/schema.prisma', + migrations: { + path: 'prisma/migrations', + }, + datasource: { + url: + env('DATABASE_URL') || 'postgresql://user:password@localhost:5432/mydb', + }, +}) diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma new file mode 100644 index 0000000..82399cc --- /dev/null +++ b/apps/web/prisma/schema.prisma @@ -0,0 +1,74 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" +} + +// Better-Auth user model +model user { + id String @id @default(cuid()) + email String? @unique + emailVerified Boolean? @default(false) + name String? + image String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + accounts account[] + sessions session[] +} + +// Better-Auth account model (for OAuth providers) +model account { + id String @id @default(cuid()) + userId String + providerId String + accountId String + accessToken String? @db.Text + refreshToken String? @db.Text + idToken String? @db.Text + accessTokenExpiresAt DateTime? + refreshTokenExpiresAt DateTime? + scope String? + password String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user user @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([providerId, accountId]) + @@index([userId]) +} + +// Better-Auth session model +model session { + id String @id @default(cuid()) + userId String + token String @unique + expiresAt DateTime + ipAddress String? + userAgent String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user user @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) +} + +// Better-Auth verification model +model verification { + id String @id @default(cuid()) + identifier String + value String + expiresAt DateTime + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([identifier, value]) +} diff --git a/apps/web/src/app/[locale]/(dashboard)/dashboard/page.tsx b/apps/web/src/app/[locale]/(dashboard)/dashboard/page.tsx index a1f9b09..a1aba8c 100644 --- a/apps/web/src/app/[locale]/(dashboard)/dashboard/page.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/dashboard/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useSession } from 'next-auth/react' +import { authClient } from '@/lib/auth-client' import { useTranslations } from 'next-intl' import { CustomLink } from '@/components/molecules' import { @@ -27,9 +27,9 @@ import { redirect } from 'next/navigation' export default function DashboardPage() { const t = useTranslations('Dashboard') - const { data: session, status } = useSession() + const { data: session, isPending } = authClient.useSession() - if (status === 'loading') { + if (isPending) { return (
@@ -42,7 +42,7 @@ export default function DashboardPage() { ) } - if (status === 'unauthenticated') { + if (!session) { redirect('/auth/signin') } diff --git a/apps/web/src/app/[locale]/(dashboard)/layout.tsx b/apps/web/src/app/[locale]/(dashboard)/layout.tsx index 9db3a21..08d3b9c 100644 --- a/apps/web/src/app/[locale]/(dashboard)/layout.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/layout.tsx @@ -1,4 +1,5 @@ -import { authOptions } from '@/lib/auth' +import { auth } from '@/lib/auth' +import { headers } from 'next/headers' import { redirect } from '@/i18n/navigation' import { ReactNode } from 'react' import { Navbar } from '@/components/organisms' @@ -12,7 +13,9 @@ interface Iprops { export default async function DashboardLayout({ children, params }: Iprops) { const { locale } = await params - const session = await authOptions + const session = await auth.api.getSession({ + headers: await headers(), + }) if (!session) { redirect({ href: '/auth/signin', locale }) diff --git a/apps/web/src/app/[locale]/(dashboard)/profile/page.tsx b/apps/web/src/app/[locale]/(dashboard)/profile/page.tsx index d6a7a52..cceea8a 100644 --- a/apps/web/src/app/[locale]/(dashboard)/profile/page.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/profile/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useSession } from 'next-auth/react' +import { authClient } from '@/lib/auth-client' import { useTranslations } from 'next-intl' import { Card, @@ -20,9 +20,9 @@ import { CustomLink } from '@/components/molecules' import { redirect } from 'next/navigation' export default function ProfilePage() { - const { data: session, status } = useSession() + const { data: session, isPending } = authClient.useSession() - if (status === 'loading') { + if (isPending) { return (
@@ -35,7 +35,7 @@ export default function ProfilePage() { ) } - if (status === 'unauthenticated') { + if (!session) { redirect('/auth/signin') } diff --git a/apps/web/src/app/[locale]/(dashboard)/settings/page.tsx b/apps/web/src/app/[locale]/(dashboard)/settings/page.tsx index bd49c63..f62f54e 100644 --- a/apps/web/src/app/[locale]/(dashboard)/settings/page.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/settings/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useSession } from 'next-auth/react' +import { authClient } from '@/lib/auth-client' import { useTranslations } from 'next-intl' import { Card, @@ -26,13 +26,13 @@ import { redirect } from 'next/navigation' import { useState } from 'react' export default function SettingsPage() { - const { data: session, status } = useSession() + const { data: session, isPending } = authClient.useSession() const [notifications, setNotifications] = useState(true) const [darkMode, setDarkMode] = useState(false) const [emailUpdates, setEmailUpdates] = useState(true) - if (status === 'loading') { + if (isPending) { return (
@@ -45,7 +45,7 @@ export default function SettingsPage() { ) } - if (status === 'unauthenticated') { + if (!session) { redirect('/auth/signin') } diff --git a/apps/web/src/app/[locale]/(dashboard)/workflows/page.tsx b/apps/web/src/app/[locale]/(dashboard)/workflows/page.tsx index de7ec9a..288643d 100644 --- a/apps/web/src/app/[locale]/(dashboard)/workflows/page.tsx +++ b/apps/web/src/app/[locale]/(dashboard)/workflows/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useSession } from 'next-auth/react' +import { authClient } from '@/lib/auth-client' import { useTranslations } from 'next-intl' import { useState, useMemo } from 'react' import { @@ -27,7 +27,7 @@ import { redirect } from 'next/navigation' export default function WorkflowsPage() { const t = useTranslations('Workflows') - const { data: session, status } = useSession() + const { data: session, isPending } = authClient.useSession() // 模拟工作流数据 const workflows = useMemo( @@ -72,7 +72,7 @@ export default function WorkflowsPage() { [t] ) - if (status === 'loading') { + if (isPending) { return (
@@ -85,7 +85,7 @@ export default function WorkflowsPage() { ) } - if (status === 'unauthenticated') { + if (!session) { redirect('/auth/signin') } diff --git a/apps/web/src/app/[locale]/(default-layout)/auth/signin/page.tsx b/apps/web/src/app/[locale]/(default-layout)/auth/signin/page.tsx index b2f7261..b3f4451 100644 --- a/apps/web/src/app/[locale]/(default-layout)/auth/signin/page.tsx +++ b/apps/web/src/app/[locale]/(default-layout)/auth/signin/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { signIn, useSession } from 'next-auth/react' +import { authClient } from '@/lib/auth-client' import { useSearchParams, useRouter } from 'next/navigation' import { useState, useEffect } from 'react' import { useTranslations } from 'next-intl' @@ -18,8 +18,7 @@ import { CustomLink } from '@/components/molecules' export default function SignInPage() { const searchParams = useSearchParams() const router = useRouter() - const { data: session, status } = useSession() - const callbackUrl = searchParams.get('callbackUrl') || '/' + const { data: session, isPending } = authClient.useSession() const error = searchParams.get('error') const t = useTranslations('Auth.signIn') const tError = useTranslations('Auth.error') @@ -27,29 +26,33 @@ export default function SignInPage() { const [isLoading, setIsLoading] = useState(false) const [loadingProvider, setLoadingProvider] = useState(null) + const callbackUrl = searchParams.get('callbackUrl') || '/dashboard' + // 如果用户已经登录,直接跳转到目标页面 useEffect(() => { - if (status === 'authenticated' && session) { + if (session && !isPending) { router.push(callbackUrl) } - }, [status, session, callbackUrl, router]) + }, [session, isPending, callbackUrl, router]) const handleSignIn = async (provider: string) => { setIsLoading(true) setLoadingProvider(provider) try { - // signIn 会触发页面跳转,所以不需要重置loading状态 - await signIn(provider, { callbackUrl }) + // Better-Auth social signIn + await authClient.signIn.social({ + provider: provider as 'github', + callbackURL: callbackUrl, + }) } catch (error) { console.error(`${provider} 登录失败:`, error) - // 只有发生错误时才重置loading状态 setIsLoading(false) setLoadingProvider(null) } } return ( -
+
{t('title')} diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..5d94414 --- /dev/null +++ b/apps/web/src/app/api/auth/[...all]/route.ts @@ -0,0 +1,4 @@ +import { auth } from '@/lib/auth' +import { toNextJsHandler } from 'better-auth/next-js' + +export const { GET, POST } = toNextJsHandler(auth) diff --git a/apps/web/src/app/api/auth/[...nextauth]/route.ts b/apps/web/src/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index e10628f..0000000 --- a/apps/web/src/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import NextAuth from 'next-auth' -import { authOptions } from '@/lib/auth' - -const handler = NextAuth(authOptions) - -export { handler as GET, handler as POST } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index d0a13d5..a991c9f 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -11,7 +11,7 @@ import '@/styles/globals.css' import NextTopLoader from 'nextjs-toploader' import { headers } from 'next/headers' import appConfig from '@/appConfig' -import { ThemeProvider, SessionProvider } from '@/components/providers' +import { ThemeProvider } from '@/components/providers' const geistSans = Geist({ variable: '--font-geist-sans', @@ -89,16 +89,14 @@ export default async function RootLayout({ children }: Iprops) { className={`${geistSans.variable} ${geistMono.variable} ${inter.variable} ${playfairDisplay.variable} ${jetbrainsMono.variable} ${poppins.variable} antialiased`} > - - - {children} - - + + {children} + ) diff --git a/apps/web/src/components/molecules/UserAvatar.tsx b/apps/web/src/components/molecules/UserAvatar.tsx index 3bc1f99..3a7eac5 100644 --- a/apps/web/src/components/molecules/UserAvatar.tsx +++ b/apps/web/src/components/molecules/UserAvatar.tsx @@ -1,6 +1,6 @@ 'use client' -import { useSession, signOut } from 'next-auth/react' +import { authClient } from '@/lib/auth-client' import { useTranslations } from 'next-intl' import { Avatar, @@ -19,14 +19,14 @@ import { User, Settings, LogOut } from 'lucide-react' import { CustomLink } from './CustomLink' export function UserAvatar() { - const { data: session, status } = useSession() + const { data: session, isPending } = authClient.useSession() const t = useTranslations('UserMenu') - if (status === 'loading') { + if (isPending) { return } - if (status === 'unauthenticated' || !session?.user) { + if (!session) { return (