From 74b509f14fd2f2d0302e19dc9d5f75f7c64a5c6d Mon Sep 17 00:00:00 2001 From: LeviLiu Date: Wed, 21 Jan 2026 14:36:19 +0800 Subject: [PATCH 1/8] feat: migrate from NextAuth to Better-Auth BREAKING CHANGE: Complete migration from NextAuth v4 to Better-Auth ### Auth Library Changes - Replace next-auth with better-auth (v1.4.16) - Replace @auth/core with better-auth/adapters/prisma - Add @prisma/adapter-pg for Prisma 7 PostgreSQL driver ### Database Setup - Add Prisma with PostgreSQL adapter - Create auth tables: user, account, session, verification - Configure DATABASE_URL for local development ### API Routes - Replace [...nextauth] route with [...all] for Better-Auth - Add toNextJsHandler adapter for Next.js App Router ### Client Changes - Remove SessionProvider (no longer needed) - Replace useSession() with authClient.useSession() - Update signIn/signOut to use authClient methods - Add auth-client.ts with createAuthClient configuration ### Component Updates - UserAvatar: migrate to authClient.useSession() - HeroSection: migrate to authClient.useSession() - Signin page: use authClient.signIn.social() - Dashboard pages: migrate to authClient.useSession() - Dashboard layout: fix authOptions bug, use auth.api.getSession() ### Configuration - Add skipTrailingSlashes: true for Better-Auth advanced config - Add basePath: /api/auth and secret for Better-Auth - Configure cookiePrefix: 'telos' ### Migration Docs - Add docs/migrate-to-better-auth.md with migration guide - Add docs/known-issues-better-auth-nextjs-trailing-slash.md Co-Authored-By: Claude Opus 4.5 --- apps/web/TURBOPACK_MDX_MIGRATION.md | 262 ---- apps/web/next-env.d.ts | 2 +- apps/web/package.json | 6 +- apps/web/prisma.config.ts | 14 + apps/web/prisma/schema.prisma | 74 + .../[locale]/(dashboard)/dashboard/page.tsx | 8 +- .../src/app/[locale]/(dashboard)/layout.tsx | 7 +- .../app/[locale]/(dashboard)/profile/page.tsx | 8 +- .../[locale]/(dashboard)/settings/page.tsx | 8 +- .../[locale]/(dashboard)/workflows/page.tsx | 8 +- .../(default-layout)/auth/signin/page.tsx | 20 +- apps/web/src/app/api/auth/[...all]/route.ts | 4 + .../src/app/api/auth/[...nextauth]/route.ts | 6 - apps/web/src/app/layout.tsx | 20 +- .../src/components/molecules/UserAvatar.tsx | 11 +- .../src/components/organisms/HeroSection.tsx | 4 +- .../components/providers/SessionProvider.tsx | 12 - apps/web/src/components/providers/index.ts | 1 - apps/web/src/lib/auth-client.ts | 8 + apps/web/src/lib/auth.ts | 85 +- apps/web/src/lib/db.ts | 24 + pnpm-lock.yaml | 1298 ++++++++++++----- 22 files changed, 1132 insertions(+), 758 deletions(-) delete mode 100644 apps/web/TURBOPACK_MDX_MIGRATION.md create mode 100644 apps/web/prisma.config.ts create mode 100644 apps/web/prisma/schema.prisma create mode 100644 apps/web/src/app/api/auth/[...all]/route.ts delete mode 100644 apps/web/src/app/api/auth/[...nextauth]/route.ts delete mode 100644 apps/web/src/components/providers/SessionProvider.tsx create mode 100644 apps/web/src/lib/auth-client.ts create mode 100644 apps/web/src/lib/db.ts 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 index a3e4680..c4b7818 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import './.next/dev/types/routes.d.ts' +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..2dd6bb0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,11 +19,11 @@ "build-storybook": "storybook build" }, "dependencies": { - "@auth/core": "^0.34.3", "@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 +55,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 +69,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,9 +94,11 @@ "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", + "@prisma/client": "^7.2.0", "@storybook/addon-a11y": "^9.1.15", "@storybook/addon-docs": "^9.1.15", "@storybook/addon-onboarding": "^9.1.15", diff --git a/apps/web/prisma.config.ts b/apps/web/prisma.config.ts new file mode 100644 index 0000000..a9057bd --- /dev/null +++ b/apps/web/prisma.config.ts @@ -0,0 +1,14 @@ +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://LeviLiu:12345678@localhost:5432/Telos', + }, +}) 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..d895da2 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') @@ -29,27 +28,28 @@ export default function SignInPage() { // 如果用户已经登录,直接跳转到目标页面 useEffect(() => { - if (status === 'authenticated' && session) { - router.push(callbackUrl) + if (session && !isPending) { + router.push('/') } - }, [status, session, callbackUrl, router]) + }, [session, isPending, 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', + }) } 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 (