main
import type { ReactNode } from 'react'
import { useEffect, useState } from 'react'
import { createBrowserRouter, Outlet, useLocation, useNavigate } from 'react-router-dom'
import { registrationDraft } from '@/entities/registration'
import type { LoginComplete } from '@/entities/session'
import { mfaChallenge, useSession } from '@/entities/session'
import { AccountPage } from '@/pages/account'
import { ForgotPasswordPage } from '@/pages/forgot-password'
import { LoginPage } from '@/pages/login'
import { LoginCodePage } from '@/pages/login-code'
import { LoginVerifyPage } from '@/pages/login-verify'
import { NotFoundPage } from '@/pages/not-found'
import { RegisterPage } from '@/pages/register'
import { RegisterVerifyPage } from '@/pages/register-verify'
import { ResetCompletePage } from '@/pages/reset-complete'
import { ResetPasswordPage } from '@/pages/reset-password'
import { ResetSentPage } from '@/pages/reset-sent'
import { AuthError } from '@/shared/api'
import { routes } from '@/shared/config'
import { t, useLocale } from '@/shared/i18n'
import { SessionCheck } from '@/shared/ui'
import { AppShell } from '@/widgets/app-shell'
function AuthLayout() {
const { pathname } = useLocation()
const locale = useLocale()
useEffect(() => {
document.documentElement.lang = locale
document.querySelector('meta[name="description"]')?.setAttribute('content', t('app.description'))
}, [locale])
// biome-ignore lint/correctness/useExhaustiveDependencies: locale 变化时需要按当前标题重算 document.title
useEffect(() => {
const heading = document.querySelector('h1')
document.title = `${heading?.textContent || t('app.title')} · GitDataAI`
heading?.focus({ preventScroll: true })
if (pathname !== routes.loginVerify) mfaChallenge.reset()
if (pathname !== routes.register && pathname !== routes.registerVerify) registrationDraft.reset()
if (pathname === routes.resetPassword && new URLSearchParams(location.search).has('token')) {
const url = new URL(location.href)
url.searchParams.delete('token')
history.replaceState(null, '', `${url.pathname}${url.search}${url.hash}`)
}
}, [pathname, locale])
return (
<AppShell>
<Outlet />
</AppShell>
)
}
function LoginEntry({ children }: { children: ReactNode }) {
const [unauthenticated, setUnauthenticated] = useState(false)
const navigate = useNavigate()
const { user, loading, task } = useSession({ onUnauthenticated: () => setUnauthenticated(true) })
useEffect(() => {
if (user) navigate(routes.account, { replace: true })
}, [user, navigate])
if (unauthenticated) return <>{children}</>
return <SessionCheck loading={loading} notice={task.notice} onRetry={() => location.reload()} />
}
function useLoginComplete(): LoginComplete {
const navigate = useNavigate()
return (response) => {
if (response.mfa_required) {
if (!response.challenge) throw new AuthError('challenge', t('mfa.missingChallenge'))
mfaChallenge.set(response.challenge)
navigate(routes.loginVerify)
return
}
mfaChallenge.reset()
navigate(routes.account, { replace: true })
}
}
function LoginRoute() {
const onComplete = useLoginComplete()
return (
<LoginEntry>
<LoginPage onComplete={onComplete} />
</LoginEntry>
)
}
function LoginCodeRoute() {
const onComplete = useLoginComplete()
return (
<LoginEntry>
<LoginCodePage onComplete={onComplete} />
</LoginEntry>
)
}
function LoginVerifyRoute() {
const onComplete = useLoginComplete()
return <LoginVerifyPage onComplete={onComplete} />
}
function RegisterRoute() {
const navigate = useNavigate()
return <RegisterPage onSent={() => navigate(routes.registerVerify)} />
}
function RegisterVerifyRoute() {
const navigate = useNavigate()
return <RegisterVerifyPage onComplete={() => navigate(routes.account, { replace: true })} />
}
function ForgotPasswordRoute() {
const navigate = useNavigate()
return <ForgotPasswordPage onSent={() => navigate(routes.resetSent)} />
}
function ResetPasswordRoute() {
const navigate = useNavigate()
return <ResetPasswordPage onComplete={() => navigate(routes.resetComplete, { replace: true })} />
}
export const router = createBrowserRouter([
{
path: routes.home,
element: <AuthLayout />,
children: [
{ index: true, element: <LoginRoute /> },
{ path: routes.login, element: <LoginRoute /> },
{ path: routes.loginCode, element: <LoginCodeRoute /> },
{ path: routes.loginVerify, element: <LoginVerifyRoute /> },
{ path: routes.register, element: <RegisterRoute /> },
{ path: routes.registerVerify, element: <RegisterVerifyRoute /> },
{ path: routes.forgotPassword, element: <ForgotPasswordRoute /> },
{ path: routes.resetSent, element: <ResetSentPage /> },
{ path: routes.resetPassword, element: <ResetPasswordRoute /> },
{ path: routes.resetComplete, element: <ResetCompletePage /> },
{ path: routes.account, element: <AccountPage /> },
],
},
{
path: '*',
element: <AuthLayout />,
children: [{ index: true, element: <NotFoundPage /> }],
},
])