From af7bcb89d9d481b893a708a5adafa8ed028ce237 Mon Sep 17 00:00:00 2001 From: shenlan Date: Wed, 1 Oct 2025 11:20:39 +0800 Subject: [PATCH] Improve login flow and account navigation (#340) --- ui/homepage/app/api/auth/login/route.ts | 6 +- ui/homepage/app/login/LoginForm.tsx | 25 ++---- ui/homepage/app/logout/page.tsx | 76 +++++++++++++++++ ui/homepage/components/Navbar.tsx | 104 ++++++++++++------------ 4 files changed, 137 insertions(+), 74 deletions(-) create mode 100644 ui/homepage/app/logout/page.tsx diff --git a/ui/homepage/app/api/auth/login/route.ts b/ui/homepage/app/api/auth/login/route.ts index da96422..79e2324 100644 --- a/ui/homepage/app/api/auth/login/route.ts +++ b/ui/homepage/app/api/auth/login/route.ts @@ -43,7 +43,11 @@ export async function POST(request: NextRequest) { const cookieMaxAge = remember ? 60 * 60 * 24 * 30 : 60 * 60 * 24 const wantsJSON = prefersJson(request) const successResponse = wantsJSON - ? NextResponse.json({ success: true, message: data?.message ?? 'login_success' }) + ? NextResponse.json({ + success: true, + message: data?.message ?? 'login_success', + redirectTo: '/', + }) : NextResponse.redirect(new URL('/', request.url), { status: 303 }) successResponse.cookies.set({ diff --git a/ui/homepage/app/login/LoginForm.tsx b/ui/homepage/app/login/LoginForm.tsx index 5ea0edc..396a5e8 100644 --- a/ui/homepage/app/login/LoginForm.tsx +++ b/ui/homepage/app/login/LoginForm.tsx @@ -1,6 +1,6 @@ 'use client' -import { FormEvent, useEffect, useRef, useState } from 'react' +import { FormEvent, useState } from 'react' import { useRouter } from 'next/navigation' import { useLanguage } from '@i18n/LanguageProvider' @@ -12,21 +12,11 @@ export function LoginForm() { const { language } = useLanguage() const copy = translations[language].login const authCopy = translations[language].auth.login - const { user, login, logout } = useUser() + const { user, login } = useUser() const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) - const [success, setSuccess] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) - const redirectTimer = useRef(null) - - useEffect(() => { - return () => { - if (redirectTimer.current) { - clearTimeout(redirectTimer.current) - } - } - }, []) const handleSubmit = async (event: FormEvent) => { event.preventDefault() @@ -40,7 +30,6 @@ export function LoginForm() { } setError(null) - setSuccess(null) setIsSubmitting(true) try { const response = await fetch('/api/auth/login', { @@ -73,11 +62,10 @@ export function LoginForm() { return } - setSuccess(copy.success.replace('{username}', username.trim())) await login(username.trim()) - redirectTimer.current = setTimeout(() => { - router.push('/') - }, 800) + router.replace('/') + router.refresh() + return } catch (submitError) { console.warn('Login failed', submitError) setError(copy.genericError) @@ -91,7 +79,7 @@ export function LoginForm() { } const handleLogout = () => { - logout() + router.push('/logout') } return ( @@ -170,7 +158,6 @@ export function LoginForm() {

* {copy.disclaimer}

- {success ?

{success}

: null} diff --git a/ui/homepage/app/logout/page.tsx b/ui/homepage/app/logout/page.tsx new file mode 100644 index 0000000..2e90b79 --- /dev/null +++ b/ui/homepage/app/logout/page.tsx @@ -0,0 +1,76 @@ +'use client' + +import { useEffect } from 'react' +import { useRouter } from 'next/navigation' + +import Navbar from '@components/Navbar' +import Footer from '@components/Footer' +import { AskAIButton } from '@components/AskAIButton' +import { useLanguage } from '@i18n/LanguageProvider' +import { translations } from '@i18n/translations' +import { useUser } from '@lib/userStore' + +export default function LogoutPage() { + const router = useRouter() + const { logout } = useUser() + const { language } = useLanguage() + const navCopy = translations[language].nav.account + const signingOutMessage = language === 'zh' ? '正在安全退出,请稍候…' : 'Signing you out safely. One moment…' + + useEffect(() => { + let cancelled = false + const performLogout = async () => { + try { + await logout() + } finally { + if (!cancelled) { + router.replace('/') + router.refresh() + } + } + } + + void performLogout() + + return () => { + cancelled = true + } + }, [logout, router]) + + return ( +
+ +
+
+
+ + + + +
+

{navCopy.logout}

+

{signingOutMessage}

+
+
+
+ +
+ ) +} diff --git a/ui/homepage/components/Navbar.tsx b/ui/homepage/components/Navbar.tsx index 892c1d3..b1957fd 100644 --- a/ui/homepage/components/Navbar.tsx +++ b/ui/homepage/components/Navbar.tsx @@ -34,7 +34,7 @@ export default function Navbar() { const [activeItem, setActiveItem] = useState(null) const [selectedChannels, setSelectedChannels] = useState(['stable']) const { language } = useLanguage() - const { user, logout } = useUser() + const { user } = useUser() const nav = translations[language].nav const channelLabels = nav.releaseChannels const accountCopy = nav.account @@ -98,6 +98,41 @@ export default function Navbar() { const selectedChannelSet = useMemo(() => new Set(selectedChannels), [selectedChannels]) + const accountChildren: NavSubItem[] = user + ? [ + { + key: 'userCenter', + label: accountCopy.userCenter, + href: '/panel', + togglePath: '/panel', + }, + { + key: 'logout', + label: accountCopy.logout, + href: '/logout', + }, + ] + : [ + { + key: 'register', + label: nav.account.register, + href: '/register', + togglePath: '/register', + }, + { + key: 'login', + label: nav.account.login, + href: '/login', + togglePath: '/login', + }, + { + key: 'demo', + label: nav.account.demo, + href: '/demo', + togglePath: '/demo', + }, + ] + const navItems: NavItem[] = [ { key: 'openSource', @@ -152,46 +187,12 @@ export default function Navbar() { }, { key: 'account', - label: nav.account.title, - children: [ - { - key: 'register', - label: nav.account.register, - href: '/register', - togglePath: '/register', - }, - { - key: 'login', - label: nav.account.login, - href: '/login', - togglePath: '/login', - }, - { - key: 'demo', - label: nav.account.demo, - href: '/demo', - togglePath: '/demo', - }, - ], + label: user?.username ?? nav.account.title, + children: accountChildren, }, ] - const navItemsWithAuth: NavItem[] = user - ? navItems.map((item) => { - if (item.key !== 'account') { - return item - } - - return { - ...item, - children: item.children.filter( - (child) => child.key !== 'login' && child.key !== 'register', - ), - } - }) - : navItems - - const visibleNavItems: NavItem[] = navItemsWithAuth + const visibleNavItems: NavItem[] = navItems .map((item) => ({ ...item, children: item.children @@ -330,22 +331,19 @@ export default function Navbar() { ) : null} @@ -449,21 +447,19 @@ export default function Navbar() { setMenuOpen(false)} > {accountCopy.userCenter} - + ) : null}