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 (
+
+ );
+}
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() {
) : (
<>
-
- Start free trial
+ setLoginModalOpen(true)}>
+ Start free trial
See pricing
@@ -381,14 +381,14 @@ export default function Landing() {
setLoginModalOpen(true)}
>
- {tier.cta}
+ {tier.cta}
))}
@@ -472,8 +472,8 @@ export default function Landing() {