feat: simplify login page UX and add i18n English support
- Redesign LoginPage with minimal professional style - Remove animated gradient backgrounds and floating orbs - Remove marketing claims (99% accuracy, enterprise-grade) - Center login form with clean card design - Add multi-language support (zh-TW, en-US) - Create LanguageSwitcher component in sidebar - Add en-US.json translation file - Persist language preference in localStorage - Remove unused top header bar with search - Move language switcher to sidebar user section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
45
frontend/src/components/LanguageSwitcher.tsx
Normal file
45
frontend/src/components/LanguageSwitcher.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Globe } from 'lucide-react'
|
||||
|
||||
const languages = [
|
||||
{ code: 'zh-TW', label: '繁體中文' },
|
||||
{ code: 'en-US', label: 'English' },
|
||||
]
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const handleLanguageChange = (langCode: string) => {
|
||||
i18n.changeLanguage(langCode)
|
||||
localStorage.setItem('tool-ocr-language', langCode)
|
||||
}
|
||||
|
||||
const currentLang = languages.find((lang) => lang.code === i18n.language) || languages[0]
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<button
|
||||
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-sidebar-foreground/70
|
||||
hover:text-sidebar-foreground hover:bg-white/5 rounded-lg transition-colors"
|
||||
>
|
||||
<Globe className="w-5 h-5" />
|
||||
<span>{currentLang.label}</span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown */}
|
||||
<div className="absolute left-0 bottom-full mb-1 py-1 bg-card border border-border rounded-lg shadow-lg
|
||||
opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50 min-w-[140px]">
|
||||
{languages.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => handleLanguageChange(lang.code)}
|
||||
className={`w-full px-3 py-2 text-sm text-left hover:bg-muted transition-colors
|
||||
${i18n.language === lang.code ? 'text-primary font-medium' : 'text-foreground'}`}
|
||||
>
|
||||
{lang.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Outlet, NavLink, useNavigate } from 'react-router-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { apiClientV2 } from '@/services/apiV2'
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher'
|
||||
import {
|
||||
Upload,
|
||||
Settings,
|
||||
@@ -11,8 +12,6 @@ import {
|
||||
LogOut,
|
||||
LayoutDashboard,
|
||||
ChevronRight,
|
||||
Bell,
|
||||
Search,
|
||||
History,
|
||||
Shield
|
||||
} from 'lucide-react'
|
||||
@@ -96,9 +95,9 @@ export default function Layout() {
|
||||
</nav>
|
||||
|
||||
{/* User section */}
|
||||
<div className="px-3 py-4 border-t border-border/20">
|
||||
<div className="px-3 py-4 border-t border-border/20 space-y-2">
|
||||
{user && (
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-lg bg-white/5 mb-2">
|
||||
<div className="flex items-center gap-3 px-3 py-2 rounded-lg bg-white/5">
|
||||
<div className="w-8 h-8 rounded-full bg-primary flex items-center justify-center text-white font-semibold text-sm flex-shrink-0">
|
||||
{user.username.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
@@ -108,6 +107,9 @@ export default function Layout() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sidebar-foreground/70 hover:text-sidebar-foreground hover:bg-white/5 transition-colors text-sm"
|
||||
@@ -119,37 +121,11 @@ export default function Layout() {
|
||||
</aside>
|
||||
|
||||
{/* Main content area */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Top bar */}
|
||||
<header className="bg-card border-b border-border px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Search bar - placeholder for future use */}
|
||||
<div className="flex-1 max-w-2xl">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-muted-foreground" />
|
||||
<input
|
||||
type="search"
|
||||
placeholder="搜尋檔案或功能..."
|
||||
className="w-full pl-10 pr-4 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<button className="ml-4 p-2 rounded-lg hover:bg-muted transition-colors relative">
|
||||
<Bell className="w-5 h-5 text-muted-foreground" />
|
||||
<span className="absolute top-1 right-1 w-2 h-2 bg-destructive rounded-full"></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="flex-1 overflow-y-auto bg-background p-6 scrollbar-thin">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<main className="flex-1 overflow-y-auto bg-background p-6 scrollbar-thin">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user