From 487621a645eec96da2bacfdf9e9a7612fe1538c1 Mon Sep 17 00:00:00 2001 From: hobby Date: Tue, 25 Mar 2025 08:25:40 +0800 Subject: [PATCH] update loyout --- .../auth.ts => api/_middleware.js} | 42 ++++++--- package-lock.json | 62 +++++++++++++ package.json | 1 + src/App.tsx | 86 ++----------------- src/components/AuthGuard.jsx | 16 ++++ src/layouts/BasicLayout.tsx | 22 +++++ src/{ => pages/chat}/components/AdSection.tsx | 2 +- src/{ => pages/chat}/components/ChatUI.tsx | 4 +- src/{ => pages/chat}/components/Header.tsx | 0 src/{ => pages/chat}/components/Layout.tsx | 0 .../chat}/components/MembersManagement.tsx | 0 .../chat}/components/SharePoster.tsx | 0 src/{ => pages/chat}/components/Sidebar.tsx | 0 src/pages/chat/index.tsx | 7 ++ .../login/comonents}/PhoneLogin.tsx | 5 +- src/pages/login/index.jsx | 17 ++++ src/routes.tsx | 26 ++++++ src/utils/request.ts | 36 ++++++++ warngler.toml | 3 - 19 files changed, 229 insertions(+), 100 deletions(-) rename functions/{middleware/auth.ts => api/_middleware.js} (55%) create mode 100644 src/components/AuthGuard.jsx create mode 100644 src/layouts/BasicLayout.tsx rename src/{ => pages/chat}/components/AdSection.tsx (99%) rename src/{ => pages/chat}/components/ChatUI.tsx (99%) rename src/{ => pages/chat}/components/Header.tsx (100%) rename src/{ => pages/chat}/components/Layout.tsx (100%) rename src/{ => pages/chat}/components/MembersManagement.tsx (100%) rename src/{ => pages/chat}/components/SharePoster.tsx (100%) rename src/{ => pages/chat}/components/Sidebar.tsx (100%) create mode 100644 src/pages/chat/index.tsx rename src/{components => pages/login/comonents}/PhoneLogin.tsx (96%) create mode 100644 src/pages/login/index.jsx create mode 100644 src/routes.tsx create mode 100644 src/utils/request.ts delete mode 100644 warngler.toml diff --git a/functions/middleware/auth.ts b/functions/api/_middleware.js similarity index 55% rename from functions/middleware/auth.ts rename to functions/api/_middleware.js index 05b2450..de21585 100644 --- a/functions/middleware/auth.ts +++ b/functions/api/_middleware.js @@ -1,8 +1,4 @@ -interface Env { - JWT_SECRET: string; -} - -export async function verifyToken(token: string, env: Env) { +async function verifyToken(token, env) { try { const [headerB64, payloadB64, signature] = token.split('.'); @@ -49,12 +45,32 @@ export async function verifyToken(token: string, env: Env) { } // 中间件函数 -export const authMiddleware = async (request: Request, env: Env) => { - const authHeader = request.headers.get('Authorization'); - if (!authHeader || !authHeader.startsWith('Bearer ')) { - throw new Error('No token provided'); +export async function onRequest(context) { + try { + const request = context.request; + const env = context.env; + //跳过登录页面 + if (request.url.includes('/login') || request.url.includes('/sendcode') || request.url.includes('/login')) { + return await context.next(); + } + const authHeader = request.headers.get('Authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + throw new Error('No token provided'); + } + + const token = authHeader.split(' ')[1]; + const payload = await verifyToken(token, env); + + // 将用户信息添加到上下文中 + context.user = payload; + + return await context.next(); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 401, + headers: { + 'Content-Type': 'application/json' + } + }); } - - const token = authHeader.split(' ')[1]; - return await verifyToken(token, env); -}; \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index c558cb6..cfea23a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-dom": "^18.2.0", "react-github-btn": "^1.4.0", "react-markdown": "^9.0.3", + "react-router-dom": "^7.4.0", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", @@ -2405,6 +2406,11 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", @@ -6060,6 +6066,52 @@ } } }, + "node_modules/react-router": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.4.0.tgz", + "integrity": "sha512-Y2g5ObjkvX3VFeVt+0CIPuYd9PpgqCslG7ASSIdN73LwA1nNWzcMLaoMRJfP3prZFI92svxFwbn7XkLJ+UPQ6A==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.4.0.tgz", + "integrity": "sha512-VlksBPf3n2bijPvnA7nkTsXxMAKOj+bWp4R9c3i+bnwlSOFAGOkJkKhzy/OsRkWaBMICqcAl1JDzh9ZSOze9CA==", + "dependencies": { + "react-router": "7.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -6449,6 +6501,11 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.33.5.tgz", @@ -6918,6 +6975,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==" + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", diff --git a/package.json b/package.json index 8fa2622..27240bc 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-dom": "^18.2.0", "react-github-btn": "^1.4.0", "react-markdown": "^9.0.3", + "react-router-dom": "^7.4.0", "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", diff --git a/src/App.tsx b/src/App.tsx index bfbf30e..957ceec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,83 +1,11 @@ -import React, { useState, useEffect } from 'react'; -import ChatUI from './components/ChatUI'; -import PhoneLogin from './components/PhoneLogin'; -import './App.css'; -import Layout from './components/Layout'; - -const App: React.FC = () => { - const [isLoggedIn, setIsLoggedIn] = useState(false); - const [isLoading, setIsLoading] = useState(true); - - // 在组件加载时检查登录状态 - useEffect(() => { - checkLoginStatus(); - }, []); - - // 检查登录状态 - const checkLoginStatus = async () => { - try { - // 从 localStorage 获取 token - const token = localStorage.getItem('token'); - if (!token) { - setIsLoggedIn(false); - setIsLoading(false); - return; - } - - // 验证 token 有效性 - const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/verify-token`, { - headers: { - 'Authorization': `Bearer ${token}` - } - }); - - if (response.ok) { - setIsLoggedIn(true); - } else { - // token 无效,清除存储的 token - localStorage.removeItem('token'); - setIsLoggedIn(false); - } - } catch (error) { - console.error('验证登录状态失败:', error); - setIsLoggedIn(false); - } finally { - setIsLoading(false); - } - }; - - - // 处理登出 - const handleLogout = () => { - localStorage.removeItem('token'); - setIsLoggedIn(false); - }; - - if (isLoading) { - return ( -
-
-
- ); - } +import { RouterProvider } from 'react-router-dom'; +import { router } from './routes'; +function App() { + console.log("App rendering"); // 添加日志 return ( - <> - {isLoggedIn ? ( -
- - -
- ) : ( - - )} - + ); -}; +} -export default App; +export default App; \ No newline at end of file diff --git a/src/components/AuthGuard.jsx b/src/components/AuthGuard.jsx new file mode 100644 index 0000000..738133e --- /dev/null +++ b/src/components/AuthGuard.jsx @@ -0,0 +1,16 @@ +import { Navigate, useLocation } from 'react-router-dom'; + +export default function AuthGuard({ children }) { + const location = useLocation(); + const token = localStorage.getItem('token'); + + if (!token && location.pathname !== '/login') { + return ; + } + + if (token && location.pathname === '/login') { + return ; + } + + return children; +} \ No newline at end of file diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx new file mode 100644 index 0000000..12e32c0 --- /dev/null +++ b/src/layouts/BasicLayout.tsx @@ -0,0 +1,22 @@ +import { Outlet, useNavigate } from 'react-router-dom'; + +export default function BasicLayout() { + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem('token'); + navigate('/login'); + }; + + return ( +
+
+
AI Chat
+ +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/components/AdSection.tsx b/src/pages/chat/components/AdSection.tsx similarity index 99% rename from src/components/AdSection.tsx rename to src/pages/chat/components/AdSection.tsx index a36ce32..1bff3b7 100644 --- a/src/components/AdSection.tsx +++ b/src/pages/chat/components/AdSection.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { cn } from '../lib/utils'; +import { cn } from '@/lib/utils'; import { Popover, PopoverContent, diff --git a/src/components/ChatUI.tsx b/src/pages/chat/components/ChatUI.tsx similarity index 99% rename from src/components/ChatUI.tsx rename to src/pages/chat/components/ChatUI.tsx index 5e21286..89541d8 100644 --- a/src/components/ChatUI.tsx +++ b/src/pages/chat/components/ChatUI.tsx @@ -17,8 +17,8 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm' import remarkMath from 'remark-math' import rehypeKatex from 'rehype-katex' -import { SharePoster } from '@/components/SharePoster'; -import { MembersManagement } from '@/components/MembersManagement'; +import { SharePoster } from '@/pages/chat/components/SharePoster'; +import { MembersManagement } from '@/pages/chat/components/MembersManagement'; import Sidebar from './Sidebar'; import { AdBanner, AdBannerMobile } from './AdSection'; // 使用本地头像数据,避免外部依赖 diff --git a/src/components/Header.tsx b/src/pages/chat/components/Header.tsx similarity index 100% rename from src/components/Header.tsx rename to src/pages/chat/components/Header.tsx diff --git a/src/components/Layout.tsx b/src/pages/chat/components/Layout.tsx similarity index 100% rename from src/components/Layout.tsx rename to src/pages/chat/components/Layout.tsx diff --git a/src/components/MembersManagement.tsx b/src/pages/chat/components/MembersManagement.tsx similarity index 100% rename from src/components/MembersManagement.tsx rename to src/pages/chat/components/MembersManagement.tsx diff --git a/src/components/SharePoster.tsx b/src/pages/chat/components/SharePoster.tsx similarity index 100% rename from src/components/SharePoster.tsx rename to src/pages/chat/components/SharePoster.tsx diff --git a/src/components/Sidebar.tsx b/src/pages/chat/components/Sidebar.tsx similarity index 100% rename from src/components/Sidebar.tsx rename to src/pages/chat/components/Sidebar.tsx diff --git a/src/pages/chat/index.tsx b/src/pages/chat/index.tsx new file mode 100644 index 0000000..e7d9216 --- /dev/null +++ b/src/pages/chat/index.tsx @@ -0,0 +1,7 @@ +import ChatUI from './components/ChatUI'; + +export default function Chat() { + return ( + + ); +} diff --git a/src/components/PhoneLogin.tsx b/src/pages/login/comonents/PhoneLogin.tsx similarity index 96% rename from src/components/PhoneLogin.tsx rename to src/pages/login/comonents/PhoneLogin.tsx index 61868b4..44c49e4 100644 --- a/src/components/PhoneLogin.tsx +++ b/src/pages/login/comonents/PhoneLogin.tsx @@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input"; interface PhoneLoginProps { onLogin: (phone: string, code: string) => void; } +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; const PhoneLogin: React.FC = ({ onLogin }) => { const [phone, setPhone] = useState(''); @@ -21,7 +22,7 @@ const PhoneLogin: React.FC = ({ onLogin }) => { //setIsLoading(true); try { - const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/sendcode`, { + const response = await fetch(`${API_BASE_URL}/api/sendcode`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -64,7 +65,7 @@ const PhoneLogin: React.FC = ({ onLogin }) => { setIsLoading(true); try { - const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/login`, { + const response = await fetch(`${API_BASE_URL}/api/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/pages/login/index.jsx b/src/pages/login/index.jsx new file mode 100644 index 0000000..b5aef5d --- /dev/null +++ b/src/pages/login/index.jsx @@ -0,0 +1,17 @@ +import { useNavigate } from 'react-router-dom'; +import PhoneLogin from './comonents/PhoneLogin'; + +export default function Login() { + const navigate = useNavigate(); + + const handleLoginSuccess = (token) => { + localStorage.setItem('token', token); + navigate('/'); + }; + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx new file mode 100644 index 0000000..162afa5 --- /dev/null +++ b/src/routes.tsx @@ -0,0 +1,26 @@ +import { createBrowserRouter } from 'react-router-dom'; +import Login from './pages/login'; +import Chat from './pages/chat'; +import BasicLayout from './layouts/BasicLayout'; +import AuthGuard from './components/AuthGuard'; + +export const router = createBrowserRouter([ + { + path: '/login', + element: , + }, + { + path: '/', + element: ( + + + + ), + children: [ + { + path: '', + element: , + }, + ], + }, +]); \ No newline at end of file diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..b309ad7 --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,36 @@ +export async function request(url: string, options: RequestInit = {}) { + const token = localStorage.getItem('token'); + + const headers = { + 'Content-Type': 'application/json', + ...options.headers, + }; + + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + try { + const response = await fetch(url, { + ...options, + headers, + }); + + // 如果返回 401,清除 token 并跳转到登录页 + if (response.status === 401) { + localStorage.removeItem('token'); + window.location.href = '/login'; + throw new Error('Unauthorized'); + } + + if (!response.ok) { + throw new Error('Request failed'); + } + + return response.json(); + } catch (error) { + // 如果是网络错误或其他错误,也可以处理 + console.error('Request error:', error); + throw error; + } +} \ No newline at end of file diff --git a/warngler.toml b/warngler.toml deleted file mode 100644 index 829ef71..0000000 --- a/warngler.toml +++ /dev/null @@ -1,3 +0,0 @@ -[observability] -enabled = true -head_sampling_rate = 1 # optional. default = 1. \ No newline at end of file