feat(web): render auth pages without app shell

This commit is contained in:
2026-04-05 17:09:17 +08:00
parent e8dd85ee65
commit aff645bc5d
+47 -44
View File
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import { import {
Bell, Bell,
@@ -14,7 +14,7 @@ import {
Sun, Sun,
X X
} from "lucide-react"; } from "lucide-react";
import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; import { Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { EmailLoginPage } from "@/pages/email-login-page"; import { EmailLoginPage } from "@/pages/email-login-page";
@@ -66,6 +66,10 @@ function App() {
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const isAuthPage =
location.pathname === "/login/email" || location.pathname.startsWith("/auth/callback/");
useEffect(() => { useEffect(() => {
applyThemeMode(themeMode); applyThemeMode(themeMode);
@@ -95,6 +99,19 @@ function App() {
setThemeMode((currentTheme) => (currentTheme === "dark" ? "light" : "dark")); setThemeMode((currentTheme) => (currentTheme === "dark" ? "light" : "dark"));
} }
function handleLoginSuccess(payload: EmailLoginResult): void {
const nextSession = toWebSession(payload);
saveSession(nextSession);
setSession(nextSession);
setMobileSidebarOpen(false);
navigate("/", { replace: true });
}
function handleBootstrapSession(nextSession: WebSession): void {
setSession(nextSession);
setMobileSidebarOpen(false);
}
function renderSidebarContent(options: { collapsed: boolean; mobile: boolean }) { function renderSidebarContent(options: { collapsed: boolean; mobile: boolean }) {
const { collapsed, mobile } = options; const { collapsed, mobile } = options;
@@ -124,9 +141,8 @@ function App() {
key={item.key} key={item.key}
type="button" type="button"
className={cn( className={cn(
"group flex w-full items-center rounded-xl border border-transparent text-left transition-colors", "group flex w-full items-center rounded-xl border border-transparent px-3 py-2.5 text-left transition-colors",
"hover:border-primary/25 hover:bg-primary/10", "gap-3 hover:border-primary/25 hover:bg-primary/10"
"gap-3 px-3 py-2.5"
)} )}
> >
<ItemIcon className="size-5 shrink-0 text-primary" /> <ItemIcon className="size-5 shrink-0 text-primary" />
@@ -150,10 +166,7 @@ function App() {
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
className={cn( className="w-full justify-start gap-2 border-primary/25 px-3 text-primary hover:bg-primary/10"
"w-full border-primary/25 text-primary hover:bg-primary/10",
"justify-start gap-2 px-3"
)}
onClick={handleToggleTheme} onClick={handleToggleTheme}
> >
{themeMode === "dark" ? <Sun className="size-4" /> : <Moon className="size-4" />} {themeMode === "dark" ? <Sun className="size-4" /> : <Moon className="size-4" />}
@@ -167,10 +180,7 @@ function App() {
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
className={cn( className="w-full justify-start gap-2 border-primary/25 px-3 text-primary hover:bg-primary/10"
"w-full border-primary/25 text-primary hover:bg-primary/10",
"justify-start gap-2 px-3"
)}
onClick={handleLogout} onClick={handleLogout}
disabled={!session || loggingOut} disabled={!session || loggingOut}
> >
@@ -184,6 +194,28 @@ function App() {
); );
} }
if (isAuthPage) {
return (
<div className="min-h-dvh bg-background text-foreground md:min-h-screen">
<main className="flex min-h-dvh items-center justify-center px-4 py-8 md:min-h-screen md:px-6">
<div className="w-full max-w-md">
<Routes>
<Route
path="/login/email"
element={<EmailLoginPage onLoginSuccess={handleLoginSuccess} />}
/>
<Route
path="/auth/callback/:provider"
element={<OAuthCallbackPage onBootstrapSession={handleBootstrapSession} />}
/>
<Route path="*" element={<Navigate to={session ? "/" : "/login/email"} replace />} />
</Routes>
</div>
</main>
</div>
);
}
return ( return (
<div className="h-dvh overflow-hidden bg-background text-foreground md:h-screen"> <div className="h-dvh overflow-hidden bg-background text-foreground md:h-screen">
<header className="relative z-50 shrink-0 border-b border-border/70 bg-background/80 backdrop-blur-xl"> <header className="relative z-50 shrink-0 border-b border-border/70 bg-background/80 backdrop-blur-xl">
@@ -202,11 +234,7 @@ function App() {
alt="TodoList" alt="TodoList"
className="h-9 w-9 shrink-0 rounded-xl shadow-sm" className="h-9 w-9 shrink-0 rounded-xl shadow-sm"
/> />
<div className="flex flex-col"> <span className="text-base font-semibold tracking-tight text-foreground">TodoList</span>
<span className="text-base font-semibold tracking-tight text-foreground">
TodoList
</span>
</div>
</div> </div>
<span className="hidden max-w-[280px] truncate text-sm text-muted-foreground md:block"> <span className="hidden max-w-[280px] truncate text-sm text-muted-foreground md:block">
{session ? session.user.email : "未登录"} {session ? session.user.email : "未登录"}
@@ -232,7 +260,7 @@ function App() {
{renderSidebarContent({ collapsed: false, mobile: true })} {renderSidebarContent({ collapsed: false, mobile: true })}
</aside> </aside>
<div className="flex min-h-0 h-[calc(100dvh-4rem)] md:h-[calc(100vh-4rem)]"> <div className="flex h-[calc(100dvh-4rem)] min-h-0 md:h-[calc(100vh-4rem)]">
<aside <aside
className={cn( className={cn(
"relative hidden h-full border-r border-border/80 bg-card/88 backdrop-blur-xl transition-[width] duration-300 md:flex md:flex-col", "relative hidden h-full border-r border-border/80 bg-card/88 backdrop-blur-xl transition-[width] duration-300 md:flex md:flex-col",
@@ -264,31 +292,6 @@ function App() {
<main className="min-h-0 flex-1 overflow-y-auto px-4 py-6 md:px-6 md:py-8"> <main className="min-h-0 flex-1 overflow-y-auto px-4 py-6 md:px-6 md:py-8">
<div className="mx-auto w-full max-w-6xl"> <div className="mx-auto w-full max-w-6xl">
<Routes> <Routes>
<Route
path="/login/email"
element={
<EmailLoginPage
onLoginSuccess={(payload) => {
const nextSession = toWebSession(payload);
saveSession(nextSession);
setSession(nextSession);
setMobileSidebarOpen(false);
navigate("/");
}}
/>
}
/>
<Route
path="/auth/callback/:provider"
element={
<OAuthCallbackPage
onBootstrapSession={(nextSession) => {
setSession(nextSession);
setMobileSidebarOpen(false);
}}
/>
}
/>
<Route <Route
path="/" path="/"
element={ element={