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}
+