diff --git a/package.json b/package.json index 25dd07f7..2d4711c3 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "streamdown": "^2.0.1", "supermemory": "^3.14.0", "tailwind-merge": "^3.4.0", + "typewriter-effect": "^2.22.0", "zod": "^4.3.4", "zustand": "^5.0.9" }, diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 6a1e80f3..78112baf 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -27,7 +27,7 @@ import { DashboardLayout } from "@/components/layout/DashboardLayout"; import { ItemPanelContent } from "@/components/workspace-canvas/ItemPanelContent"; import { SidebarProvider, useSidebar } from "@/components/ui/sidebar"; import { MobileWarning } from "@/components/ui/MobileWarning"; -import { AuthPageBackground } from "@/components/auth/AuthPageBackground"; +import { AnonymousSessionHandler, SidebarCoordinator } from "@/components/layout/SessionHandler"; // import { OnboardingVideoDialog } from "@/components/onboarding/OnboardingVideoDialog"; // import { useOnboardingStatus } from "@/hooks/user/use-onboarding-status"; import { PdfEngineWrapper } from "@/components/pdf/PdfEngineWrapper"; @@ -429,59 +429,6 @@ export function DashboardPage() { ); } -// Wrapper for sidebar provider -export function SidebarCoordinator({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} - -// Component to handle anonymous users - redirect to guest-setup if no session -export function AnonymousSessionHandler({ children }: { children: React.ReactNode }) { - const { data: session, isPending } = useSession(); - const router = useRouter(); - - useEffect(() => { - // If no session and not loading, redirect to guest-setup - if (!isPending && !session) { - router.replace("/guest-setup"); - return; - } - }, [session, isPending, router]); - - // Show loading while checking session - if (isPending) { - return ( -
- {/* Background with grid and cards - same as auth page */} - - - {/* Content */} -
-
- -
-

- Setting up your workspace... -

-

- This will only take a moment -

-
-
- ); - } - - // Don't render dashboard if no session (will redirect) - if (!session) { - return null; - } - - return <>{children}; -} - export function DashboardShell() { return ( <> diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx index 4d59a74f..df10d8a5 100644 --- a/src/app/home/page.tsx +++ b/src/app/home/page.tsx @@ -1,5 +1,45 @@ +"use client"; + import { SEO } from "@/components/seo/SEO"; -import { DashboardShell } from "../dashboard/page"; +import { MobileWarning } from "@/components/ui/MobileWarning"; +import { WorkspaceProvider, useWorkspaceContext } from "@/contexts/WorkspaceContext"; +import { HomeLayout } from "@/components/layout/HomeLayout"; +import { HomeContent } from "@/components/home/HomeContent"; +import { AnonymousSessionHandler, SidebarCoordinator } from "@/components/layout/SessionHandler"; +import { useUIStore } from "@/lib/stores/ui-store"; + +// Home page content component +function HomePageContent() { + const { switchWorkspace } = useWorkspaceContext(); + const showCreateModal = useUIStore((state) => state.showCreateWorkspaceModal); + const setShowCreateModal = useUIStore((state) => state.setShowCreateWorkspaceModal); + + return ( + + + + ); +} + +// Main shell component for home page +export function HomeShell() { + return ( + <> + + + + + + + + + + ); +} export default function HomePage() { return ( @@ -11,7 +51,7 @@ export default function HomePage() { url="https://thinkex.app/home" canonical="https://thinkex.app/home" /> - + ); } diff --git a/src/components/home/FeaturedWorkspaces.tsx b/src/components/home/FeaturedWorkspaces.tsx new file mode 100644 index 00000000..88faea43 --- /dev/null +++ b/src/components/home/FeaturedWorkspaces.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { toast } from "sonner"; + +// Featured workspace templates with current topics +const FEATURED_WORKSPACES = [ + { + id: "featured-1", + name: "AI & Machine Learning", + description: "Explore GPT, LLMs, and AI trends", + color: "hsl(217, 91%, 60%)", + }, + { + id: "featured-2", + name: "Climate Science", + description: "Study climate change and solutions", + color: "hsl(142, 76%, 36%)", + }, + { + id: "featured-3", + name: "Web3 & Blockchain", + description: "Learn crypto, DeFi, and NFTs", + color: "hsl(280, 100%, 70%)", + }, + { + id: "featured-4", + name: "Quantum Computing", + description: "Understand quantum mechanics", + color: "hsl(24, 95%, 53%)", + }, + { + id: "featured-5", + name: "Space Exploration", + description: "Mars missions and astronomy", + color: "hsl(200, 100%, 50%)", + }, + { + id: "featured-6", + name: "Neuroscience", + description: "Brain science and cognition", + color: "hsl(320, 70%, 60%)", + }, + { + id: "featured-7", + name: "Sustainable Energy", + description: "Solar, wind, and green tech", + color: "hsl(120, 60%, 45%)", + }, + { + id: "featured-8", + name: "Biotechnology", + description: "CRISPR, gene therapy, and more", + color: "hsl(15, 85%, 55%)", + }, +]; + +export function FeaturedWorkspaces() { + const scrollRef = useRef(null); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(true); + + const checkScrollability = () => { + if (!scrollRef.current) return; + const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current; + setCanScrollLeft(scrollLeft > 0); + setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 10); + }; + + useEffect(() => { + checkScrollability(); + // Check on window resize + window.addEventListener("resize", checkScrollability); + return () => window.removeEventListener("resize", checkScrollability); + }, []); + + const scroll = (direction: "left" | "right") => { + if (!scrollRef.current) return; + const scrollAmount = scrollRef.current.clientWidth * 0.8; + scrollRef.current.scrollBy({ + left: direction === "left" ? -scrollAmount : scrollAmount, + behavior: "smooth", + }); + }; + + const handleWorkspaceClick = useCallback((workspaceName: string) => { + toast.info("Coming soon", { + description: `Template workspace "${workspaceName}" will be available soon!`, + }); + }, []); + + return ( +
+
+

