diff --git a/frontend/src/components/AddRepoForm.tsx b/frontend/src/components/AddRepoForm.tsx index d0cf43a..dbcd095 100644 --- a/frontend/src/components/AddRepoForm.tsx +++ b/frontend/src/components/AddRepoForm.tsx @@ -5,15 +5,30 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -interface AddRepoFormProps { +// Discriminated union: if isOpen is provided, onOpenChange is required +type UncontrolledProps = { + isOpen?: undefined + onOpenChange?: undefined +} + +type ControlledProps = { + isOpen: boolean + onOpenChange: (open: boolean) => void +} + +type AddRepoFormProps = { onAdd: (gitUrl: string, branch: string) => Promise loading: boolean -} +} & (UncontrolledProps | ControlledProps) -export function AddRepoForm({ onAdd, loading }: AddRepoFormProps) { +export function AddRepoForm({ onAdd, loading, isOpen, onOpenChange }: AddRepoFormProps) { const [gitUrl, setGitUrl] = useState('') const [branch, setBranch] = useState('main') - const [showForm, setShowForm] = useState(false) + const [internalOpen, setInternalOpen] = useState(false) + + const isControlled = isOpen !== undefined + const showForm = isControlled ? isOpen : internalOpen + const setShowForm = isControlled ? onOpenChange : setInternalOpen const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() @@ -26,14 +41,17 @@ export function AddRepoForm({ onAdd, loading }: AddRepoFormProps) { return ( <> - + {/* Only show trigger button in uncontrolled mode */} + {!isControlled && ( + + )} {showForm && ( diff --git a/frontend/src/components/RepoList.tsx b/frontend/src/components/RepoList.tsx index 2c4ae86..c76e77c 100644 --- a/frontend/src/components/RepoList.tsx +++ b/frontend/src/components/RepoList.tsx @@ -8,6 +8,7 @@ interface RepoListProps { repos: Repository[] selectedRepo: string | null onSelect: (repoId: string) => void + onAddClick?: () => void loading?: boolean } @@ -94,15 +95,31 @@ const RepoCard = ({ repo, index, onSelect }: { ) } -export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListProps) { +export function RepoList({ repos, selectedRepo, onSelect, onAddClick, loading }: RepoListProps) { + // Hooks must be called before any conditional returns + const sortedRepos = useMemo(() => { + return [...repos].sort((a, b) => { + if (a.status === 'indexed' && b.status !== 'indexed') return -1 + if (b.status === 'indexed' && a.status !== 'indexed') return 1 + return (b.file_count || 0) - (a.file_count || 0) + }) + }, [repos]) + if (loading) return if (repos.length === 0) { + const isClickable = !!onAddClick return ( -
@@ -111,18 +128,10 @@ export function RepoList({ repos, selectedRepo, onSelect, loading }: RepoListPro

Add your first repository to start searching code with AI

- + ) } - const sortedRepos = useMemo(() => { - return [...repos].sort((a, b) => { - if (a.status === 'indexed' && b.status !== 'indexed') return -1 - if (b.status === 'indexed' && a.status !== 'indexed') return 1 - return (b.file_count || 0) - (a.file_count || 0) - }) - }, [repos]) - return (
{sortedRepos.map((repo, index) => ( diff --git a/frontend/src/components/auth/LoginForm.tsx b/frontend/src/components/auth/LoginForm.tsx index ca40fb0..a791d28 100644 --- a/frontend/src/components/auth/LoginForm.tsx +++ b/frontend/src/components/auth/LoginForm.tsx @@ -26,7 +26,14 @@ export function LoginForm() { await signIn(email, password) navigate('/dashboard') } catch (err: any) { - setError(err.message || 'Login failed') + const message = err.message?.toLowerCase() || '' + if (message.includes('email not confirmed') || message.includes('not confirmed')) { + setError('Please verify your email before logging in. Check your inbox for the verification link.') + } else if (message.includes('invalid login credentials')) { + setError('Invalid email or password. Please try again.') + } else { + setError(err.message || 'Login failed') + } } finally { setLoading(false) } diff --git a/frontend/src/components/auth/SignupForm.tsx b/frontend/src/components/auth/SignupForm.tsx index 8f4a8b4..57c57ed 100644 --- a/frontend/src/components/auth/SignupForm.tsx +++ b/frontend/src/components/auth/SignupForm.tsx @@ -1,12 +1,13 @@ import { useState } from 'react' import { useAuth } from '@/contexts/AuthContext' import { useNavigate, Link } from 'react-router-dom' +import { motion } from 'framer-motion' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Alert, AlertDescription } from '@/components/ui/alert' import { Navbar } from '@/components/landing' -import { Github, Loader2, Mail, Lock } from 'lucide-react' +import { Github, Loader2, Mail, Lock, CheckCircle2, Send, ArrowLeft } from 'lucide-react' export function SignupForm() { const [email, setEmail] = useState('') @@ -15,9 +16,36 @@ export function SignupForm() { const [error, setError] = useState('') const [loading, setLoading] = useState(false) const [oauthLoading, setOauthLoading] = useState<'github' | 'google' | null>(null) - const { signUp, signInWithGitHub, signInWithGoogle } = useAuth() + const [emailSent, setEmailSent] = useState(false) + const [resendLoading, setResendLoading] = useState(false) + const [resendSuccess, setResendSuccess] = useState(false) + const { signUp, signInWithGitHub, signInWithGoogle, resendVerification } = useAuth() const navigate = useNavigate() + const handleResend = async () => { + setError('') + setResendLoading(true) + setResendSuccess(false) + try { + await resendVerification(email) + setResendSuccess(true) + setError('') + } catch (err: any) { + setError(err.message || 'Failed to resend verification email') + } finally { + setResendLoading(false) + } + } + + const handleGoBack = () => { + setEmail('') + setPassword('') + setConfirmPassword('') + setEmailSent(false) + setResendSuccess(false) + setError('') + } + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError('') @@ -35,7 +63,9 @@ export function SignupForm() { setLoading(true) try { await signUp(email, password) - navigate('/dashboard') + setEmailSent(true) + setPassword('') + setConfirmPassword('') } catch (err: any) { setError(err.message || 'Signup failed') } finally { @@ -73,22 +103,150 @@ export function SignupForm() {
-
-

- Create your account -

-

- Free for open source projects -

-
+ {/* Email Verification Sent */} + {emailSent ? ( + + {/* Animated Icon */} +
+ {/* Outer glow ring */} + + {/* Icon container */} + + + + + +
+ + {/* Title */} + + Check your inbox + + + {/* Email display */} + +

We sent a verification link to

+
+ + {email} +
+ {error && ( +

{error}

+ )} +
+ + {/* Instructions card */} + +
+
+ +
+
+

+ Click the link in the email to verify your account +

+

+ Didn't receive it? Check your spam folder. +

+ + {/* Resend success message */} + {resendSuccess && ( +

+ ✓ Verification email resent! +

+ )} + + {/* Action buttons */} +
+ + · + +
+
+
+
+ + {/* Back link */} + + resendLoading && e.preventDefault()} + > + + Back to login + + +
+ ) : ( + <> +
+

+ Create your account +

+

+ Free for open source projects +

+
-
-
- {error && ( - - {error} - - )} +
+ + {error && ( + + {error} + + )}
@@ -242,6 +400,8 @@ export function SignupForm() { Sign in

+ + )}
diff --git a/frontend/src/components/dashboard/DashboardHome.tsx b/frontend/src/components/dashboard/DashboardHome.tsx index d472877..a682398 100644 --- a/frontend/src/components/dashboard/DashboardHome.tsx +++ b/frontend/src/components/dashboard/DashboardHome.tsx @@ -9,9 +9,11 @@ import { Zap, ArrowLeft, FolderGit2, - ExternalLink + ExternalLink, + Plus } from 'lucide-react' import { useAuth } from '../../contexts/AuthContext' +import { Button } from '../ui/button' import { RepoList } from '../RepoList' import { AddRepoForm } from '../AddRepoForm' import { SearchPanel } from '../SearchPanel' @@ -32,6 +34,7 @@ export function DashboardHome() { const [activeTab, setActiveTab] = useState('overview') const [loading, setLoading] = useState(false) const [reposLoading, setReposLoading] = useState(true) + const [showAddForm, setShowAddForm] = useState(false) const fetchRepos = async () => { if (!session?.access_token) return @@ -136,7 +139,20 @@ export function DashboardHome() { Semantic code search powered by AI

- + +
{/* Stats */} @@ -151,6 +167,7 @@ export function DashboardHome() { setSelectedRepo(id) setActiveTab('overview') }} + onAddClick={() => setShowAddForm(true)} /> )} diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx index dfbf272..c2b3f63 100644 --- a/frontend/src/contexts/AuthContext.tsx +++ b/frontend/src/contexts/AuthContext.tsx @@ -11,6 +11,7 @@ interface AuthContextType { signInWithGitHub: () => Promise; signInWithGoogle: () => Promise; signOut: () => Promise; + resendVerification: (email: string) => Promise; } const AuthContext = createContext(undefined); @@ -86,6 +87,14 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (error) throw error; }; + const resendVerification = async (email: string) => { + const { error } = await supabase.auth.resend({ + type: 'signup', + email, + }); + if (error) throw error; + }; + const value = { user, session, @@ -95,6 +104,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { signInWithGitHub, signInWithGoogle, signOut, + resendVerification, }; return {children};