diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx index 1ce307d..c76e358 100644 --- a/web/src/pages/LoginPage.tsx +++ b/web/src/pages/LoginPage.tsx @@ -1,22 +1,22 @@ -import { FormEvent, useState } from 'react' +import { useState } from 'react' +import type { FormEvent } from 'react' import { useNavigate } from 'react-router-dom' -import { motion } from 'framer-motion' import toast from 'react-hot-toast' -import { Eye, EyeOff, ArrowRight, Lock, User } from 'lucide-react' -import { AppFooter } from '../components/AppFooter' + import { authAPI } from '../api/auth' import { useAuthStore } from '../stores/auth' +import { LoginCard, LoginPageShell } from './LoginPageSections' export function LoginPage() { const navigate = useNavigate() - const setSession = useAuthStore((s) => s.setSession) + const setSession = useAuthStore((state) => state.setSession) const [username, setUsername] = useState('') const [password, setPassword] = useState('') - const [showPw, setShowPw] = useState(false) + const [showPassword, setShowPassword] = useState(false) const [loading, setLoading] = useState(false) - const handleSubmit = async (e: FormEvent) => { - e.preventDefault() + const handleSubmit = async (event: FormEvent) => { + event.preventDefault() setLoading(true) try { const data = await authAPI.login(username, password) @@ -26,159 +26,23 @@ export function LoginPage() { } catch (err: unknown) { const msg = (err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? '登录失败' toast.error(msg) - } finally { setLoading(false) } + } finally { + setLoading(false) + } } return ( -
- {/* ── Editorial Soft Geometric Background Decor ── */} -
-
-
-
-
- - {/* ── Main Container ── */} - - {/* High-Contrast Swiss White Panel */} -
- - {/* Logo & Headline */} -
- - - - MediaStationGo - - - - 一站式媒体管理中心 - -
- - {/* Form */} -
- {/* Username Input with Integrated Icon */} - - -
- - - - setUsername(e.target.value)} - autoComplete="username" - autoFocus - required - placeholder="请输入您的账号" - /> -
-
- - {/* Password Input with Integrated Icon */} - - -
- - - - setPassword(e.target.value)} - autoComplete="current-password" - required - placeholder="••••••••" - /> - -
-
- - {/* Submit Button */} - - - -
-
-
- - {/* Footer */} - - - -
+ + setShowPassword((visible) => !visible)} + onSubmit={handleSubmit} + /> + ) } diff --git a/web/src/pages/LoginPageSections.tsx b/web/src/pages/LoginPageSections.tsx new file mode 100644 index 0000000..e223068 --- /dev/null +++ b/web/src/pages/LoginPageSections.tsx @@ -0,0 +1,234 @@ +import type { FormEvent, ReactNode } from 'react' +import { motion } from 'framer-motion' +import { ArrowRight, Eye, EyeOff, Lock, User } from 'lucide-react' + +import { AppFooter } from '../components/AppFooter' + +type LoginPageShellProps = { + children: ReactNode +} + +type LoginCardProps = { + username: string + password: string + showPassword: boolean + loading: boolean + onUsernameChange: (value: string) => void + onPasswordChange: (value: string) => void + onTogglePassword: () => void + onSubmit: (event: FormEvent) => void +} + +type LoginInputProps = { + label: string + value: string + type: string + autoComplete: string + placeholder: string + delay: number + icon: ReactNode + autoFocus?: boolean + trailing?: ReactNode + onChange: (value: string) => void +} + +export function LoginPageShell({ children }: LoginPageShellProps) { + return ( +
+ + {children} + +
+ ) +} + +function LoginBackground() { + return ( +
+
+
+
+
+ ) +} + +export function LoginCard(props: LoginCardProps) { + return ( + +
+ + +
+
+ ) +} + +function LoginBrandHeader() { + return ( +
+ + + + MediaStationGo + + + + 一站式媒体管理中心 + +
+ ) +} + +function LoginForm({ + username, + password, + showPassword, + loading, + onUsernameChange, + onPasswordChange, + onTogglePassword, + onSubmit, +}: LoginCardProps) { + return ( +
+ } + autoFocus + onChange={onUsernameChange} + /> + } + trailing={} + onChange={onPasswordChange} + /> + + + ) +} + +function LoginInput({ + label, + value, + type, + autoComplete, + placeholder, + delay, + icon, + autoFocus, + trailing, + onChange, +}: LoginInputProps) { + return ( + + +
+ {icon} + onChange(event.target.value)} + autoComplete={autoComplete} + autoFocus={autoFocus} + required + placeholder={placeholder} + /> + {trailing} +
+
+ ) +} + +function PasswordVisibilityButton({ visible, onToggle }: { visible: boolean; onToggle: () => void }) { + return ( + + ) +} + +function LoginSubmitButton({ loading }: { loading: boolean }) { + return ( + + + + ) +} + +function LoginSpinnerLabel() { + return ( + + + + + + 正在进入系统舱... + + ) +} + +function LoginReadyLabel() { + return ( + + 立即开启观影之旅 + + ) +} + +function LoginFooter() { + return ( + + + + ) +}