diff --git a/frontend/app/analyze/[id]/page.tsx b/frontend/app/analyze/[id]/page.tsx index 0d05625..54d59c1 100644 --- a/frontend/app/analyze/[id]/page.tsx +++ b/frontend/app/analyze/[id]/page.tsx @@ -22,7 +22,8 @@ import { import { Button } from "@/components/ui/button"; import { ChatBox } from "@/components/ChatBox"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { getAccessToken } from "@auth0/nextjs-auth0/client"; +import { getAccessToken, useUser } from "@auth0/nextjs-auth0/client"; +import { LoginRequiredDialog } from "@/components/login-required-dialog"; interface AnalysisResults { title: string; @@ -63,6 +64,8 @@ export default function AnalysisResultsPage({ const [combinedMessages, setCombinedMessages] = useState([]); const [biomodelData, setBiomodelData] = useState(null); const [biomodelLoading, setBiomodelLoading] = useState(true); + const [showLoginDialog, setShowLoginDialog] = useState(false); + const { user, isLoading: isUserLoading } = useUser(); useEffect(() => { const fetchBiomodelData = async () => { @@ -88,6 +91,13 @@ export default function AnalysisResultsPage({ }, [id]); useEffect(() => { + if (isUserLoading) return; + if (!user) { + setShowLoginDialog(true); + setIsAnalysisLoading(false); + return; + } + const fetchDiagramAnalysis = async () => { setIsAnalysisLoading(true); setAnalysisError(""); @@ -154,7 +164,7 @@ export default function AnalysisResultsPage({ }; fetchBothAnalyses(); - }, [id, prompt]); + }, [id, prompt, isUserLoading, user]); // Create combined messages when analyses are ready useEffect(() => { @@ -247,6 +257,7 @@ export default function AnalysisResultsPage({ return (
+
diff --git a/frontend/app/analyze/page.tsx b/frontend/app/analyze/page.tsx index 023657a..65c26f4 100644 --- a/frontend/app/analyze/page.tsx +++ b/frontend/app/analyze/page.tsx @@ -18,6 +18,8 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useUser } from "@auth0/nextjs-auth0/client"; +import { LoginRequiredDialog } from "@/components/login-required-dialog"; interface PromptTemplate { title: string; @@ -30,6 +32,8 @@ export default function AnalyzePage() { const router = useRouter(); const [biomodelId, setBiomodelId] = useState(""); const [prompt, setPrompt] = useState(""); + const [showLoginDialog, setShowLoginDialog] = useState(false); + const { user, isLoading: isUserLoading } = useUser(); const promptTemplates: PromptTemplate[] = [ { @@ -76,11 +80,17 @@ export default function AnalyzePage() { const handleAnalyze = () => { if (!biomodelId.trim() || !prompt.trim()) return; + if (isUserLoading) return; + if (!user) { + setShowLoginDialog(true); + return; + } router.push(`/analyze/${biomodelId}?prompt=${encodeURIComponent(prompt)}`); }; return (
+
{/* Header */}
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 37e98b8..7e5d416 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -15,6 +15,7 @@ import Image from "next/image"; const loginHref = "/auth/login?returnTo=/chat"; const signupHref = "/auth/login?returnTo=/chat&screen_hint=signup"; +const exploreHref = "/search"; export default function LandingPage() { return ( @@ -78,7 +79,7 @@ export default function LandingPage() {

- + - +
diff --git a/frontend/components/login-required-dialog.tsx b/frontend/components/login-required-dialog.tsx new file mode 100644 index 0000000..c23845c --- /dev/null +++ b/frontend/components/login-required-dialog.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { usePathname, useSearchParams } from "next/navigation"; +import Link from "next/link"; +import { LogIn } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/components/ui/dialog"; + +interface LoginRequiredDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function LoginRequiredDialog({ + open, + onOpenChange, +}: LoginRequiredDialogProps) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const query = searchParams.toString(); + const returnTo = query ? `${pathname}?${query}` : pathname; + const loginHref = `/auth/login?${new URLSearchParams({ returnTo }).toString()}`; + + return ( + + + + Log in to continue + + Log in to chat with the AI assistant or run an AI analysis. + You'll be brought right back here. + + + + + + + + + ); +} diff --git a/frontend/middleware.ts b/frontend/middleware.ts index fbeb1f1..de14757 100644 --- a/frontend/middleware.ts +++ b/frontend/middleware.ts @@ -2,16 +2,26 @@ import { NextResponse, type NextRequest } from 'next/server'; import { auth0 } from './lib/auth0'; const publicRoutes = new Set(['/']); +const publicRoutePrefixes = ['/search', '/chat', '/analyze']; const publicFilePattern = /\.(.*)$/; +function isPublicRoute(pathname: string): boolean { + return ( + publicRoutes.has(pathname) || + publicRoutePrefixes.some( + (prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`), + ) + ); +} + export async function middleware(request: NextRequest) { const { pathname, search } = request.nextUrl; const authResponse = await auth0.middleware(request); if ( - publicRoutes.has(pathname) || + isPublicRoute(pathname) || pathname.startsWith('/auth/') || publicFilePattern.test(pathname) ) {