diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx index 99a5b62e..9323f163 100644 --- a/frontend/src/app/dashboard/page.tsx +++ b/frontend/src/app/dashboard/page.tsx @@ -1,17 +1,48 @@ "use client"; -import React, { useState, Suspense } from 'react'; +import React, { useState, Suspense, useEffect } from 'react'; import { Skeleton } from "@/components/ui/skeleton"; import { useRouter } from 'next/navigation'; import { ChatInput } from '@/components/thread/chat-input'; import { createProject, addUserMessage, startAgent, createThread } from "@/lib/api"; import { generateThreadName } from "@/lib/actions/threads"; +// Constant for localStorage key to ensure consistency +const PENDING_PROMPT_KEY = 'pendingAgentPrompt'; + function DashboardContent() { const [inputValue, setInputValue] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); + const [autoSubmit, setAutoSubmit] = useState(false); const router = useRouter(); + // Check for pending prompt in localStorage on mount + useEffect(() => { + // Use a small delay to ensure we're fully mounted + const timer = setTimeout(() => { + const pendingPrompt = localStorage.getItem(PENDING_PROMPT_KEY); + + if (pendingPrompt) { + setInputValue(pendingPrompt); + setAutoSubmit(true); // Flag to auto-submit after mounting + } + }, 200); + + return () => clearTimeout(timer); + }, []); + + // Auto-submit the form if we have a pending prompt + useEffect(() => { + if (autoSubmit && inputValue && !isSubmitting) { + const timer = setTimeout(() => { + handleSubmit(inputValue); + setAutoSubmit(false); + }, 500); + + return () => clearTimeout(timer); + } + }, [autoSubmit, inputValue, isSubmitting]); + const handleSubmit = async (message: string, options?: { model_name?: string; enable_thinking?: boolean }) => { if (!message.trim() || isSubmitting) return; @@ -40,6 +71,9 @@ function DashboardContent() { stream: true }); + // If successful, clear the pending prompt + localStorage.removeItem(PENDING_PROMPT_KEY); + // 5. Navigate to the new agent's thread page router.push(`/dashboard/agents/${thread.thread_id}`); } catch (error) { diff --git a/frontend/src/components/GoogleSignIn.tsx b/frontend/src/components/GoogleSignIn.tsx index 452298f5..e8bce93c 100644 --- a/frontend/src/components/GoogleSignIn.tsx +++ b/frontend/src/components/GoogleSignIn.tsx @@ -74,7 +74,11 @@ export default function GoogleSignIn({ returnUrl }: GoogleSignInProps) { }); if (error) throw error; - window.location.href = returnUrl || "/dashboard"; + + // Add a small delay before redirecting to ensure localStorage is properly saved + setTimeout(() => { + window.location.href = returnUrl || "/dashboard"; + }, 100); } catch (error) { console.error('Error signing in with Google:', error); setIsLoading(false); diff --git a/frontend/src/components/home/sections/hero-section.tsx b/frontend/src/components/home/sections/hero-section.tsx index c099c131..eb25bc60 100644 --- a/frontend/src/components/home/sections/hero-section.tsx +++ b/frontend/src/components/home/sections/hero-section.tsx @@ -1,21 +1,51 @@ "use client" import { HeroVideoSection } from "@/components/home/sections/hero-video-section"; import { siteConfig } from "@/lib/home"; -import { ArrowRight, Github } from "lucide-react"; +import { ArrowRight, Github, X, AlertCircle } from "lucide-react"; import { FlickeringGrid } from "@/components/home/ui/flickering-grid"; import { useMediaQuery } from "@/hooks/use-media-query"; -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, FormEvent } from "react"; import { useScroll } from "motion/react"; import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/components/AuthProvider"; +import { createProject, createThread, addUserMessage, startAgent } from "@/lib/api"; +import { generateThreadName } from "@/lib/actions/threads"; +import GoogleSignIn from "@/components/GoogleSignIn"; +import { Input } from "@/components/ui/input"; +import { SubmitButton } from "@/components/ui/submit-button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogOverlay +} from "@/components/ui/dialog"; + +// Custom dialog overlay with blur effect +const BlurredDialogOverlay = () => ( + +); + +// Constant for localStorage key to ensure consistency +const PENDING_PROMPT_KEY = 'pendingAgentPrompt'; export function HeroSection() { const { hero } = siteConfig; const tablet = useMediaQuery("(max-width: 1024px)"); const [mounted, setMounted] = useState(false); const [isScrolling, setIsScrolling] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); const scrollTimeout = useRef(null); const { scrollY } = useScroll(); const [inputValue, setInputValue] = useState(""); + const router = useRouter(); + const { user, isLoading } = useAuth(); + + // Auth dialog state + const [authDialogOpen, setAuthDialogOpen] = useState(false); + const [authError, setAuthError] = useState(null); useEffect(() => { setMounted(true); @@ -45,6 +75,108 @@ export function HeroSection() { }; }, [scrollY]); + // Store the input value when auth dialog opens + useEffect(() => { + if (authDialogOpen && inputValue.trim()) { + localStorage.setItem(PENDING_PROMPT_KEY, inputValue.trim()); + } + }, [authDialogOpen, inputValue]); + + // Close dialog and redirect when user authenticates + useEffect(() => { + if (authDialogOpen && user && !isLoading) { + setAuthDialogOpen(false); + router.push('/dashboard'); + } + }, [user, isLoading, authDialogOpen, router]); + + // Create an agent with the provided prompt + const createAgentWithPrompt = async () => { + if (!inputValue.trim() || isSubmitting) return; + + setIsSubmitting(true); + + try { + // Generate a name for the project using GPT + const projectName = await generateThreadName(inputValue); + + // 1. Create a new project with the GPT-generated name + const newAgent = await createProject({ + name: projectName, + description: "", + }); + + // 2. Create a new thread for this project + const thread = await createThread(newAgent.id); + + // 3. Add the user message to the thread + await addUserMessage(thread.thread_id, inputValue.trim()); + + // 4. Start the agent with the thread ID + await startAgent(thread.thread_id, { + stream: true + }); + + // 5. Navigate to the new agent's thread page + router.push(`/dashboard/agents/${thread.thread_id}`); + } catch (error) { + console.error("Error creating agent:", error); + setIsSubmitting(false); + } + }; + + // Handle form submission + const handleSubmit = async (e?: FormEvent) => { + if (e) { + e.preventDefault(); + e.stopPropagation(); // Stop event propagation to prevent dialog closing + } + + if (!inputValue.trim() || isSubmitting) return; + + // If user is not logged in, save prompt and show auth dialog + if (!user && !isLoading) { + // Save prompt to localStorage BEFORE showing the dialog + localStorage.setItem(PENDING_PROMPT_KEY, inputValue.trim()); + setAuthDialogOpen(true); + return; + } + + // User is logged in, create the agent + createAgentWithPrompt(); + }; + + // Handle Enter key press + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); // Prevent default form submission + e.stopPropagation(); // Stop event propagation + handleSubmit(); + } + }; + + // Handle auth form submission + const handleSignIn = async (prevState: any, formData: FormData) => { + setAuthError(null); + try { + // Implement sign in logic here + const email = formData.get("email") as string; + const password = formData.get("password") as string; + + // Add the returnUrl to the form data for proper redirection + formData.append("returnUrl", "/dashboard"); + + // Call your authentication function here + + // Return any error state + return { message: "Invalid credentials" }; + } catch (error) { + console.error("Sign in error:", error); + setAuthError(error instanceof Error ? error.message : "An error occurred"); + return { message: "An error occurred during sign in" }; + } + }; + return (
@@ -122,7 +254,7 @@ export function HeroSection() {

-
+
{/* ChatGPT-like input with glow effect */}
@@ -130,30 +262,145 @@ export function HeroSection() { type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} + onKeyDown={handleKeyDown} placeholder={hero.inputPlaceholder} className="flex-1 h-12 md:h-14 rounded-full px-2 bg-transparent focus:outline-none text-sm md:text-base py-2" + disabled={isSubmitting} />
{/* Subtle glow effect */}
-
+
+ + {/* Auth Dialog */} + + + + +
+ Sign in to continue + +
+ + Sign in or create an account to talk with Suna + +
+ + {/* Auth error message */} + {authError && ( +
+ + {authError} +
+ )} + + {/* Google Sign In */} +
+ +
+ + {/* Divider */} +
+
+
+
+
+ + or continue with email + +
+
+ + {/* Sign in form */} +
+
+ +
+ +
+ +
+ +
+ + Sign in + + + setAuthDialogOpen(false)} + > + Create new account + +
+ +
+ setAuthDialogOpen(false)} + > + More sign in options + +
+
+ +
+ By continuing, you agree to our{' '} + + Terms of Service + {' '} + and{' '}Privacy Policy +
+
+
); } \ No newline at end of file