feat(web): render auth pages without app shell
This commit is contained in:
+47
-44
@@ -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={
|
||||||
|
|||||||
Reference in New Issue
Block a user