From 26d2c904b4072060527b068b35060cbb36cc2975 Mon Sep 17 00:00:00 2001 From: Oliver Bryan Date: Wed, 28 Jan 2026 10:22:36 +0000 Subject: [PATCH] replaced login page with modal --- .../frontend/src/components/login-form.tsx | 17 +++--- .../frontend/src/components/login-modal.tsx | 55 +++++++++++++++++++ .../src/components/session-provider.tsx | 16 ++++-- packages/frontend/src/main.tsx | 2 - packages/frontend/src/pages/Landing.tsx | 22 ++++---- packages/frontend/src/pages/Login.tsx | 32 ----------- 6 files changed, 89 insertions(+), 55 deletions(-) create mode 100644 packages/frontend/src/components/login-modal.tsx delete mode 100644 packages/frontend/src/pages/Login.tsx diff --git a/packages/frontend/src/components/login-form.tsx b/packages/frontend/src/components/login-form.tsx index b1d4d21..9093341 100644 --- a/packages/frontend/src/components/login-form.tsx +++ b/packages/frontend/src/components/login-form.tsx @@ -19,15 +19,18 @@ const DEMO_USERS = [ { name: "User 2", username: "u2", password: "a" }, ]; -export default function LogInForm() { +export default function LogInForm({ + showWarning, + setShowWarning, +}: { + showWarning: boolean; + setShowWarning: (value: boolean) => void; +}) { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { setUser } = useSession(); const [loginDetailsOpen, setLoginDetailsOpen] = useState(false); - const [showWarning, setShowWarning] = useState(() => { - return localStorage.getItem("hide-under-construction") !== "true"; - }); const [mode, setMode] = useState<"login" | "register">("login"); @@ -143,7 +146,7 @@ export default function LogInForm() { <> {/* under construction warning */} {showWarning && ( -
+
Login Details - + Demo Login Credentials
{DEMO_USERS.map((user) => ( @@ -208,7 +211,7 @@ export default function LogInForm() {
diff --git a/packages/frontend/src/components/login-modal.tsx b/packages/frontend/src/components/login-modal.tsx new file mode 100644 index 0000000..89b5177 --- /dev/null +++ b/packages/frontend/src/components/login-modal.tsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import LogInForm from "@/components/login-form"; +import { useSession } from "@/components/session-provider"; +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; +import { cn } from "@/lib/utils"; + +interface LoginModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess?: () => void; + dismissible?: boolean; +} + +export function LoginModal({ open, onOpenChange, onSuccess, dismissible = true }: LoginModalProps) { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { user, isLoading } = useSession(); + const [hasRedirected, setHasRedirected] = useState(false); + const [showWarning, setShowWarning] = useState(() => { + return localStorage.getItem("hide-under-construction") !== "true"; + }); + + useEffect(() => { + if (open && !isLoading && user && !hasRedirected) { + setHasRedirected(true); + const next = searchParams.get("next") || "/issues"; + navigate(next, { replace: true }); + onSuccess?.(); + onOpenChange(false); + } + }, [open, user, isLoading, navigate, searchParams, onSuccess, onOpenChange, hasRedirected]); + + useEffect(() => { + if (!open) { + setHasRedirected(false); + } + }, [open]); + + const handleOpenChange = (newOpen: boolean) => { + if (!dismissible && !newOpen) { + return; + } + onOpenChange(newOpen); + }; + + return ( + + + Log In or Register + + + + ); +} diff --git a/packages/frontend/src/components/session-provider.tsx b/packages/frontend/src/components/session-provider.tsx index 6321f74..4b0966a 100644 --- a/packages/frontend/src/components/session-provider.tsx +++ b/packages/frontend/src/components/session-provider.tsx @@ -1,7 +1,8 @@ import type { UserRecord } from "@sprint/shared"; import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react"; -import { Navigate, useLocation } from "react-router-dom"; + import Loading from "@/components/loading"; +import { LoginModal } from "@/components/login-modal"; import { clearAuth, getServerURL, setCsrfToken } from "@/lib/utils"; interface SessionContextValue { @@ -74,15 +75,22 @@ export function SessionProvider({ children }: { children: React.ReactNode }) { export function RequireAuth({ children }: { children: React.ReactNode }) { const { user, isLoading } = useSession(); - const location = useLocation(); + const [loginModalOpen, setLoginModalOpen] = useState(false); + + useEffect(() => { + if (!isLoading && !user) { + setLoginModalOpen(true); + } else if (user) { + setLoginModalOpen(false); + } + }, [user, isLoading]); if (isLoading) { return ; } if (!user) { - const next = encodeURIComponent(location.pathname + location.search); - return ; + return ; } return <>{children}; diff --git a/packages/frontend/src/main.tsx b/packages/frontend/src/main.tsx index 7f184f4..9e9b102 100644 --- a/packages/frontend/src/main.tsx +++ b/packages/frontend/src/main.tsx @@ -11,7 +11,6 @@ import { Toaster } from "@/components/ui/sonner"; import Font from "@/pages/Font"; import Issues from "@/pages/Issues"; import Landing from "@/pages/Landing"; -import Login from "@/pages/Login"; import NotFound from "@/pages/NotFound"; import Test from "@/pages/Test"; import Timeline from "@/pages/Timeline"; @@ -27,7 +26,6 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( {/* public routes */} } /> } /> - } /> {/* authed routes */} ("monthly"); + const [loginModalOpen, setLoginModalOpen] = useState(false); return (
@@ -129,8 +129,8 @@ export default function Landing() { Open app ) : ( - )}
@@ -162,8 +162,8 @@ export default function Landing() { ) : ( <> -
))} @@ -472,8 +472,8 @@ export default function Landing() { Open app ) : ( - )}
@@ -484,6 +484,8 @@ export default function Landing() {
+ +