diff --git a/README.md b/README.md index f74d11b..f100b80 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,10 @@ 使用 pure js 的包代替 canvas,加快编译速度 -重新设计验证逻辑,通过全局 cache 优化请求效率,使用服务端组件实现验证 - 提取后刷新提取页套餐可用余量 -提取时检查 IP 和实名状态 - 保存客户端信息时用 jwt 序列化 -登录后刷新 profile - -区分调用方式,提供 callByDevice callByUser call 三种调用方式 - --- 页面数据: diff --git a/package.json b/package.json index 0639e49..fd8b8e2 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "date-fns": "^4.1.0", "lucide-react": "^0.479.0", "motion": "^12.5.0", - "next": "15.2.1", + "next": "15.2.4", "next-themes": "^0.4.6", "qrcode": "^1.5.4", "react": "^19.0.0", @@ -38,7 +38,8 @@ "sonner": "^2.0.1", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", - "zod": "^3.24.2" + "zod": "^3.24.2", + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80277a6..d54a9c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,8 +66,8 @@ importers: specifier: ^12.5.0 version: 12.5.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next: - specifier: 15.2.1 - version: 15.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: 15.2.4 + version: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -98,6 +98,9 @@ importers: zod: specifier: ^3.24.2 version: 3.24.2 + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.0.10)(react@19.0.0) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -339,60 +342,60 @@ packages: cpu: [x64] os: [win32] - '@next/env@15.2.1': - resolution: {integrity: sha512-JmY0qvnPuS2NCWOz2bbby3Pe0VzdAQ7XpEB6uLIHmtXNfAsAO0KLQLkuAoc42Bxbo3/jMC3dcn9cdf+piCcG2Q==} + '@next/env@15.2.4': + resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==} '@next/eslint-plugin-next@15.2.1': resolution: {integrity: sha512-6ppeToFd02z38SllzWxayLxjjNfzvc7Wm07gQOKSLjyASvKcXjNStZrLXMHuaWkhjqxe+cnhb2uzfWXm1VEj/Q==} - '@next/swc-darwin-arm64@15.2.1': - resolution: {integrity: sha512-aWXT+5KEREoy3K5AKtiKwioeblmOvFFjd+F3dVleLvvLiQ/mD//jOOuUcx5hzcO9ISSw4lrqtUPntTpK32uXXQ==} + '@next/swc-darwin-arm64@15.2.4': + resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.2.1': - resolution: {integrity: sha512-E/w8ervu4fcG5SkLhvn1NE/2POuDCDEy5gFbfhmnYXkyONZR68qbUlJlZwuN82o7BrBVAw+tkR8nTIjGiMW1jQ==} + '@next/swc-darwin-x64@15.2.4': + resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.2.1': - resolution: {integrity: sha512-gXDX5lIboebbjhiMT6kFgu4svQyjoSed6dHyjx5uZsjlvTwOAnZpn13w9XDaIMFFHw7K8CpBK7HfDKw0VZvUXQ==} + '@next/swc-linux-arm64-gnu@15.2.4': + resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@15.2.1': - resolution: {integrity: sha512-3v0pF/adKZkBWfUffmB/ROa+QcNTrnmYG4/SS+r52HPwAK479XcWoES2I+7F7lcbqc7mTeVXrIvb4h6rR/iDKg==} + '@next/swc-linux-arm64-musl@15.2.4': + resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@15.2.1': - resolution: {integrity: sha512-RbsVq2iB6KFJRZ2cHrU67jLVLKeuOIhnQB05ygu5fCNgg8oTewxweJE8XlLV+Ii6Y6u4EHwETdUiRNXIAfpBww==} + '@next/swc-linux-x64-gnu@15.2.4': + resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@15.2.1': - resolution: {integrity: sha512-QHsMLAyAIu6/fWjHmkN/F78EFPKmhQlyX5C8pRIS2RwVA7z+t9cTb0IaYWC3EHLOTjsU7MNQW+n2xGXr11QPpg==} + '@next/swc-linux-x64-musl@15.2.4': + resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@15.2.1': - resolution: {integrity: sha512-Gk42XZXo1cE89i3hPLa/9KZ8OuupTjkDmhLaMKFohjf9brOeZVEa3BQy1J9s9TWUqPhgAEbwv6B2+ciGfe54Vw==} + '@next/swc-win32-arm64-msvc@15.2.4': + resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.2.1': - resolution: {integrity: sha512-YjqXCl8QGhVlMR8uBftWk0iTmvtntr41PhG1kvzGp0sUP/5ehTM+cwx25hKE54J0CRnHYjSGjSH3gkHEaHIN9g==} + '@next/swc-win32-x64-msvc@15.2.4': + resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2136,8 +2139,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.2.1: - resolution: {integrity: sha512-zxbsdQv3OqWXybK5tMkPCBKyhIz63RstJ+NvlfkaLMc/m5MwXgz2e92k+hSKcyBpyADhMk2C31RIiaDjUZae7g==} + next@15.2.4: + resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -2715,6 +2718,24 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -2876,34 +2897,34 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@next/env@15.2.1': {} + '@next/env@15.2.4': {} '@next/eslint-plugin-next@15.2.1': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.2.1': + '@next/swc-darwin-arm64@15.2.4': optional: true - '@next/swc-darwin-x64@15.2.1': + '@next/swc-darwin-x64@15.2.4': optional: true - '@next/swc-linux-arm64-gnu@15.2.1': + '@next/swc-linux-arm64-gnu@15.2.4': optional: true - '@next/swc-linux-arm64-musl@15.2.1': + '@next/swc-linux-arm64-musl@15.2.4': optional: true - '@next/swc-linux-x64-gnu@15.2.1': + '@next/swc-linux-x64-gnu@15.2.4': optional: true - '@next/swc-linux-x64-musl@15.2.1': + '@next/swc-linux-x64-musl@15.2.4': optional: true - '@next/swc-win32-arm64-msvc@15.2.1': + '@next/swc-win32-arm64-msvc@15.2.4': optional: true - '@next/swc-win32-x64-msvc@15.2.1': + '@next/swc-win32-x64-msvc@15.2.4': optional: true '@nodelib/fs.scandir@2.1.5': @@ -4735,9 +4756,9 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - next@15.2.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - '@next/env': 15.2.1 + '@next/env': 15.2.4 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 @@ -4747,14 +4768,14 @@ snapshots: react-dom: 19.0.0(react@19.0.0) styled-jsx: 5.1.6(react@19.0.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.2.1 - '@next/swc-darwin-x64': 15.2.1 - '@next/swc-linux-arm64-gnu': 15.2.1 - '@next/swc-linux-arm64-musl': 15.2.1 - '@next/swc-linux-x64-gnu': 15.2.1 - '@next/swc-linux-x64-musl': 15.2.1 - '@next/swc-win32-arm64-msvc': 15.2.1 - '@next/swc-win32-x64-msvc': 15.2.1 + '@next/swc-darwin-arm64': 15.2.4 + '@next/swc-darwin-x64': 15.2.4 + '@next/swc-linux-arm64-gnu': 15.2.4 + '@next/swc-linux-arm64-musl': 15.2.4 + '@next/swc-linux-x64-gnu': 15.2.4 + '@next/swc-linux-x64-musl': 15.2.4 + '@next/swc-win32-arm64-msvc': 15.2.4 + '@next/swc-win32-x64-msvc': 15.2.4 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' @@ -5435,3 +5456,8 @@ snapshots: yocto-queue@0.1.0: {} zod@3.24.2: {} + + zustand@5.0.3(@types/react@19.0.10)(react@19.0.0): + optionalDependencies: + '@types/react': 19.0.10 + react: 19.0.0 diff --git a/src/actions/auth/auth.ts b/src/actions/auth/auth.ts index fc5eb28..8e1b946 100644 --- a/src/actions/auth/auth.ts +++ b/src/actions/auth/auth.ts @@ -1,62 +1,57 @@ 'use server' import {cookies} from 'next/headers' -import {ApiResponse} from '@/lib/api' +import {ApiResponse, UnauthorizedError} from '@/lib/api' import {AuthContext} from '@/lib/auth' import {User} from '@/lib/models' -import {callByDevice, callByUser, getUserToken} from '@/actions/base' +import {callByDevice, callByUser, callPublic, getUserToken} from '@/actions/base' import {redirect} from 'next/navigation' +import {cache} from 'react' export interface LoginParams { username: string password: string - remember?: boolean + remember: boolean } type LoginResp = { access_token: string refresh_token: string - expires: number - auth: AuthContext - profile: User + expires_in: number + token_type: string + scope?: string } export async function login(props: LoginParams): Promise { // 尝试登录 - const result = await callByDevice('/api/auth/login/sms', { - username: props.username, - password: props.password, - remember: props.remember ?? false, + const result = await callByDevice('/api/auth/token', { + ...props, + grant_type: 'password', + login_type: 'phone_code', }) if (!result.success) { return result } - const data = result.data // 保存到 cookies - const current = Math.floor(Date.now() / 1000) - const future = data.expires - current - + const data = result.data const cookieStore = await cookies() cookieStore.set('auth_token', data.access_token, { httpOnly: true, sameSite: 'strict', - maxAge: Math.max(future, 0), + maxAge: Math.max(data.expires_in, 0), }) cookieStore.set('auth_refresh', data.refresh_token, { httpOnly: true, sameSite: 'strict', - maxAge: 7 * 24 * 3600, - }) - cookieStore.set('auth_info', JSON.stringify(data.auth), { - httpOnly: true, - sameSite: 'strict', - maxAge: 7 * 24 * 3600, - }) - cookieStore.set('auth_profile', JSON.stringify(data.profile), { - httpOnly: true, - sameSite: 'strict', - maxAge: 7 * 24 * 3600, }) + // cookieStore.set('auth_info', JSON.stringify(data.auth), { + // httpOnly: true, + // sameSite: 'strict', + // }) + // cookieStore.set('auth_profile', JSON.stringify(data.profile), { + // httpOnly: true, + // sameSite: 'strict', + // }) return { success: true, @@ -88,52 +83,27 @@ export async function logout() { sameSite: 'strict', maxAge: -1, }) - cookieStore.set('auth_info', '', { - httpOnly: true, - sameSite: 'strict', - maxAge: -1, - }) - cookieStore.set('auth_profile', '', { - httpOnly: true, - sameSite: 'strict', - maxAge: -1, - }) - return redirect('/') + return { + success: true, + data: undefined, + } } -export async function getProfile(refresh: boolean = false) { - const cookie = await cookies() - - // 获取缓存的用户信息 - if (!refresh) { - const profile = cookie.get('auth_profile')?.value - if (profile) { - return JSON.parse(profile) as User - } - } - - // 获取缓存的 token - let token: string +export async function getProfile() { try { - token = await getUserToken() + const token = await getUserToken() + const result = await callPublic('/api/user/get/token', {token}) + + if (!result.success) { + throw new Error('获取用户信息失败') + } + return result.data } catch (e) { - return null + if (e === UnauthorizedError) { + return null + } + throw e } - - // 如果没有缓存,则请求用户信息 - const result = await callByUser('/api/user/get/token', {token}) - if (!result.success) { - return null - } - - // 保存用户信息到cookie - cookie.set('auth_profile', JSON.stringify(result.data), { - httpOnly: true, - sameSite: 'strict', - maxAge: 7 * 24 * 3600, - }) - - return result.data } diff --git a/src/actions/base.ts b/src/actions/base.ts index 81aec09..d03bcfe 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -2,106 +2,52 @@ import {API_BASE_URL, CLIENT_ID, CLIENT_SECRET, ApiResponse, UnauthorizedError} from '@/lib/api' import {cookies, headers} from 'next/headers' import {redirect} from 'next/navigation' - -// OAuth令牌缓存 -interface TokenCache { - token: string - expires: number // 过期时间戳 -} +import {cache} from 'react' // ====================== // region device token // ====================== -let tokenCache: TokenCache | null = null - -// 获取OAuth2访问令牌 -async function getDeviceToken(forceRefresh = false): Promise { - try { - // 检查缓存的令牌是否可用 - if (!forceRefresh && tokenCache && tokenCache.expires > Date.now()) { - return tokenCache.token - } - - const addr = `${API_BASE_URL}/api/auth/token` - const body = { - client_id: CLIENT_ID, - client_secret: CLIENT_SECRET, - grant_type: 'client_credentials', - } - - const response = await fetch(addr, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), - }) - - if (!response.ok) { - throw new Error(`OAuth token request failed: ${response.status} ${await response.text()}`) - } - - const data = await response.json() - - // 缓存令牌和过期时间 - // 通常后端会返回expires_in(秒为单位) - tokenCache = { - token: data.access_token, - expires: Date.now() + data.expires_in * 1000, - } - - return tokenCache.token - } - catch (error) { - console.error('Failed to get access token:', error) - throw new Error('认证服务暂时不可用') - } +async function callByDevice( + endpoint: string, + data: unknown, +): Promise> { + return _callByDevice(endpoint, data ? JSON.stringify(data) : undefined) } // 通用的API调用函数 -async function callByDevice(endpoint: string, data: unknown): Promise> { +const _callByDevice = cache(async ( + endpoint: string, + data?: string, +): Promise> => { try { - // 发送请求 - let accessToken = getDeviceToken() - const requestOptions = { + // 获取设备令牌 + if (!CLIENT_ID || !CLIENT_SECRET) { + throw new Error('缺少CLIENT_ID或CLIENT_SECRET环境变量') + } + const token = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64url') + + // 构造请求 + const url = `${API_BASE_URL}${endpoint}` + const request = { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${await accessToken}`, + 'Authorization': `Basic ${token}`, }, - body: JSON.stringify(data), - } - let response = await fetch(`${API_BASE_URL}${endpoint}`, requestOptions) - - // 如果返回401未授权,尝试刷新令牌并重试一次 - if (response.status === 401) { - accessToken = getDeviceToken(true) // 强制刷新令牌 - - // 使用新令牌重试请求 - requestOptions.headers['Authorization'] = `Bearer ${await accessToken}` - response = await fetch(`${API_BASE_URL}${endpoint}`, requestOptions) + body: data, } - // 检查响应状态 - if (!response.ok) { - console.log('响应不成功', `status=${response.status}`, await response.text()) - return { - success: false, - status: response.status, - message: '请求失败', - } - } + // 发送请求 + const response = await fetch(`${API_BASE_URL}${endpoint}`, request) // 检查响应状态 return handleResponse(response) } catch (e) { - console.error('API call failed:', e) throw new Error('服务调用失败', {cause: e}) } -} - +}) // endregion @@ -172,26 +118,33 @@ async function callByUser( endpoint: string, data?: unknown, ): Promise> { + return _callByUser(endpoint, data ? JSON.stringify(data) : undefined) +} + +const _callByUser = cache(async ( + endpoint: string, + data?: string, +): Promise> => { + const header = await headers() try { let token = await getUserToken() - const header = await headers() - - // 获取客户端 IP - const clientIp = header.get('x-forwarded-for') - - // 发送请求 + + // 构造请求 const request = { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, } as Record, - body: data ? JSON.stringify(data) : undefined, - } - if (clientIp) { - request.headers['X-Forwarded-For'] = clientIp + body: data, } + const userIp = header.get('x-forwarded-for') + if (userIp) { + request.headers['X-Forwarded-For'] = userIp + } + + // 发送请求 let response = await fetch(`${API_BASE_URL}${endpoint}`, request) if (response.status === 401) { token = await getUserToken(true) @@ -199,45 +152,25 @@ async function callByUser( response = await fetch(`${API_BASE_URL}${endpoint}`, request) } - // 检查响应状态 - if (!response.ok) { - const body = await response.text() - console.log('响应不成功', `status=${response.status}`, body) - - if (response.status === 401) { - return redirect('/login') - } - - if (response.status >= 400 && response.status < 500) { - return { - status: response.status, - success: false, - message: body, - } - } - - if (response.status >= 500) { - return { - status: response.status, - success: false, - message: '服务器错误', - } - } - - return { - status: response.status, - success: false, - message: `请求失败,status = ${response.status}`, - } + if (response.status === 401) { + throw UnauthorizedError } return handleResponse(response) } catch (e) { - console.error('API call with user token failed:', e) + if (e === UnauthorizedError) { + const referer = header.get('referer') + let redirectUrl = '/login' + if (referer) { + const url = new URL(referer) + redirectUrl = `/login?redirect=${encodeURIComponent(url.pathname)}` + } + return redirect(redirectUrl) + } throw new Error('服务调用失败', {cause: e}) } -} +}) // endregion @@ -250,43 +183,35 @@ async function callPublic( endpoint: string, data?: unknown, ): Promise> { + return _callPublic(endpoint, data ? JSON.stringify(data) : undefined) +} + +const _callPublic = cache(async ( + endpoint: string, + data?: string, +): Promise> => { try { - // 发送请求 - const requestOptions: RequestInit = { + const url = `${API_BASE_URL}${endpoint}` + const request: RequestInit = { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: data ? JSON.stringify(data) : undefined, - } - - const response = await fetch(`${API_BASE_URL}${endpoint}`, requestOptions) - - // 检查响应状态 - if (!response.ok) { - console.log('公共接口响应不成功', `status=${response.status}`, await response.text()) - - return { - status: response.status, - success: false, - message: response.status >= 500 ? '服务器错误' : '请求失败', - } + body: data, } + const response = await fetch(url, request) return handleResponse(response) } catch (e) { - console.error('Public API call failed:', e) - throw new Error('服务调用失败', { cause: e }) + throw new Error('服务调用失败', {cause: e}) } -} +}) // endregion // 统一响应解析 async function handleResponse(response: Response): Promise> { - - // 解析响应数据 const type = response.headers.get('Content-Type') ?? 'text/plain' if (type.indexOf('application/json') !== -1) { const json = await response.json() @@ -313,9 +238,11 @@ async function handleResponse(response: Response): Promise(response: Response): Promisestore.refreshProfile) + + // ====================== + // render + // ====================== + return (
void } | null>(null) -export type ProviderProps = { - userCenter: ReactNode -} +export type ProviderProps = {} export default function Provider(props: ProviderProps) { @@ -51,7 +51,13 @@ export default function Provider(props: ProviderProps) { ], []) // ====================== - // 渲染组件 + // 用户信息 + // ====================== + + const profile = useProfileStore(store=>store.profile) + + // ====================== + // render // ====================== return ( @@ -116,7 +122,35 @@ export default function Provider(props: ProviderProps) { {/* 登录 */} - {props.userCenter} +
+ {profile == undefined + ? <> + + 登录 + + + 注册 + + + : ( + + + + ) + } +
diff --git a/src/app/(root)/@header/_server/user-center.tsx b/src/app/(root)/@header/_server/user-center.tsx deleted file mode 100644 index 94ad869..0000000 --- a/src/app/(root)/@header/_server/user-center.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import Link from 'next/link' -import {cookies} from 'next/headers' -import {Button} from '@/components/ui/button' -import {AuthContext} from '@/lib/auth' - -export type UserCenterProps = {} - -export default async function UserCenter(props: UserCenterProps) { - - const store = await cookies() - const info = store.get('auth_info')?.value - const data = info ? JSON.parse(info) as AuthContext : undefined - - return ( -
- {data == undefined - ? <> - - 登录 - - - 注册 - - - : ( - - - - ) - } -
- ) -} diff --git a/src/app/(root)/@header/page.tsx b/src/app/(root)/@header/page.tsx index 07f4227..6b7defe 100644 --- a/src/app/(root)/@header/page.tsx +++ b/src/app/(root)/@header/page.tsx @@ -1,14 +1,11 @@ import Provider from '@/app/(root)/@header/_client/provider' -import UserCenter from '@/app/(root)/@header/_server/user-center' export type HeaderProps = {} export default async function Header(props: HeaderProps) { return (
- } - /> +
) } diff --git a/src/app/admin/(dashboard)/page.tsx b/src/app/admin/(dashboard)/page.tsx index b44f8e9..692255d 100644 --- a/src/app/admin/(dashboard)/page.tsx +++ b/src/app/admin/(dashboard)/page.tsx @@ -10,12 +10,6 @@ import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs' export type DashboardPageProps = {} export default async function DashboardPage(props: DashboardPageProps) { - - const profile = await getProfile() - if (!profile) { - return redirect('/login') - } - return ( {/* banner */} diff --git a/src/app/admin/_server/profile.tsx b/src/app/admin/_server/profile.tsx index 3226159..cb23d7a 100644 --- a/src/app/admin/_server/profile.tsx +++ b/src/app/admin/_server/profile.tsx @@ -1,17 +1,33 @@ -import {cookies} from 'next/headers' +'use client' import {Button} from '@/components/ui/button' import {logout} from '@/actions/auth/auth' +import {useProfileStore} from '@/components/providers/StoreProvider' +import {useRouter} from 'next/navigation' +import {toast} from 'sonner' export type ProfileProps = {} -export default async function Profile(props: ProfileProps) { - const store = await cookies() - const info = store.get('auth_info')?.value - const data = info ? JSON.parse(info) : undefined +export default function Profile(props: ProfileProps) { + const refreshProfile = useProfileStore(store => store.refreshProfile) + const router = useRouter() + const doLogout = async () => { + try { + const resp = await logout() + if (resp.success) { + await refreshProfile() + router.push('/') + } + } + catch (e) { + toast.error('退出登录失败', { + description: (e as Error).message, + }) + } + } + return (
- 下午好,{data?.payload.name} -
diff --git a/src/app/admin/bills/page.tsx b/src/app/admin/bills/page.tsx index 821beaf..264004f 100644 --- a/src/app/admin/bills/page.tsx +++ b/src/app/admin/bills/page.tsx @@ -68,6 +68,8 @@ export default function BillsPage(props: BillsPageProps) { } useEffect(() => { + console.log('init bill list') + refresh(1, 10).then() refresh(1, 10).then() }, []) diff --git a/src/app/admin/identify/page.tsx b/src/app/admin/identify/page.tsx index 1550c32..86f90a5 100644 --- a/src/app/admin/identify/page.tsx +++ b/src/app/admin/identify/page.tsx @@ -13,7 +13,7 @@ import {Identify} from '@/actions/auth/identify' import {toast} from 'sonner' import {useContext, useEffect, useRef, useState} from 'react' import * as qrcode from 'qrcode' -import {AuthContext} from '@/components/providers/AuthProvider' +import {StoreContext, useProfileStore} from '@/components/providers/StoreProvider' export type IdentifyPageProps = {} @@ -90,8 +90,8 @@ export default function IdentifyPage(props: IdentifyPageProps) { // 用户数据 // ====================== - const ctx = useContext(AuthContext) - console.log('render identify page') + const profile = useProfileStore(store => store.profile) + const refreshProfile = useProfileStore(store => store.refreshProfile) // ====================== // render @@ -116,7 +116,7 @@ export default function IdentifyPage(props: IdentifyPageProps) {

个人认证

平台授权支付宝安全认证,不会泄露您的认证信息

- {ctx.profile?.id_token ? ( + {profile?.id_token ? (

已完成实名认证

@@ -162,7 +162,7 @@ export default function IdentifyPage(props: IdentifyPageProps) {

请扫码完成认证