Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
PORT=3000

# PostgreSQL Database URL (Neon DB or any PostgreSQL provider)
# Example: postgresql://user:password@host:port/database?sslmode=require
SHADOW_DB_URL=postgresql://username:password@your-host.region.provider.tech/database?sslmode=require
# Example: ******host:port/database?sslmode=require
SHADOW_DB_URL=******your-host.region.provider.tech/database?sslmode=require

# Hugging Face Access Token for AI features
# Get your token from: https://huggingface.co/settings/tokens
Expand Down
45 changes: 40 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';

// Components
import WorkStation from './components/WorkStation';
import { Home } from './components/Home';
import { NotFound } from './components/NotFound';
import { Login } from './components/auth/Login';
import { SignUp } from './components/auth/SignUp';
import { ForgotPassword } from './components/auth/ForgotPassword';
import { ProtectedRoute } from './components/auth/ProtectedRoute';
import { useAuthStore } from './store/authStore';
import { useEffect } from 'react';

function RootRedirect() {
const { isAuthenticated, isLoading } = useAuthStore();

if (isLoading) {
return null; // Or a loading spinner
}

return isAuthenticated ? <Navigate to="/workstation" replace /> : <Home />;
}

export function App() {
const initialize = useAuthStore((state) => state.initialize);

useEffect(() => {
initialize();
}, [initialize]);

return (
<BrowserRouter>
<Routes>
{/* --- Public Routes --- */}
<Route path='/' element={<Home />} />
<Route path='/' element={<RootRedirect />} />

{/* --- Auth Routes --- */}
<Route path='/login' element={<Login />} />
<Route path='/signup' element={<SignUp />} />
<Route path='/forgot-password' element={<ForgotPassword />} />

{/* Direct access to workstation - no authentication */}
<Route path='/workstation' element={<WorkStation />} />
<Route path='/workstation/:projectId' element={<WorkStation />} />
{/* --- Protected Routes --- */}
<Route path='/workstation' element={
<ProtectedRoute>
<WorkStation />
</ProtectedRoute>
} />
<Route path='/workstation/:projectId' element={
<ProtectedRoute>
<WorkStation />
</ProtectedRoute>
} />

{/* --- Catch-all / 404 --- */}
<Route path='*' element={<NotFound />} />
Expand Down
36 changes: 32 additions & 4 deletions src/components/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
import { useRef } from 'react';
import './css/home.css';
import GlassButton from '../components/ui/GlassButton.jsx';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import { useAuthStore } from '@/store/authStore';
import { Button } from './ui/button';
import { LogIn } from 'lucide-react';

export function Home() {
const navigate = useNavigate();
const { isAuthenticated } = useAuthStore();

const handleStartClick = () => {
if (isAuthenticated) {
navigate('/workstation');
} else {
navigate('/login');
}
};

return (
<div className="page-wrapper">
{/* Sign In Button for unauthenticated users */}
{!isAuthenticated && (
<div className="absolute top-6 right-6 z-20">
<Link to="/login">
<Button
variant="outline"
className="bg-white/5 hover:bg-white/10 border-white/20 text-white backdrop-blur-sm"
>
<LogIn className="mr-2 h-4 w-4" />
Sign in
</Button>
</Link>
</div>
)}

{/* LAYER 1: The Content (Text + Button) */}
<div className="content-layer">
<div className="hero-text-container">
<h1 className="hero-title">DB-builder</h1>
<p className="hero-subtitle">Build databases faster.</p>

<div className="button-wrapper">
<Link to='/workstation'>
<div onClick={handleStartClick} style={{ cursor: 'pointer' }}>
<GlassButton>Start now!</GlassButton>
</Link>
</div>
</div>
</div>
</div>
Expand Down
100 changes: 91 additions & 9 deletions src/components/WorkStation.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useParams, useNavigate } from "react-router-dom";
import "../index.css";
import { Toaster, toast } from "sonner";

Expand All @@ -16,7 +16,10 @@ import {
Share2,
Sparkles,
Wand2,
Loader2
Bot,
LogOut,
Settings,
FolderKanban
} from "lucide-react";

// Components
Expand All @@ -25,13 +28,24 @@ import MiniMap from "./Minimap";
import SQLDrawer from "./SQLDrawer";
import SnipOverlay from "./SnipOverlay";
import GenerateModal from "./GenerateModel";
import { NotFound } from "./NotFound"; // Import your 404 component
import AssistantPanel from "./assistant/AssistantPanel";
import AssistantButton from "./assistant/AssistantButton";
import ShadowWorkspace from "./ShadowWorkspace";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from "./ui/dropdown-menu";
import { Avatar, AvatarFallback } from "./ui/avatar";

// Store & Libs
import { useDBStore } from "../store/dbStore";
import { useAssistantStore } from "../store/assistantStore";
import { useAIChatStore } from "../store/aiChatStore";
import { useAuthStore } from "../store/authStore";
import { saveProject as saveLocal, importProject } from "../lib/projectIO";
import { getLayoutedElements } from '../utils/layout';
import { ProjectCompiler } from "../lib/compiler";
Expand All @@ -40,6 +54,7 @@ import { useProjectSave } from "@/hooks/useProjectSave";

function WorkStation() {
const { projectId } = useParams(); // Get Project ID from URL
const navigate = useNavigate();

// --- STORE STATE ---
const addTable = useDBStore((s) => s.addTable);
Expand All @@ -62,17 +77,39 @@ function WorkStation() {
// --- AI CHAT STATE ---
const { isChatOpen, isSplitView, toggleChat } = useAIChatStore();

// --- LOCAL STATE ---
// --- AUTH STATE ---
const { user, signOut } = useAuthStore();

// --- SIMPLIFIED STATE (No cloud/auth) ---
const [projectName] = useState("Untitled Project");
const [snipOpen, setSnipOpen] = useState(false);
const [generateOpen, setGenerateOpen] = useState(false);
const mainRef = useRef<HTMLDivElement | null>(null);

// --- SIMPLIFIED STATE (No cloud/auth) ---
const [projectName, setProjectName] = useState("Untitled Project");
const [isSaving, setIsSaving] = useState(false);

// Hook for auto-saving (now local only)
useProjectSave(projectId || '');
useProjectSave(projectId || '');

// Handle sign out
const handleSignOut = async () => {
await signOut();
toast.success("Signed out successfully");
navigate('/login');
};

// Get user initials for avatar
const getUserInitials = () => {
if (!user) return 'U';
if (user.user_metadata?.full_name) {
const names = user.user_metadata.full_name.split(' ');
return names.length > 1
? `${names[0][0]}${names[1][0]}`.toUpperCase()
: names[0][0].toUpperCase();
}
if (user.email) {
return user.email[0].toUpperCase();
}
return 'U';
};

/* -------------------------------------------------------
1. INITIALIZATION (No cloud loading)
Expand Down Expand Up @@ -403,6 +440,51 @@ useEffect(() => {

{/* Top Right: Inspector */}
<div className={`absolute top-4 z-50 flex flex-col gap-3 items-end pointer-events-none transition-all duration-300 ${isChatOpen ? 'right-[25rem]' : 'right-4'}`}>
{/* User Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="pointer-events-auto flex items-center gap-2 px-3 py-2 bg-zinc-900/80 backdrop-blur-md border border-white/10 hover:border-violet-500/50 rounded-xl shadow-xl transition-all">
<Avatar className="h-7 w-7">
<AvatarFallback className="bg-gradient-to-br from-violet-600 to-indigo-600 text-white text-xs font-medium">
{getUserInitials()}
</AvatarFallback>
</Avatar>
<span className="text-sm text-white font-medium hidden md:block">
{user?.user_metadata?.full_name || user?.email?.split('@')[0] || 'User'}
</span>
</button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56 bg-zinc-900/95 backdrop-blur-xl border-white/10">
<DropdownMenuLabel className="text-zinc-400">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium text-white">
{user?.user_metadata?.full_name || 'User'}
</p>
<p className="text-xs text-zinc-400 truncate">
{user?.email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator className="bg-white/10" />
<DropdownMenuItem className="text-zinc-300 hover:text-white cursor-pointer">
<FolderKanban className="mr-2 h-4 w-4" />
<span>My Projects</span>
</DropdownMenuItem>
<DropdownMenuItem className="text-zinc-300 hover:text-white cursor-pointer">
<Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-white/10" />
<DropdownMenuItem
onClick={handleSignOut}
className="text-red-400 hover:text-red-300 focus:text-red-300 cursor-pointer"
>
<LogOut className="mr-2 h-4 w-4" />
<span>Sign out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

{/* AI Chat Toggle Button */}
<button
onClick={toggleChat}
Expand Down
Loading