Featured workspaces

+
+ + +
+
+ +
+ {FEATURED_WORKSPACES.map((workspace) => ( +
handleWorkspaceClick(workspace.name)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleWorkspaceClick(workspace.name); + } + }} + className={cn( + "flex-shrink-0 w-64 px-6 py-8 rounded-md border border-sidebar-border/50 backdrop-blur-sm", + "hover:border-primary/40 hover:shadow-lg hover:shadow-primary/5", + "transition-all duration-300 cursor-pointer", + "flex flex-col items-start justify-between", + "aspect-[3.5/1]" + )} + style={{ + backgroundColor: `${workspace.color}08`, + }} + > +
+
+
+
+
+

+ {workspace.name} +

+

{workspace.description}

+
+
+
+ ))} +
+
+ ); +} diff --git a/src/components/home/HomeContent.tsx b/src/components/home/HomeContent.tsx new file mode 100644 index 00000000..3f2c298f --- /dev/null +++ b/src/components/home/HomeContent.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { HomePromptInput } from "./HomePromptInput"; +import { FeaturedWorkspaces } from "./FeaturedWorkspaces"; +import { WorkspaceGrid } from "./WorkspaceGrid"; + +export function HomeContent() { + return ( +
+ {/* Main content area */} +
+ {/* Hero Section */} +
+

+ What's on your mind? +

+
+ +
+
+ + {/* Content Sections */} +
+ {/* Featured Workspaces */} + + + {/* Your Workspaces */} + +
+
+
+ ); +} diff --git a/src/components/home/HomePromptInput.tsx b/src/components/home/HomePromptInput.tsx new file mode 100644 index 00000000..75a8a660 --- /dev/null +++ b/src/components/home/HomePromptInput.tsx @@ -0,0 +1,228 @@ +"use client"; + +import { useState, useRef, useEffect } from "react"; +import { toast } from "sonner"; +import { ArrowUp, Paperclip } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import Typewriter, { TypewriterClass } from "typewriter-effect"; + +const PLACEHOLDER_OPTIONS = [ + "climate change impacts", + "quantum computing applications", + "neural network architectures", + "CRISPR gene editing", + "renewable energy systems", + "machine learning ethics", + "space exploration missions", + "biotechnology advances", + "sustainable agriculture", + "artificial intelligence safety", + "blockchain technology", + "cancer immunotherapy", + "ocean acidification", + "renewable energy storage", + "autonomous vehicles", + "precision medicine", + "carbon capture technology", + "synthetic biology", + "quantum cryptography", + "fusion energy research", +]; + +const baseText = "Create a workspace for "; + +export function HomePromptInput() { + const [value, setValue] = useState(""); + const [isFocused, setIsFocused] = useState(false); + const fileInputRef = useRef(null); + const typewriterRef = useRef(null); + + // Cleanup typewriter on unmount to prevent memory leaks + useEffect(() => { + return () => { + if (typewriterRef.current) { + typewriterRef.current.stop(); + } + }; + }, []); + + // Handle user typing - stop animation + const handleInput = (e: React.ChangeEvent) => { + const newValue = e.target.value; + setValue(newValue); + }; + + const handleFocus = () => { + setIsFocused(true); + if (typewriterRef.current) { + typewriterRef.current.stop(); + typewriterRef.current.deleteAll(1); + } + }; + + const handleBlur = () => { + setIsFocused(false); + if (value.length === 0 && typewriterRef.current) { + typewriterRef.current.start(); + } + }; + + const handleFileUpload = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + toast.info("Coming soon", { + description: "File upload for workspace creation will be available soon!", + }); + + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!value.trim()) return; + + toast.info("Coming soon", { + description: "Workspace creation from prompts will be available soon!", + }); + + setValue(""); + if (typewriterRef.current) { + typewriterRef.current.start(); + } + }; + + return ( +
+
+ {/* Hidden file input */} + + + {/* Textarea */} +
+ {/* File upload button - inside on the left */} + + +