From 2bb6302cbc67aca67bedb461e532f1a0e71903e8 Mon Sep 17 00:00:00 2001 From: samanhappy Date: Sat, 10 May 2025 21:33:05 +0800 Subject: [PATCH] feat: enhance user experience with version info (#69) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- frontend/src/components/icons/LucideIcons.tsx | 10 +- frontend/src/components/layout/Header.tsx | 34 +---- frontend/src/components/layout/Sidebar.tsx | 61 +++++---- frontend/src/components/ui/AboutDialog.tsx | 92 +++++++++++++ .../src/components/ui/UserProfileMenu.tsx | 127 ++++++++++++++++++ frontend/src/layouts/MainLayout.tsx | 2 +- frontend/src/locales/en.json | 13 +- frontend/src/locales/zh.json | 13 +- frontend/src/vite-env.d.ts | 9 ++ frontend/vite.config.ts | 11 ++ package.json | 2 +- 11 files changed, 310 insertions(+), 64 deletions(-) create mode 100644 frontend/src/components/ui/AboutDialog.tsx create mode 100644 frontend/src/components/ui/UserProfileMenu.tsx create mode 100644 frontend/src/vite-env.d.ts diff --git a/frontend/src/components/icons/LucideIcons.tsx b/frontend/src/components/icons/LucideIcons.tsx index e086ec9..2d9a536 100644 --- a/frontend/src/components/icons/LucideIcons.tsx +++ b/frontend/src/components/icons/LucideIcons.tsx @@ -1,6 +1,6 @@ -import { ChevronDown, ChevronRight, Edit, Trash, Copy, Check } from 'lucide-react' +import { ChevronDown, ChevronRight, Edit, Trash, Copy, Check, User, Settings, LogOut, Info } from 'lucide-react' -export { ChevronDown, ChevronRight, Edit, Trash, Copy, Check } +export { ChevronDown, ChevronRight, Edit, Trash, Copy, Check, User, Settings, LogOut, Info } const LucideIcons = { ChevronDown, @@ -8,7 +8,11 @@ const LucideIcons = { Edit, Trash, Copy, - Check + Check, + User, + Settings, + LogOut, + Info } export default LucideIcons \ No newline at end of file diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index 1a718a8..b3afa5f 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; import { useAuth } from '@/contexts/AuthContext'; import ThemeSwitch from '@/components/ui/ThemeSwitch'; @@ -10,20 +9,14 @@ interface HeaderProps { const Header: React.FC = ({ onToggleSidebar }) => { const { t } = useTranslation(); - const navigate = useNavigate(); - const { auth, logout } = useAuth(); - - const handleLogout = () => { - logout(); - navigate('/login'); - }; + const { auth } = useAuth(); return (
{/* 侧边栏切换按钮 */} - - + {/* 应用标题 */}

{t('app.title')}

- - {/* 用户信息和操作 */} + + {/* Theme Switch */}
- {/* Theme Switch */} - - {auth.user && ( - - {t('app.welcomeUser', { username: auth.user.username })} - - )} - -
- -
+
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 51aaa7d..71b5839 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { NavLink, useLocation } from 'react-router-dom'; +import UserProfileMenu from '@/components/ui/UserProfileMenu'; interface SidebarProps { collapsed: boolean; @@ -16,6 +17,9 @@ const Sidebar: React.FC = ({ collapsed }) => { const { t } = useTranslation(); const location = useLocation(); + // Application version from package.json (accessed via Vite environment variables) + const appVersion = import.meta.env.PACKAGE_VERSION as string; + // Menu item configuration const menuItems: MenuItem[] = [ { @@ -64,42 +68,41 @@ const Sidebar: React.FC = ({ collapsed }) => { ), }, - { - path: '/settings', - label: t('nav.settings'), - icon: ( - - - - ), - }, ]; return ( ); }; diff --git a/frontend/src/components/ui/AboutDialog.tsx b/frontend/src/components/ui/AboutDialog.tsx new file mode 100644 index 0000000..4d74073 --- /dev/null +++ b/frontend/src/components/ui/AboutDialog.tsx @@ -0,0 +1,92 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { X } from 'lucide-react'; + +interface AboutDialogProps { + isOpen: boolean; + onClose: () => void; + version: string; +} + +const AboutDialog: React.FC = ({ isOpen, onClose, version }) => { + const { t } = useTranslation(); + const [hasNewVersion, setHasNewVersion] = useState(false); + const [latestVersion, setLatestVersion] = useState(""); + + // Check if there's a new version available + // In a real application, this would make an API call to check for updates + useEffect(() => { + if (isOpen) { + // Mock implementation - in real implementation, you would fetch from an API + const checkForUpdates = async () => { + try { + // This is a placeholder - in a real app you would call an API + // const response = await fetch('/api/check-updates'); + // const data = await response.json(); + + // For demo purposes, we'll just pretend there's a new version + // TODO: Replace this placeholder logic with a real API call to check for updates + setHasNewVersion(false); + setLatestVersion("0.0.28"); // Just a higher version for demo + } catch (error) { + console.error('Failed to check for updates:', error); + } + }; + + checkForUpdates(); + } + }, [isOpen]); + + if (!isOpen) return null; + + return ( +
+
+
+ {/* Close button (X) in the top-right corner */} + + +

+ {t('about.title')} +

+ +
+
+ + {t('about.currentVersion')}: + + + {version} + +
+ + {hasNewVersion && ( +
+
+
+ + + +
+
+

+ {t('about.newVersion')} (v{latestVersion}) +

+
+
+
+ )} +
+
+
+
+ ); +}; + +export default AboutDialog; diff --git a/frontend/src/components/ui/UserProfileMenu.tsx b/frontend/src/components/ui/UserProfileMenu.tsx new file mode 100644 index 0000000..275f2b4 --- /dev/null +++ b/frontend/src/components/ui/UserProfileMenu.tsx @@ -0,0 +1,127 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '@/contexts/AuthContext'; +import { User, Settings, LogOut, Info } from 'lucide-react'; +import AboutDialog from './AboutDialog'; + +interface UserProfileMenuProps { + collapsed: boolean; + version: string; +} + +const UserProfileMenu: React.FC = ({ collapsed, version }) => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const { auth, logout } = useAuth(); + const [isOpen, setIsOpen] = useState(false); + const [showNewVersionInfo, setShowNewVersionInfo] = useState(false); + const [showAboutDialog, setShowAboutDialog] = useState(false); + const menuRef = useRef(null); + + // Check for new version - this could be replaced with a real API call + useEffect(() => { + // Mock implementation - in a real app, you would check against an API + const checkForNewVersion = async () => { + // For demo purposes, we're just showing the notification + // In real implementation, you would compare current version with latest version + setShowNewVersionInfo(false); + }; + + checkForNewVersion(); + }, []); + + // Close the menu when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const handleSettingsClick = () => { + navigate('/settings'); + setIsOpen(false); + }; + + const handleLogoutClick = () => { + logout(); + navigate('/login'); + }; + + const handleAboutClick = () => { + setShowAboutDialog(true); + setIsOpen(false); + }; + + return ( +
+ + + {isOpen && ( +
+ + + +
+ )} + + {/* About dialog */} + setShowAboutDialog(false)} + version={version} + /> +
+ ); +}; + +export default UserProfileMenu; diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index bd314b2..e0a3204 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -13,7 +13,7 @@ const MainLayout: React.FC = () => { }; return ( -
+
{/* 顶部导航 */}
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 2566332..a8355b3 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -12,6 +12,16 @@ "welcomeUser": "Welcome, {{username}}", "name": "MCP Hub" }, + "about": { + "title": "About", + "versionInfo": "MCP Hub Version: {{version}}", + "newVersion": "New version available!", + "currentVersion": "Current version" + }, + "profile": { + "viewProfile": "View profile", + "userCenter": "User Center" + }, "theme": { "title": "Theme", "light": "Light", @@ -105,7 +115,8 @@ "delete": "Delete", "copy": "Copy", "copySuccess": "Copied to clipboard", - "copyFailed": "Copy failed" + "copyFailed": "Copy failed", + "close": "Close" }, "nav": { "dashboard": "Dashboard", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index a3e0f47..67d14fc 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -12,6 +12,16 @@ "welcomeUser": "欢迎, {{username}}", "name": "MCP Hub" }, + "about": { + "title": "关于", + "versionInfo": "MCP Hub 版本: {{version}}", + "newVersion": "有新版本可用!", + "currentVersion": "当前版本" + }, + "profile": { + "viewProfile": "查看个人中心", + "userCenter": "个人中心" + }, "theme": { "title": "主题", "light": "浅色", @@ -106,7 +116,8 @@ "delete": "删除", "copy": "复制", "copySuccess": "已复制到剪贴板", - "copyFailed": "复制失败" + "copyFailed": "复制失败", + "close": "关闭" }, "nav": { "dashboard": "仪表盘", diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..07e677c --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMeta { + readonly env: { + readonly PACKAGE_VERSION: string; + // Add other custom env variables here if needed + [key: string]: any; + }; +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 086adae..ba9a1d0 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,6 +2,13 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; import tailwindcss from '@tailwindcss/vite'; +// Import the package.json to get the version +import { readFileSync } from 'fs'; + +// Get package.json version +const packageJson = JSON.parse( + readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8') +); // https://vitejs.dev/config/ export default defineConfig({ @@ -11,6 +18,10 @@ export default defineConfig({ '@': path.resolve(__dirname, './src'), }, }, + define: { + // Make package version available as global variable + 'import.meta.env.PACKAGE_VERSION': JSON.stringify(packageJson.version), + }, server: { proxy: { '/api': { diff --git a/package.json b/package.json index 1e6a047..57d5fb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@samanhappy/mcphub", - "version": "0.0.27", + "version": "0.5.4", "description": "A hub server for mcp servers", "main": "dist/index.js", "type": "module",