[dyad] commited on
Commit
b9d5aef
·
1 Parent(s): a27839e

[dyad] Implement guest mode for builder - wrote 3 file(s), deleted 8 file(s)

Browse files
src/App.tsx CHANGED
@@ -6,10 +6,6 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
6
  import { ThemeProvider } from "@/components/theme-provider";
7
  import Index from "./pages/Index";
8
  import NotFound from "./pages/NotFound";
9
- import Landing from "./pages/Landing";
10
- import Auth from "./pages/Auth";
11
- import Dashboard from "./pages/Dashboard";
12
- import VerifyEmail from "./pages/VerifyEmail";
13
 
14
  const queryClient = new QueryClient();
15
 
@@ -21,11 +17,7 @@ const App = () => (
21
  <Sonner />
22
  <BrowserRouter>
23
  <Routes>
24
- <Route path="/" element={<Landing />} />
25
- <Route path="/auth" element={<Auth />} />
26
- <Route path="/verify-email" element={<VerifyEmail />} />
27
- <Route path="/dashboard" element={<Dashboard />} />
28
- <Route path="/builder" element={<Index />} />
29
  <Route path="*" element={<NotFound />} />
30
  </Routes>
31
  </BrowserRouter>
 
6
  import { ThemeProvider } from "@/components/theme-provider";
7
  import Index from "./pages/Index";
8
  import NotFound from "./pages/NotFound";
 
 
 
 
9
 
10
  const queryClient = new QueryClient();
11
 
 
17
  <Sonner />
18
  <BrowserRouter>
19
  <Routes>
20
+ <Route path="/" element={<Index />} />
 
 
 
 
21
  <Route path="*" element={<NotFound />} />
22
  </Routes>
23
  </BrowserRouter>
src/components/ProjectHistory.tsx DELETED
@@ -1,117 +0,0 @@
1
- "use client";
2
-
3
- import { useState } from "react";
4
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5
- import { Badge } from "@/components/ui/badge";
6
- import { Button } from "@/components/ui/button";
7
- import {
8
- CheckCircle,
9
- Clock,
10
- XCircle,
11
- MoreHorizontal,
12
- Play,
13
- Eye
14
- } from "lucide-react";
15
- import { Project } from "@/types/project";
16
- import { useNavigate } from "react-router-dom";
17
-
18
- interface ProjectHistoryProps {
19
- projects: Project[];
20
- onViewProject: (project: Project) => void;
21
- }
22
-
23
- const ProjectHistory = ({ projects, onViewProject }: ProjectHistoryProps) => {
24
- const navigate = useNavigate();
25
- const [expandedProject, setExpandedProject] = useState<string | null>(null);
26
-
27
- const getStatusIcon = (status: Project["status"]) => {
28
- switch (status) {
29
- case 'completed':
30
- return <CheckCircle className="h-4 w-4 text-green-500" />;
31
- case 'pending':
32
- return <Clock className="h-4 w-4 text-blue-500" />;
33
- case 'failed':
34
- return <XCircle className="h-4 w-4 text-red-500" />;
35
- default:
36
- return <Clock className="h-4 w-4 text-gray-500" />;
37
- }
38
- };
39
-
40
- const getStatusBadge = (status: Project["status"]) => {
41
- switch (status) {
42
- case 'completed':
43
- return <Badge variant="secondary" className="bg-green-100 text-green-800">Completed</Badge>;
44
- case 'pending':
45
- return <Badge variant="secondary" className="bg-blue-100 text-blue-800">In Progress</Badge>;
46
- case 'failed':
47
- return <Badge variant="secondary" className="bg-red-100 text-red-800">Failed</Badge>;
48
- default:
49
- return <Badge variant="secondary" className="bg-gray-100 text-gray-800">Unknown</Badge>;
50
- }
51
- };
52
-
53
- const formatDate = (date: Date) => {
54
- return new Intl.DateTimeFormat("en-US", {
55
- month: "short",
56
- day: "numeric",
57
- hour: "2-digit",
58
- minute: "2-digit",
59
- }).format(date);
60
- };
61
-
62
- return (
63
- <div className="space-y-4">
64
- {projects.map((project) => (
65
- <Card key={project.id} className="border border-border">
66
- <CardHeader className="pb-3">
67
- <div className="flex justify-between items-start">
68
- <div className="flex-1 min-w-0">
69
- <CardTitle className="text-lg flex items-center gap-2">
70
- <span className="truncate">{project.name}</span>
71
- {getStatusIcon(project.status)}
72
- </CardTitle>
73
- <p className="text-sm text-muted-foreground mt-1">
74
- Created {formatDate(project.createdAt)}
75
- </p>
76
- </div>
77
- <div className="flex items-center gap-2 ml-4">
78
- {getStatusBadge(project.status)}
79
- <Button
80
- variant="ghost"
81
- size="icon"
82
- onClick={() => setExpandedProject(expandedProject === project.id ? null : project.id)}
83
- >
84
- <MoreHorizontal className="h-4 w-4" />
85
- </Button>
86
- </div>
87
- </div>
88
- </CardHeader>
89
-
90
- {expandedProject === project.id && (
91
- <CardContent className="pt-0 border-t">
92
- <div className="flex gap-2 mt-3">
93
- <Button
94
- size="sm"
95
- onClick={() => navigate(`/builder?projectId=${project.id}`)}
96
- >
97
- <Play className="h-4 w-4 mr-2" />
98
- Continue
99
- </Button>
100
- <Button
101
- variant="outline"
102
- size="sm"
103
- onClick={() => onViewProject(project)}
104
- >
105
- <Eye className="h-4 w-4 mr-2" />
106
- View
107
- </Button>
108
- </div>
109
- </CardContent>
110
- )}
111
- </Card>
112
- ))}
113
- </div>
114
- );
115
- };
116
-
117
- export default ProjectHistory;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/hooks/useAuth.ts DELETED
@@ -1,56 +0,0 @@
1
- import { useState, useEffect } from 'react'
2
- import { supabase } from '@/lib/supabaseClient'
3
- import { User } from '@supabase/supabase-js'
4
-
5
- export const useAuth = () => {
6
- const [user, setUser] = useState<User | null>(null)
7
- const [loading, setLoading] = useState(true)
8
-
9
- useEffect(() => {
10
- const getUser = async () => {
11
- const { data: { user } } = await supabase.auth.getUser()
12
- setUser(user)
13
- setLoading(false)
14
- }
15
-
16
- getUser()
17
-
18
- const { data: { subscription } } = supabase.auth.onAuthStateChange(
19
- (_event, session) => {
20
- setUser(session?.user || null)
21
- setLoading(false)
22
- }
23
- )
24
-
25
- return () => subscription.unsubscribe()
26
- }, [])
27
-
28
- const signIn = async (email: string, password: string) => {
29
- const { data, error } = await supabase.auth.signInWithPassword({
30
- email,
31
- password,
32
- })
33
- return { data, error }
34
- }
35
-
36
- const signUp = async (email: string, password: string, firstName?: string, lastName?: string) => {
37
- const { data, error } = await supabase.auth.signUp({
38
- email,
39
- password,
40
- options: {
41
- data: {
42
- first_name: firstName,
43
- last_name: lastName,
44
- }
45
- }
46
- })
47
- return { data, error }
48
- }
49
-
50
- const signOut = async () => {
51
- const { error } = await supabase.auth.signOut()
52
- return { error }
53
- }
54
-
55
- return { user, loading, signIn, signUp, signOut }
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/hooks/useProjects.ts DELETED
@@ -1,253 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { supabase } from '@/lib/supabaseClient';
3
- import { Project, ProjectVersion } from '@/types/project';
4
- import { ChatMessage } from '@/services/gemini'
5
-
6
- export const useProjects = (userId: string | null) => {
7
- const [projects, setProjects] = useState<Project[]>([]);
8
- const [versions, setVersions] = useState<ProjectVersion[]>([]);
9
- const [loading, setLoading] = useState(true);
10
- const [error, setError] = useState<string | null>(null);
11
-
12
- useEffect(() => {
13
- if (userId) {
14
- fetchProjects();
15
- } else {
16
- setProjects([]);
17
- setVersions([]);
18
- setLoading(false);
19
- }
20
- }, [userId]);
21
-
22
- const fetchProjects = async () => {
23
- try {
24
- setLoading(true);
25
- const { data, error } = await supabase
26
- .from('projects')
27
- .select('*')
28
- .eq('user_id', userId)
29
- .order('updated_at', { ascending: false });
30
-
31
- if (error) throw error;
32
-
33
- setProjects(data.map(project => ({
34
- id: project.id,
35
- userId: project.user_id,
36
- name: project.name,
37
- status: project.status,
38
- prompt: project.prompt,
39
- code: project.code,
40
- createdAt: new Date(project.created_at),
41
- updatedAt: new Date(project.updated_at),
42
- messages: project.messages || []
43
- })));
44
- } catch (err: any) {
45
- setError(err.message);
46
- } finally {
47
- setLoading(false);
48
- }
49
- };
50
-
51
- const fetchVersions = async (projectId: string) => {
52
- if (!projectId) {
53
- setVersions([]);
54
- return;
55
- }
56
-
57
- try {
58
- const { data, error } = await supabase
59
- .from('project_versions')
60
- .select('*')
61
- .eq('project_id', projectId)
62
- .order('created_at', { ascending: false });
63
-
64
- if (error) throw error;
65
-
66
- setVersions(data.map(version => ({
67
- id: version.id,
68
- projectId: version.project_id,
69
- code: version.code,
70
- messages: version.messages || [],
71
- createdAt: version.created_at
72
- })));
73
- } catch (err: any) {
74
- console.error('Error fetching versions:', err);
75
- }
76
- };
77
-
78
- const createProjectWithFirstVersion = async (projectData: Omit<Project, 'id' | 'userId' | 'createdAt' | 'updatedAt'>) => {
79
- if (!userId) throw new Error('User not authenticated');
80
-
81
- const { data, error } = await supabase
82
- .from('projects')
83
- .insert([
84
- {
85
- user_id: userId,
86
- name: projectData.name,
87
- status: projectData.status,
88
- prompt: projectData.prompt,
89
- code: projectData.code,
90
- messages: projectData.messages
91
- }
92
- ])
93
- .select()
94
- .single();
95
-
96
- if (error) throw error;
97
-
98
- return {
99
- id: data.id,
100
- userId: data.user_id,
101
- name: data.name,
102
- status: data.status,
103
- prompt: data.prompt,
104
- code: data.code,
105
- createdAt: new Date(data.created_at),
106
- updatedAt: new Date(data.updated_at),
107
- messages: data.messages || []
108
- };
109
- };
110
-
111
- const createEmptyProject = async (name: string) => {
112
- if (!userId) throw new Error('User not authenticated');
113
-
114
- const { data, error } = await supabase
115
- .from('projects')
116
- .insert([
117
- {
118
- user_id: userId,
119
- name,
120
- status: 'completed',
121
- prompt: '',
122
- code: '<!DOCTYPE html><html><head><title>Empty Project</title></head><body><h1>New Project</h1></body></html>',
123
- messages: []
124
- }
125
- ])
126
- .select()
127
- .single();
128
-
129
- if (error) throw error;
130
-
131
- await fetchProjects();
132
-
133
- return {
134
- id: data.id,
135
- userId: data.user_id,
136
- name: data.name,
137
- status: data.status,
138
- prompt: data.prompt,
139
- code: data.code,
140
- createdAt: new Date(data.created_at),
141
- updatedAt: new Date(data.updated_at),
142
- messages: data.messages || []
143
- };
144
- };
145
-
146
- const createNewVersion = async (projectId: string, versionData: { code: string; messages: ChatMessage[] }) => {
147
- const { data, error } = await supabase
148
- .from('project_versions')
149
- .insert([
150
- {
151
- project_id: projectId,
152
- code: versionData.code,
153
- messages: versionData.messages
154
- }
155
- ])
156
- .select()
157
- .single();
158
-
159
- if (error) throw error;
160
-
161
- // Update the project with the new code and messages
162
- const { data: projectData, error: projectError } = await supabase
163
- .from('projects')
164
- .update({
165
- code: versionData.code,
166
- messages: versionData.messages,
167
- updated_at: new Date().toISOString()
168
- })
169
- .eq('id', projectId)
170
- .select()
171
- .single();
172
-
173
- if (projectError) throw projectError;
174
-
175
- await fetchProjects();
176
- await fetchVersions(projectId);
177
-
178
- return {
179
- id: projectData.id,
180
- userId: projectData.user_id,
181
- name: projectData.name,
182
- status: projectData.status,
183
- prompt: projectData.prompt,
184
- code: projectData.code,
185
- createdAt: new Date(projectData.created_at),
186
- updatedAt: new Date(projectData.updated_at),
187
- messages: projectData.messages || []
188
- };
189
- };
190
-
191
- const restoreVersion = async (projectId: string, versionId: string) => {
192
- const { data, error } = await supabase
193
- .from('project_versions')
194
- .select('code, messages')
195
- .eq('id', versionId)
196
- .single();
197
-
198
- if (error) throw error;
199
-
200
- const { data: projectData, error: projectError } = await supabase
201
- .from('projects')
202
- .update({
203
- code: data.code,
204
- messages: data.messages,
205
- updated_at: new Date().toISOString()
206
- })
207
- .eq('id', projectId)
208
- .select()
209
- .single();
210
-
211
- if (projectError) throw projectError;
212
-
213
- await fetchProjects();
214
- await fetchVersions(projectId);
215
-
216
- return {
217
- id: projectData.id,
218
- userId: projectData.user_id,
219
- name: projectData.name,
220
- status: projectData.status,
221
- prompt: projectData.prompt,
222
- code: projectData.code,
223
- createdAt: new Date(projectData.created_at),
224
- updatedAt: new Date(projectData.updated_at),
225
- messages: projectData.messages || []
226
- };
227
- };
228
-
229
- const deleteProject = async (projectId: string) => {
230
- const { error } = await supabase
231
- .from('projects')
232
- .delete()
233
- .eq('id', projectId);
234
-
235
- if (error) throw error;
236
-
237
- await fetchProjects();
238
- };
239
-
240
- return {
241
- projects,
242
- versions,
243
- loading,
244
- error,
245
- fetchProjects,
246
- fetchVersions,
247
- createProjectWithFirstVersion,
248
- createEmptyProject,
249
- createNewVersion,
250
- restoreVersion,
251
- deleteProject
252
- };
253
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/supabaseClient.ts DELETED
@@ -1,6 +0,0 @@
1
- import { createClient } from '@supabase/supabase-js'
2
-
3
- const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
4
- const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
5
-
6
- export const supabase = createClient(supabaseUrl, supabaseAnonKey)
 
 
 
 
 
 
 
src/pages/Auth.tsx DELETED
@@ -1,245 +0,0 @@
1
- "use client"
2
-
3
- import { useState } from "react"
4
- import { Button } from "@/components/ui/button"
5
- import { Input } from "@/components/ui/input"
6
- import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
7
- import { useNavigate } from "react-router-dom"
8
- import { useAuth } from "@/hooks/useAuth"
9
- import { Sparkles, Mail, Lock, User, ArrowLeft } from "lucide-react"
10
- import { motion, AnimatePresence } from "framer-motion"
11
- import AnimatedBackground from "@/components/AnimatedBackground"
12
- import { Badge } from "@/components/ui/badge"
13
-
14
- const Auth = () => {
15
- const [isSignUp, setIsSignUp] = useState(false)
16
- const [email, setEmail] = useState("")
17
- const [password, setPassword] = useState("")
18
- const [firstName, setFirstName] = useState("")
19
- const [lastName, setLastName] = useState("")
20
- const [loading, setLoading] = useState(false)
21
- const [error, setError] = useState("")
22
- const navigate = useNavigate()
23
- const { signIn, signUp } = useAuth()
24
-
25
- const handleSubmit = async (e: React.FormEvent) => {
26
- e.preventDefault()
27
- setLoading(true)
28
- setError("")
29
-
30
- try {
31
- if (isSignUp) {
32
- const { data, error } = await signUp(email, password, firstName, lastName)
33
- if (error) throw error
34
-
35
- // If sign up successful but email not confirmed, redirect to verification page
36
- if (data.user && !data.user.email_confirmed_at) {
37
- navigate('/verify-email')
38
- } else if (data.user) {
39
- // If email already confirmed, go to dashboard
40
- navigate('/dashboard')
41
- }
42
- } else {
43
- const { error } = await signIn(email, password)
44
- if (error) throw error
45
- navigate('/dashboard')
46
- }
47
- } catch (err: any) {
48
- setError(err.message || "An error occurred")
49
- } finally {
50
- setLoading(false)
51
- }
52
- }
53
-
54
- return (
55
- <div className="min-h-screen w-full flex items-center justify-center p-4 relative overflow-hidden">
56
- <AnimatedBackground />
57
-
58
- <motion.div
59
- initial={{ opacity: 0, y: 20 }}
60
- animate={{ opacity: 1, y: 0 }}
61
- transition={{ duration: 0.5 }}
62
- className="w-full max-w-md z-10"
63
- >
64
- <Card className="shadow-2xl backdrop-blur-xl border border-gray-800 bg-gray-900/80">
65
- <CardHeader className="text-center pb-6">
66
- <motion.div
67
- className="flex justify-center mb-4"
68
- whileHover={{ scale: 1.1 }}
69
- whileTap={{ scale: 0.9 }}
70
- >
71
- <div className="w-16 h-16 flex items-center justify-center">
72
- <img src="/front-pilot.svg" alt="FrontPilot Logo" className="w-16 h-16" />
73
- </div>
74
- </motion.div>
75
- <div className="flex items-center justify-center">
76
- <CardTitle className="text-3xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400">
77
- {isSignUp ? "Create Account" : "Welcome Back"}
78
- </CardTitle>
79
- <span className="ml-3 text-[0.6rem] bg-yellow-500/20 text-yellow-500 font-bold px-1.5 py-0.5 rounded-full backdrop-blur-sm border border-yellow-500/30">BETA</span>
80
- </div>
81
- <CardDescription className="text-base text-gray-400">
82
- {isSignUp
83
- ? "Join us to start building amazing websites."
84
- : "Sign in to continue to your dashboard."}
85
- </CardDescription>
86
- </CardHeader>
87
-
88
- <form onSubmit={handleSubmit}>
89
- <CardContent className="space-y-5">
90
- {error && (
91
- <motion.div
92
- initial={{ opacity: 0, x: -20 }}
93
- animate={{ opacity: 1, x: 0 }}
94
- className="text-red-400 text-sm p-3 bg-red-500/10 rounded-lg border border-red-500/20"
95
- >
96
- {error}
97
- </motion.div>
98
- )}
99
-
100
- <AnimatePresence mode="wait">
101
- {isSignUp && (
102
- <motion.div
103
- initial={{ opacity: 0, height: 0 }}
104
- animate={{ opacity: 1, height: "auto" }}
105
- exit={{ opacity: 0, height: 0 }}
106
- transition={{ duration: 0.3 }}
107
- className="grid grid-cols-2 gap-4"
108
- >
109
- <div className="space-y-2">
110
- <label htmlFor="firstName" className="text-sm font-medium text-gray-300 flex items-center">
111
- <User className="h-4 w-4 mr-2" />
112
- First Name
113
- </label>
114
- <Input
115
- id="firstName"
116
- type="text"
117
- placeholder="John"
118
- value={firstName}
119
- onChange={(e) => setFirstName(e.target.value)}
120
- required={isSignUp}
121
- className="bg-gray-800/50 border-gray-700 text-white focus:ring-2 focus:ring-blue-500/50"
122
- />
123
- </div>
124
- <div className="space-y-2">
125
- <label htmlFor="lastName" className="text-sm font-medium text-gray-300 flex items-center">
126
- <User className="h-4 w-4 mr-2" />
127
- Last Name
128
- </label>
129
- <Input
130
- id="lastName"
131
- type="text"
132
- placeholder="Doe"
133
- value={lastName}
134
- onChange={(e) => setLastName(e.target.value)}
135
- required={isSignUp}
136
- className="bg-gray-800/50 border-gray-700 text-white focus:ring-2 focus:ring-blue-500/50"
137
- />
138
- </div>
139
- </motion.div>
140
- )}
141
- </AnimatePresence>
142
-
143
- <div className="space-y-2">
144
- <label htmlFor="email" className="text-sm font-medium text-gray-300 flex items-center">
145
- <Mail className="h-4 w-4 mr-2" />
146
- Email
147
- </label>
148
- <Input
149
- id="email"
150
- type="email"
151
- placeholder="name@company.com"
152
- value={email}
153
- onChange={(e) => setEmail(e.target.value)}
154
- required
155
- className="bg-gray-800/50 border-gray-700 text-white focus:ring-2 focus:ring-blue-500/50"
156
- />
157
- </div>
158
-
159
- <div className="space-y-2">
160
- <label htmlFor="password" className="text-sm font-medium text-gray-300 flex items-center">
161
- <Lock className="h-4 w-4 mr-2" />
162
- Password
163
- </label>
164
- <Input
165
- id="password"
166
- type="password"
167
- placeholder="••••••••"
168
- value={password}
169
- onChange={(e) => setPassword(e.target.value)}
170
- required
171
- className="bg-gray-800/50 border-gray-700 text-white focus:ring-2 focus:ring-blue-500/50"
172
- />
173
- </div>
174
- </CardContent>
175
-
176
- <CardFooter className="flex flex-col pt-4">
177
- <motion.div
178
- whileHover={{ scale: 1.02 }}
179
- whileTap={{ scale: 0.98 }}
180
- className="w-full"
181
- >
182
- <Button
183
- type="submit"
184
- className="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-600 py-6 text-lg shadow-lg hover:shadow-xl"
185
- disabled={loading}
186
- >
187
- {loading ? (
188
- <motion.span
189
- initial={{ opacity: 0 }}
190
- animate={{ opacity: 1 }}
191
- className="flex items-center"
192
- >
193
- <svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
194
- <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
195
- <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
196
- </svg>
197
- Processing...
198
- </motion.span>
199
- ) : (
200
- isSignUp ? "Create Account" : "Sign In"
201
- )}
202
- </Button>
203
- </motion.div>
204
-
205
- <motion.div
206
- whileHover={{ scale: 1.02 }}
207
- whileTap={{ scale: 0.98 }}
208
- className="mt-4 w-full"
209
- >
210
- <Button
211
- type="button"
212
- variant="ghost"
213
- onClick={() => setIsSignUp(!isSignUp)}
214
- className="w-full text-gray-300 hover:text-white hover:bg-gray-800/50 py-5"
215
- >
216
- {isSignUp
217
- ? "Already have an account? Sign In"
218
- : "Don't have an account? Sign Up"}
219
- </Button>
220
- </motion.div>
221
-
222
- <motion.div
223
- whileHover={{ scale: 1.05 }}
224
- whileTap={{ scale: 0.9 }}
225
- className="mt-4 w-full"
226
- >
227
- <Button
228
- type="button"
229
- variant="outline"
230
- onClick={() => navigate('/')}
231
- className="w-full text-gray-300 hover:text-white hover:bg-gray-800/50 py-5"
232
- >
233
- <ArrowLeft className="h-4 w-4 mr-2" />
234
- Back to Home
235
- </Button>
236
- </motion.div>
237
- </CardFooter>
238
- </form>
239
- </Card>
240
- </motion.div>
241
- </div>
242
- )
243
- }
244
-
245
- export default Auth
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/Dashboard.tsx DELETED
@@ -1,528 +0,0 @@
1
- "use client"
2
-
3
- import { useState, useEffect } from "react"
4
- import { Button } from "@/components/ui/button"
5
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
6
- import { Input } from "@/components/ui/input"
7
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
8
- import { useNavigate } from "react-router-dom"
9
- import { useAuth } from "@/hooks/useAuth"
10
- import { useProjects } from "@/hooks/useProjects"
11
- import { supabase } from "@/lib/supabaseClient"
12
- import { Plus, FileText, Calendar, User, Loader2, LayoutDashboard, LogOut, Search, Grid3X3, List, Trash2, MoreHorizontal } from "lucide-react"
13
- import { Project } from "@/types/project"
14
- import { ModeToggle } from "@/components/theme-toggle"
15
- import { motion, AnimatePresence } from "framer-motion"
16
- import { Badge } from "@/components/ui/badge"
17
- import {
18
- DropdownMenu,
19
- DropdownMenuContent,
20
- DropdownMenuItem,
21
- DropdownMenuTrigger,
22
- } from "@/components/ui/dropdown-menu"
23
- import { useToast } from "@/hooks/use-toast"
24
-
25
- const Dashboard = () => {
26
- const navigate = useNavigate()
27
- const { user, signOut } = useAuth()
28
- const { projects, loading, error, createEmptyProject, deleteProject } = useProjects(user?.id || null)
29
- const [userProfile, setUserProfile] = useState<{ first_name: string; last_name: string } | null>(null)
30
- const [profileLoading, setProfileLoading] = useState(true)
31
- const [showNameDialog, setShowNameDialog] = useState(false)
32
- const [prototypeName, setPrototypeName] = useState("")
33
- const [searchTerm, setSearchTerm] = useState("")
34
- const [viewMode, setViewMode] = useState<"grid" | "list">("grid")
35
- const [projectToDelete, setProjectToDelete] = useState<string | null>(null)
36
- const { toast } = useToast()
37
-
38
- useEffect(() => {
39
- if (user) {
40
- fetchUserProfile()
41
- }
42
- }, [user])
43
-
44
- const fetchUserProfile = async () => {
45
- try {
46
- const { data, error } = await supabase
47
- .from('profiles')
48
- .select('first_name, last_name')
49
- .eq('id', user?.id)
50
- .single()
51
-
52
- if (error) throw error
53
- setUserProfile(data)
54
- } catch (err) {
55
- console.error('Error fetching user profile:', err)
56
- } finally {
57
- setProfileLoading(false)
58
- }
59
- }
60
-
61
- const handleCreatePrototype = () => {
62
- setShowNameDialog(true)
63
- }
64
-
65
- const handleNameSubmit = async () => {
66
- if (!prototypeName.trim()) return;
67
-
68
- try {
69
- const newProject = await createEmptyProject(prototypeName);
70
- setShowNameDialog(false);
71
- setPrototypeName("");
72
- navigate(`/builder?projectId=${newProject.id}`);
73
- } catch (err) {
74
- console.error('Error creating prototype:', err);
75
- toast({
76
- title: "Error",
77
- description: "Failed to create prototype. Please try again.",
78
- variant: "destructive",
79
- });
80
- }
81
- }
82
-
83
- const handleSignOut = async () => {
84
- await signOut()
85
- navigate('/')
86
- }
87
-
88
- const formatDate = (date: Date) => {
89
- return new Intl.DateTimeFormat('en-US', {
90
- month: 'short',
91
- day: 'numeric',
92
- year: 'numeric'
93
- }).format(date)
94
- }
95
-
96
- const handleDeleteProject = async (projectId: string) => {
97
- try {
98
- await deleteProject(projectId);
99
- toast({
100
- title: "Project deleted",
101
- description: "Your prototype has been successfully deleted.",
102
- });
103
- } catch (err) {
104
- console.error('Error deleting project:', err);
105
- toast({
106
- title: "Error",
107
- description: "Failed to delete project. Please try again.",
108
- variant: "destructive",
109
- });
110
- } finally {
111
- setProjectToDelete(null);
112
- }
113
- }
114
-
115
- const filteredProjects = projects.filter(project =>
116
- project.name.toLowerCase().includes(searchTerm.toLowerCase())
117
- )
118
-
119
- if (!user) {
120
- return (
121
- <div className="min-h-screen flex items-center justify-center bg-secondary/50">
122
- <div className="text-center">
123
- <Loader2 className="h-8 w-8 animate-spin mx-auto mb-4 text-primary" />
124
- <p className="text-muted-foreground">Loading...</p>
125
- </div>
126
- </div>
127
- )
128
- }
129
-
130
- return (
131
- <div className="min-h-screen flex bg-background">
132
- {/* Sidebar */}
133
- <motion.aside
134
- initial={{ x: -300 }}
135
- animate={{ x: 0 }}
136
- transition={{ type: "spring", stiffness: 300, damping: 30 }}
137
- className="w-64 flex-shrink-0 bg-card border-r flex flex-col shadow-xl"
138
- >
139
- <div className="h-16 border-b flex items-center px-6">
140
- <motion.div
141
- className="flex items-center space-x-3"
142
- whileHover={{ scale: 1.02 }}
143
- >
144
- <div className="w-10 h-10 flex items-center justify-center">
145
- <img src="/front-pilot.svg" alt="FrontPilot Logo" className="w-10 h-10" />
146
- </div>
147
- <div className="flex items-center">
148
- <span className="font-bold text-xl bg-clip-text text-transparent bg-gradient-to-r from-primary to-indigo-500">
149
- FrontPilot
150
- </span>
151
- <span className="ml-2 text-[0.6rem] bg-yellow-500/20 text-yellow-500 font-bold px-1.5 py-0.5 rounded-full backdrop-blur-sm border border-yellow-500/30">BETA</span>
152
- </div>
153
- </motion.div>
154
- </div>
155
-
156
- <nav className="flex-1 px-4 py-6 space-y-2">
157
- <motion.div
158
- whileHover={{ x: 5 }}
159
- whileTap={{ scale: 0.98 }}
160
- >
161
- <Button variant="secondary" className="w-full justify-start py-6 text-base">
162
- <LayoutDashboard className="mr-3 h-5 w-5" />
163
- Dashboard
164
- </Button>
165
- </motion.div>
166
-
167
- <motion.div
168
- whileHover={{ x: 5 }}
169
- whileTap={{ scale: 0.98 }}
170
- >
171
- <Button
172
- variant="ghost"
173
- className="w-full justify-start py-6 text-base"
174
- onClick={handleCreatePrototype}
175
- >
176
- <Plus className="mr-3 h-5 w-5" />
177
- New Prototype
178
- </Button>
179
- </motion.div>
180
- </nav>
181
-
182
- <div className="mt-auto p-4 border-t">
183
- <div className="flex items-center mb-4">
184
- <div className="w-12 h-12 rounded-xl bg-secondary flex items-center justify-center mr-3 shadow-md">
185
- <User className="h-6 w-6 text-muted-foreground" />
186
- </div>
187
- <div>
188
- <p className="text-sm font-medium text-foreground">
189
- {profileLoading ? "Loading..." : userProfile ? `${userProfile.first_name} ${userProfile.last_name}` : user.email?.split('@')[0]}
190
- </p>
191
- <p className="text-xs text-muted-foreground truncate max-w-[120px]">
192
- {user.email}
193
- </p>
194
- </div>
195
- </div>
196
-
197
- <motion.div
198
- whileHover={{ scale: 1.02 }}
199
- whileTap={{ scale: 0.98 }}
200
- >
201
- <Button
202
- variant="outline"
203
- className="w-full py-5"
204
- onClick={handleSignOut}
205
- >
206
- <LogOut className="mr-2 h-4 w-4" />
207
- Sign Out
208
- </Button>
209
- </motion.div>
210
- </div>
211
- </motion.aside>
212
-
213
- {/* Main Content */}
214
- <main className="flex-1 overflow-hidden">
215
- <header className="sticky top-0 z-10 border-b bg-background/80 backdrop-blur-xl">
216
- <div className="container mx-auto px-6 py-4 flex items-center justify-between">
217
- <div>
218
- <h1 className="text-2xl font-bold text-foreground">My Prototypes</h1>
219
- <p className="text-sm text-muted-foreground">Manage and create your web projects</p>
220
- </div>
221
-
222
- <div className="flex items-center space-x-4">
223
- <div className="relative">
224
- <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
225
- <Input
226
- type="text"
227
- placeholder="Search prototypes..."
228
- className="pl-10 pr-4 py-2 w-64"
229
- value={searchTerm}
230
- onChange={(e) => setSearchTerm(e.target.value)}
231
- />
232
- </div>
233
-
234
- <div className="flex items-center space-x-2 bg-secondary rounded-lg p-1">
235
- <Button
236
- variant={viewMode === "grid" ? "secondary" : "ghost"}
237
- size="icon"
238
- className="h-9 w-9"
239
- onClick={() => setViewMode("grid")}
240
- >
241
- <Grid3X3 className="h-4 w-4" />
242
- </Button>
243
- <Button
244
- variant={viewMode === "list" ? "secondary" : "ghost"}
245
- size="icon"
246
- className="h-9 w-9"
247
- onClick={() => setViewMode("list")}
248
- >
249
- <List className="h-4 w-4" />
250
- </Button>
251
- </div>
252
-
253
- <ModeToggle />
254
- </div>
255
- </div>
256
- </header>
257
-
258
- <div className="container mx-auto px-6 py-8">
259
- {loading ? (
260
- <div className="flex justify-center items-center h-96">
261
- <Loader2 className="h-16 w-16 animate-spin text-primary" />
262
- </div>
263
- ) : error ? (
264
- <motion.div
265
- initial={{ opacity: 0, y: 20 }}
266
- animate={{ opacity: 1, y: 0 }}
267
- className="text-center py-20"
268
- >
269
- <div className="mx-auto w-24 h-24 bg-red-500/10 rounded-full flex items-center justify-center mb-6">
270
- <svg xmlns="http://www.w3.org/2000/svg" className="h-12 w-12 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
271
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
272
- </svg>
273
- </div>
274
- <h3 className="text-3xl font-bold text-foreground mb-4">Error Loading Prototypes</h3>
275
- <p className="text-muted-foreground mb-8 max-w-md mx-auto">{error}</p>
276
- <Button
277
- onClick={() => window.location.reload()}
278
- className="bg-primary text-primary-foreground hover:bg-primary/90 py-5 px-8 text-lg"
279
- >
280
- Retry
281
- </Button>
282
- </motion.div>
283
- ) : filteredProjects.length === 0 ? (
284
- searchTerm ? (
285
- <motion.div
286
- initial={{ opacity: 0 }}
287
- animate={{ opacity: 1 }}
288
- className="text-center py-20"
289
- >
290
- <div className="mx-auto w-24 h-24 bg-secondary rounded-full flex items-center justify-center mb-6">
291
- <Search className="h-12 w-12 text-muted-foreground" />
292
- </div>
293
- <h3 className="text-2xl font-bold text-foreground mb-2">No prototypes found</h3>
294
- <p className="text-muted-foreground mb-6">
295
- No prototypes match your search for "{searchTerm}"
296
- </p>
297
- <Button
298
- onClick={() => setSearchTerm("")}
299
- variant="outline"
300
- className="py-5 px-6"
301
- >
302
- Clear Search
303
- </Button>
304
- </motion.div>
305
- ) : (
306
- <motion.div
307
- initial={{ opacity: 0, y: 20 }}
308
- animate={{ opacity: 1, y: 0 }}
309
- transition={{ duration: 0.5 }}
310
- className="text-center py-20 border-2 border-dashed rounded-2xl"
311
- >
312
- <div className="mx-auto w-24 h-24 bg-secondary rounded-full flex items-center justify-center mb-6">
313
- <FileText className="h-12 w-12 text-muted-foreground" />
314
- </div>
315
- <h3 className="text-3xl font-bold text-foreground mb-4">No prototypes yet</h3>
316
- <p className="text-muted-foreground mb-8 max-w-md mx-auto">
317
- Get started by creating your first prototype. Describe what you want to build and our AI will generate it for you.
318
- </p>
319
- <motion.div
320
- whileHover={{ scale: 1.05 }}
321
- whileTap={{ scale: 0.95 }}
322
- >
323
- <Button
324
- onClick={handleCreatePrototype}
325
- className="bg-gradient-to-r from-primary to-indigo-600 text-primary-foreground hover:from-primary/90 hover:to-indigo-700 py-6 px-8 text-lg shadow-xl hover:shadow-2xl"
326
- >
327
- <Plus className="h-5 w-5 mr-2" />
328
- Create Your First Prototype
329
- </Button>
330
- </motion.div>
331
- </motion.div>
332
- )
333
- ) : (
334
- <motion.div
335
- className={viewMode === "grid"
336
- ? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
337
- : "space-y-4"
338
- }
339
- layout
340
- >
341
- <AnimatePresence>
342
- {filteredProjects.map((project) => (
343
- <motion.div
344
- key={project.id}
345
- layout
346
- initial={{ opacity: 0, scale: 0.8 }}
347
- animate={{ opacity: 1, scale: 1 }}
348
- exit={{ opacity: 0, scale: 0.8 }}
349
- transition={{ type: "spring", stiffness: 300, damping: 30 }}
350
- whileHover={{ y: -5 }}
351
- whileTap={{ scale: 0.98 }}
352
- className="w-full"
353
- >
354
- <Card
355
- className={`cursor-pointer hover:shadow-2xl transition-all h-full border-2 ${
356
- viewMode === "list" ? "flex items-center" : ""
357
- }`}
358
- onClick={() => navigate(`/builder?projectId=${project.id}`)}
359
- >
360
- {viewMode === "list" ? (
361
- <>
362
- <div className="p-6 flex-shrink-0">
363
- <div className="w-3 h-3 rounded-full bg-primary"></div>
364
- </div>
365
- <div className="flex-1 p-6">
366
- <CardTitle className="flex items-center text-lg">
367
- {project.name}
368
- </CardTitle>
369
- <CardDescription className="flex items-center text-xs mt-2">
370
- <Calendar className="h-3 w-3 mr-1" />
371
- {formatDate(project.createdAt)}
372
- </CardDescription>
373
- </div>
374
- <div className="p-6 flex items-center space-x-2">
375
- <span className="text-sm text-muted-foreground capitalize px-3 py-1 bg-secondary rounded-full">
376
- {project.status}
377
- </span>
378
- <DropdownMenu>
379
- <DropdownMenuTrigger asChild>
380
- <Button variant="ghost" size="icon">
381
- <MoreHorizontal className="h-4 w-4" />
382
- </Button>
383
- </DropdownMenuTrigger>
384
- <DropdownMenuContent align="end">
385
- <DropdownMenuItem
386
- className="text-red-600"
387
- onClick={(e) => {
388
- e.stopPropagation();
389
- setProjectToDelete(project.id);
390
- }}
391
- >
392
- <Trash2 className="h-4 w-4 mr-2" />
393
- Delete
394
- </DropdownMenuItem>
395
- </DropdownMenuContent>
396
- </DropdownMenu>
397
- </div>
398
- </>
399
- ) : (
400
- <>
401
- <CardHeader>
402
- <div className="flex justify-between items-start">
403
- <div>
404
- <CardTitle className="flex items-center">
405
- <div className="w-3 h-3 rounded-full bg-primary mr-2"></div>
406
- {project.name}
407
- </CardTitle>
408
- <CardDescription className="flex items-center text-xs pt-1">
409
- <Calendar className="h-3 w-3 mr-1" />
410
- {formatDate(project.createdAt)}
411
- </CardDescription>
412
- </div>
413
- <DropdownMenu>
414
- <DropdownMenuTrigger asChild>
415
- <Button
416
- variant="ghost"
417
- size="icon"
418
- onClick={(e) => e.stopPropagation()}
419
- >
420
- <MoreHorizontal className="h-4 w-4" />
421
- </Button>
422
- </DropdownMenuTrigger>
423
- <DropdownMenuContent align="end">
424
- <DropdownMenuItem
425
- className="text-red-600"
426
- onClick={(e) => {
427
- e.stopPropagation();
428
- setProjectToDelete(project.id);
429
- }}
430
- >
431
- <Trash2 className="h-4 w-4 mr-2" />
432
- Delete
433
- </DropdownMenuItem>
434
- </DropdownMenuContent>
435
- </DropdownMenu>
436
- </div>
437
- </CardHeader>
438
- <CardContent>
439
- <div className="flex items-center justify-between">
440
- <span className="text-sm text-muted-foreground capitalize px-3 py-1 bg-secondary rounded-full">
441
- {project.status}
442
- </span>
443
- <Button variant="ghost" size="sm">Open</Button>
444
- </div>
445
- </CardContent>
446
- </>
447
- )}
448
- </Card>
449
- </motion.div>
450
- ))}
451
- </AnimatePresence>
452
- </motion.div>
453
- )}
454
- </div>
455
- </main>
456
-
457
- {/* Name Prototype Dialog */}
458
- <Dialog open={showNameDialog} onOpenChange={setShowNameDialog}>
459
- <DialogContent className="sm:max-w-md">
460
- <DialogHeader>
461
- <DialogTitle className="text-2xl">Name Your Prototype</DialogTitle>
462
- <DialogDescription>
463
- Give your new prototype a descriptive name to help you identify it later.
464
- </DialogDescription>
465
- </DialogHeader>
466
- <div className="py-4">
467
- <Input
468
- placeholder="e.g., Landing Page, Dashboard, E-commerce Site"
469
- value={prototypeName}
470
- onChange={(e) => setPrototypeName(e.target.value)}
471
- onKeyPress={(e) => e.key === 'Enter' && handleNameSubmit()}
472
- className="text-lg py-6"
473
- />
474
- </div>
475
- <DialogFooter className="gap-3">
476
- <Button
477
- variant="outline"
478
- onClick={() => setShowNameDialog(false)}
479
- className="py-5 px-6"
480
- >
481
- Cancel
482
- </Button>
483
- <motion.div
484
- whileHover={{ scale: 1.02 }}
485
- whileTap={{ scale: 0.98 }}
486
- >
487
- <Button
488
- onClick={handleNameSubmit}
489
- disabled={!prototypeName.trim()}
490
- className="bg-gradient-to-r from-primary to-indigo-600 text-primary-foreground hover:from-primary/90 hover:to-indigo-700 py-5 px-6"
491
- >
492
- Create Prototype
493
- </Button>
494
- </motion.div>
495
- </DialogFooter>
496
- </DialogContent>
497
- </Dialog>
498
-
499
- {/* Delete Confirmation Dialog */}
500
- <Dialog open={!!projectToDelete} onOpenChange={() => setProjectToDelete(null)}>
501
- <DialogContent>
502
- <DialogHeader>
503
- <DialogTitle>Delete Prototype</DialogTitle>
504
- <DialogDescription>
505
- Are you sure you want to delete this prototype? This action cannot be undone.
506
- </DialogDescription>
507
- </DialogHeader>
508
- <DialogFooter className="gap-3">
509
- <Button
510
- variant="outline"
511
- onClick={() => setProjectToDelete(null)}
512
- >
513
- Cancel
514
- </Button>
515
- <Button
516
- variant="destructive"
517
- onClick={() => projectToDelete && handleDeleteProject(projectToDelete)}
518
- >
519
- Delete
520
- </Button>
521
- </DialogFooter>
522
- </DialogContent>
523
- </Dialog>
524
- </div>
525
- )
526
- }
527
-
528
- export default Dashboard
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/Index.tsx CHANGED
@@ -2,24 +2,16 @@
2
 
3
  import { useState, useEffect, useRef } from "react";
4
  import { Button } from "@/components/ui/button";
5
- import { Eye, Code, Download, History, FileText, Plus, Play, Save, GitBranch, Loader2, Wifi, WifiOff, MousePointer, ExternalLink } from "lucide-react";
6
- import { MadeWithDyad } from "@/components/made-with-dyad";
7
  import { useToast } from "@/hooks/use-toast";
8
  import { generateCode, modifyCode, ChatMessage } from "@/services/gemini";
9
- import { Project, ProjectFile } from "@/types/project";
10
  import ChatInterface from "@/components/ChatInterface";
11
  import VersionsPanel from "@/components/VersionsPanel";
12
  import CodeEditor from "@/components/CodeEditor";
13
  import PreviewPanel from "@/components/PreviewPanel";
14
- import { DiffResult, generateDiff, generateDiffSummary } from "@/services/diffPatch";
15
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
16
- import { Badge } from "@/components/ui/badge";
17
- import { useNavigate, useSearchParams } from "react-router-dom";
18
- import { useAuth } from "@/hooks/useAuth";
19
- import { useProjects } from "@/hooks/useProjects";
20
  import DiffReviewModal from "@/components/DiffReviewModal";
21
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
22
- import { Input } from "@/components/ui/input";
23
  import { ModeToggle } from "@/components/theme-toggle";
24
  import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
25
  import JSZip from "jszip";
@@ -41,22 +33,15 @@ const Index = () => {
41
 
42
  const [leftTab, setLeftTab] = useState("chat");
43
  const [rightTab, setRightTab] = useState("preview");
44
- const [activeProject, setActiveProject] = useState<Project | null>(null);
45
  const { toast } = useToast();
46
  const [leftPanelWidth, setLeftPanelWidth] = useState(40);
47
  const containerRef = useRef<HTMLDivElement>(null);
48
  const isResizingRef = useRef(false);
49
- const navigate = useNavigate();
50
- const [searchParams] = useSearchParams();
51
- const { user, loading: authLoading } = useAuth();
52
- const { projects, versions, fetchVersions, createEmptyProject, createProjectWithFirstVersion, createNewVersion, restoreVersion } = useProjects(user?.id || null);
53
- const projectIdFromUrl = searchParams.get('projectId');
54
-
55
  const [pendingModification, setPendingModification] = useState<{ changes: ChangeProposal[]; newMessages: ChatMessage[] } | null>(null);
56
  const [isDiffModalOpen, setIsDiffModalOpen] = useState(false);
57
  const [diffToReview, setDiffToReview] = useState<DiffResult | null>(null);
58
- const [showNameDialog, setShowNameDialog] = useState(false);
59
- const [prototypeName, setPrototypeName] = useState("");
60
  const [isAIReady, setIsAIReady] = useState(false);
61
  const [selectedElement, setSelectedElement] = useState<{ tagName: string; description: string } | null>(null);
62
 
@@ -74,30 +59,7 @@ const Index = () => {
74
  variant: "destructive",
75
  });
76
  }
77
- }, []);
78
-
79
- useEffect(() => {
80
- if (!user && !authLoading) {
81
- navigate('/auth');
82
- }
83
- }, [user, authLoading, navigate]);
84
-
85
- useEffect(() => {
86
- if (projectIdFromUrl && projects.length > 0) {
87
- const project = projects.find(p => p.id === projectIdFromUrl);
88
- if (project && (!activeProject || activeProject.id !== project.id)) {
89
- loadProject(project);
90
- }
91
- }
92
- }, [projectIdFromUrl, projects]);
93
-
94
- useEffect(() => {
95
- if (activeProject) {
96
- fetchVersions(activeProject.id);
97
- } else {
98
- fetchVersions('');
99
- }
100
- }, [activeProject]);
101
 
102
  useEffect(() => {
103
  const handleMessage = (event: MessageEvent) => {
@@ -221,7 +183,7 @@ const Index = () => {
221
  const newMessages = [...messages, userMessage];
222
  setMessages(newMessages);
223
 
224
- if (activeProject) {
225
  handleModificationRequest(messageContent, newMessages);
226
  } else {
227
  handleNewProjectRequest(prompt, newMessages);
@@ -251,16 +213,14 @@ const Index = () => {
251
  setActivePreviewFile(newFiles[0].name);
252
  }
253
 
254
- const newProject = await createProjectWithFirstVersion({
255
- name: prompt.substring(0, 30) + (prompt.length > 30 ? "..." : ""),
256
- status: 'completed',
257
- prompt,
258
  code: JSON.stringify(newFiles),
259
- messages: finalMessages as any
260
- });
261
-
262
- setActiveProject(newProject);
263
- navigate(`/builder?projectId=${newProject.id}`, { replace: true });
264
  } catch (error: any) {
265
  console.error("Error generating code:", error);
266
  toast({
@@ -306,7 +266,7 @@ const Index = () => {
306
  };
307
 
308
  const handleAcceptChanges = async () => {
309
- if (!pendingModification || !activeProject) return;
310
 
311
  const { changes, newMessages } = pendingModification;
312
  const updatedFiles = [...files];
@@ -325,9 +285,14 @@ const Index = () => {
325
  setFiles(updatedFiles);
326
  setMessages(finalMessages);
327
 
328
- await createNewVersion(activeProject.id, { code: JSON.stringify(updatedFiles), messages: finalMessages as any });
 
 
 
 
 
 
329
 
330
- setActiveProject(prev => prev ? { ...prev, code: JSON.stringify(updatedFiles), messages: finalMessages as any } : null);
331
  setPendingModification(null);
332
  toast({ title: "Changes Accepted", description: "A new version has been saved." });
333
  };
@@ -345,52 +310,35 @@ const Index = () => {
345
  setIsDiffModalOpen(true);
346
  };
347
 
348
- const handleRestoreVersion = async (versionId: string) => {
349
- if (!activeProject) return;
350
- try {
351
- const restoredProject = await restoreVersion(activeProject.id, versionId);
352
- if (restoredProject) {
353
- loadProject(restoredProject);
 
 
 
 
 
354
  toast({ title: "Version Restored", description: "The prototype has been restored to a previous version." });
 
 
355
  }
356
- } catch (error: any) {
357
- toast({ title: "Error", description: `Failed to restore version: ${error.message}.`, variant: "destructive" });
358
  }
359
  };
360
 
361
- const loadProject = (project: Project) => {
362
- setActiveProject(project);
363
- setMessages(project.messages || []);
364
- try {
365
- const parsedFiles = JSON.parse(project.code);
366
- if (Array.isArray(parsedFiles) && parsedFiles.length > 0) {
367
- setFiles(parsedFiles);
368
- setActiveEditorFile(parsedFiles[0].name);
369
- setActivePreviewFile(parsedFiles[0].name);
370
- } else { throw new Error("Not an array"); }
371
- } catch (e) {
372
- const fallbackFiles = [{ name: 'index.html', code: project.code }];
373
- setFiles(fallbackFiles);
374
- setActiveEditorFile('index.html');
375
- setActivePreviewFile('index.html');
376
- }
377
  setPendingModification(null);
378
- toast({ title: "Prototype loaded", description: `Loaded prototype: ${project.name}` });
379
- };
380
-
381
- const createNewPrototype = () => setShowNameDialog(true);
382
-
383
- const handleNameSubmit = async () => {
384
- if (!prototypeName.trim()) return;
385
- try {
386
- const newProject = await createEmptyProject(prototypeName);
387
- setShowNameDialog(false);
388
- setPrototypeName("");
389
- navigate(`/builder?projectId=${newProject.id}`);
390
- loadProject(newProject);
391
- } catch (err) {
392
- toast({ title: "Error", description: "Failed to create prototype.", variant: "destructive" });
393
- }
394
  };
395
 
396
  const exportProjectAsZip = () => {
@@ -403,7 +351,7 @@ const Index = () => {
403
  zip.file(file.name, file.code);
404
  });
405
  zip.generateAsync({ type: "blob" }).then(content => {
406
- saveAs(content, `${activeProject?.name || "project"}.zip`);
407
  });
408
  toast({ title: "Project exported", description: "Your project has been downloaded as a zip file." });
409
  };
@@ -415,7 +363,6 @@ const Index = () => {
415
  return;
416
  }
417
 
418
- // Create a Blob directly from the HTML content of the selected file
419
  const blob = new Blob([fileToOpen.code], { type: 'text/html' });
420
  const url = URL.createObjectURL(blob);
421
  window.open(url, '_blank');
@@ -464,10 +411,6 @@ const Index = () => {
464
  }
465
  };
466
 
467
- if (authLoading) {
468
- return <div className="min-h-screen flex items-center justify-center"><Loader2 className="h-8 w-8 animate-spin" /></div>;
469
- }
470
-
471
  return (
472
  <div className="flex flex-col h-screen bg-background text-foreground">
473
  <header className="border-b bg-card shadow-sm">
@@ -490,9 +433,8 @@ const Index = () => {
490
  </div>
491
  )}
492
  <ModeToggle />
493
- <Button size="sm" onClick={() => navigate('/dashboard')} variant="outline">Dashboard</Button>
494
- <Button size="sm" onClick={createNewPrototype} className="bg-primary text-primary-foreground hover:bg-primary/90">
495
- <Plus className="h-4 w-4 mr-1" /> New Prototype
496
  </Button>
497
  </div>
498
  </div>
@@ -508,7 +450,7 @@ const Index = () => {
508
  </div>
509
  <div className="flex-1 overflow-hidden">
510
  {leftTab === 'chat' && <ChatInterface prompt={prompt} setPrompt={setPrompt} messages={messages} isLoading={isLoading} onSubmit={handleSubmit} pendingModification={pendingModification} onAccept={handleAcceptChanges} onReject={handleRejectChanges} onReview={handleReviewChanges} selectedElement={selectedElement} onClearSelectedElement={() => setSelectedElement(null)} />}
511
- {leftTab === 'versions' && activeProject && <VersionsPanel versions={versions} onRestoreVersion={handleRestoreVersion} />}
512
  </div>
513
  </div>
514
 
@@ -531,18 +473,9 @@ const Index = () => {
531
  {rightTab === 'preview' && <PreviewPanel code={getPreviewCode()} ref={iframeRef} className={isSelectingElement ? "cursor-crosshair" : ""} />}
532
  {rightTab === 'code' && <CodeEditor files={files} activeFile={activeEditorFile} onFileChange={handleFileChange} onFileSelect={setActiveEditorFile} onExport={exportProjectAsZip} />}
533
  </div>
534
- {activeProject && <div className="border-t bg-card px-4 py-2 flex items-center justify-between"><div className="flex items-center"><div className="w-2 h-2 rounded-full bg-primary mr-2"></div><span className="text-sm font-medium truncate max-w-xs">{activeProject.name}</span></div></div>}
535
  </div>
536
  </div>
537
  <DiffReviewModal isOpen={isDiffModalOpen} onClose={() => setIsDiffModalOpen(false)} diffResult={diffToReview} onApply={handleAcceptChanges} />
538
-
539
- <Dialog open={showNameDialog} onOpenChange={setShowNameDialog}>
540
- <DialogContent>
541
- <DialogHeader><DialogTitle>Name Your New Prototype</DialogTitle><DialogDescription>Give your new prototype a name to get started.</DialogDescription></DialogHeader>
542
- <div className="py-4"><Input placeholder="e.g., My Awesome Landing Page" value={prototypeName} onChange={(e) => setPrototypeName(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleNameSubmit()} /></div>
543
- <DialogFooter><Button variant="outline" onClick={() => setShowNameDialog(false)}>Cancel</Button><Button onClick={handleNameSubmit} disabled={!prototypeName.trim()} className="bg-primary text-primary-foreground hover:bg-primary/90">Create Prototype</Button></DialogFooter>
544
- </DialogContent>
545
- </Dialog>
546
  </div>
547
  );
548
  };
 
2
 
3
  import { useState, useEffect, useRef } from "react";
4
  import { Button } from "@/components/ui/button";
5
+ import { Eye, Code, History, Plus, GitBranch, Loader2, WifiOff, MousePointer, ExternalLink, RefreshCw } from "lucide-react";
 
6
  import { useToast } from "@/hooks/use-toast";
7
  import { generateCode, modifyCode, ChatMessage } from "@/services/gemini";
8
+ import { ProjectFile, ProjectVersion } from "@/types/project";
9
  import ChatInterface from "@/components/ChatInterface";
10
  import VersionsPanel from "@/components/VersionsPanel";
11
  import CodeEditor from "@/components/CodeEditor";
12
  import PreviewPanel from "@/components/PreviewPanel";
13
+ import { DiffResult, generateDiff } from "@/services/diffPatch";
 
 
 
 
 
14
  import DiffReviewModal from "@/components/DiffReviewModal";
 
 
15
  import { ModeToggle } from "@/components/theme-toggle";
16
  import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
17
  import JSZip from "jszip";
 
33
 
34
  const [leftTab, setLeftTab] = useState("chat");
35
  const [rightTab, setRightTab] = useState("preview");
 
36
  const { toast } = useToast();
37
  const [leftPanelWidth, setLeftPanelWidth] = useState(40);
38
  const containerRef = useRef<HTMLDivElement>(null);
39
  const isResizingRef = useRef(false);
40
+
41
+ const [versions, setVersions] = useState<ProjectVersion[]>([]);
 
 
 
 
42
  const [pendingModification, setPendingModification] = useState<{ changes: ChangeProposal[]; newMessages: ChatMessage[] } | null>(null);
43
  const [isDiffModalOpen, setIsDiffModalOpen] = useState(false);
44
  const [diffToReview, setDiffToReview] = useState<DiffResult | null>(null);
 
 
45
  const [isAIReady, setIsAIReady] = useState(false);
46
  const [selectedElement, setSelectedElement] = useState<{ tagName: string; description: string } | null>(null);
47
 
 
59
  variant: "destructive",
60
  });
61
  }
62
+ }, [toast]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  useEffect(() => {
65
  const handleMessage = (event: MessageEvent) => {
 
183
  const newMessages = [...messages, userMessage];
184
  setMessages(newMessages);
185
 
186
+ if (files.length > 0) {
187
  handleModificationRequest(messageContent, newMessages);
188
  } else {
189
  handleNewProjectRequest(prompt, newMessages);
 
213
  setActivePreviewFile(newFiles[0].name);
214
  }
215
 
216
+ const newVersion: ProjectVersion = {
217
+ id: new Date().toISOString(),
 
 
218
  code: JSON.stringify(newFiles),
219
+ messages: finalMessages,
220
+ createdAt: new Date().toISOString(),
221
+ };
222
+ setVersions([newVersion]);
223
+
224
  } catch (error: any) {
225
  console.error("Error generating code:", error);
226
  toast({
 
266
  };
267
 
268
  const handleAcceptChanges = async () => {
269
+ if (!pendingModification) return;
270
 
271
  const { changes, newMessages } = pendingModification;
272
  const updatedFiles = [...files];
 
285
  setFiles(updatedFiles);
286
  setMessages(finalMessages);
287
 
288
+ const newVersion: ProjectVersion = {
289
+ id: new Date().toISOString(),
290
+ code: JSON.stringify(updatedFiles),
291
+ messages: finalMessages,
292
+ createdAt: new Date().toISOString(),
293
+ };
294
+ setVersions(prev => [newVersion, ...prev]);
295
 
 
296
  setPendingModification(null);
297
  toast({ title: "Changes Accepted", description: "A new version has been saved." });
298
  };
 
310
  setIsDiffModalOpen(true);
311
  };
312
 
313
+ const handleRestoreVersion = (versionId: string) => {
314
+ const versionToRestore = versions.find(v => v.id === versionId);
315
+ if (versionToRestore) {
316
+ try {
317
+ const parsedFiles = JSON.parse(versionToRestore.code);
318
+ setFiles(parsedFiles);
319
+ setMessages(versionToRestore.messages);
320
+ if (parsedFiles.length > 0) {
321
+ setActiveEditorFile(parsedFiles[0].name);
322
+ setActivePreviewFile(parsedFiles[0].name);
323
+ }
324
  toast({ title: "Version Restored", description: "The prototype has been restored to a previous version." });
325
+ } catch (error) {
326
+ toast({ title: "Error", description: "Failed to parse version data.", variant: "destructive" });
327
  }
 
 
328
  }
329
  };
330
 
331
+ const handleStartOver = () => {
332
+ setPrompt("");
333
+ setMessages([]);
334
+ setIsLoading(false);
335
+ setFiles([]);
336
+ setActiveEditorFile("");
337
+ setActivePreviewFile("");
338
+ setVersions([]);
 
 
 
 
 
 
 
 
339
  setPendingModification(null);
340
+ setSelectedElement(null);
341
+ toast({ title: "Started Over", description: "The builder has been reset." });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  };
343
 
344
  const exportProjectAsZip = () => {
 
351
  zip.file(file.name, file.code);
352
  });
353
  zip.generateAsync({ type: "blob" }).then(content => {
354
+ saveAs(content, `project.zip`);
355
  });
356
  toast({ title: "Project exported", description: "Your project has been downloaded as a zip file." });
357
  };
 
363
  return;
364
  }
365
 
 
366
  const blob = new Blob([fileToOpen.code], { type: 'text/html' });
367
  const url = URL.createObjectURL(blob);
368
  window.open(url, '_blank');
 
411
  }
412
  };
413
 
 
 
 
 
414
  return (
415
  <div className="flex flex-col h-screen bg-background text-foreground">
416
  <header className="border-b bg-card shadow-sm">
 
433
  </div>
434
  )}
435
  <ModeToggle />
436
+ <Button size="sm" onClick={handleStartOver} variant="outline">
437
+ <RefreshCw className="h-4 w-4 mr-1" /> Start Over
 
438
  </Button>
439
  </div>
440
  </div>
 
450
  </div>
451
  <div className="flex-1 overflow-hidden">
452
  {leftTab === 'chat' && <ChatInterface prompt={prompt} setPrompt={setPrompt} messages={messages} isLoading={isLoading} onSubmit={handleSubmit} pendingModification={pendingModification} onAccept={handleAcceptChanges} onReject={handleRejectChanges} onReview={handleReviewChanges} selectedElement={selectedElement} onClearSelectedElement={() => setSelectedElement(null)} />}
453
+ {leftTab === 'versions' && <VersionsPanel versions={versions} onRestoreVersion={handleRestoreVersion} />}
454
  </div>
455
  </div>
456
 
 
473
  {rightTab === 'preview' && <PreviewPanel code={getPreviewCode()} ref={iframeRef} className={isSelectingElement ? "cursor-crosshair" : ""} />}
474
  {rightTab === 'code' && <CodeEditor files={files} activeFile={activeEditorFile} onFileChange={handleFileChange} onFileSelect={setActiveEditorFile} onExport={exportProjectAsZip} />}
475
  </div>
 
476
  </div>
477
  </div>
478
  <DiffReviewModal isOpen={isDiffModalOpen} onClose={() => setIsDiffModalOpen(false)} diffResult={diffToReview} onApply={handleAcceptChanges} />
 
 
 
 
 
 
 
 
479
  </div>
480
  );
481
  };
src/pages/Landing.tsx DELETED
@@ -1,288 +0,0 @@
1
- "use client"
2
-
3
- import { Button } from "@/components/ui/button"
4
- import { Badge } from "@/components/ui/badge"
5
- import { Code, Zap, Users, ArrowRight } from "lucide-react"
6
- import { useNavigate } from "react-router-dom"
7
- import { useAuth } from "@/hooks/useAuth"
8
- import { motion } from "framer-motion"
9
- import AnimatedBackground from "@/components/AnimatedBackground"
10
- import { useEffect } from "react"
11
-
12
- const Landing = () => {
13
- const navigate = useNavigate()
14
- const { user, loading } = useAuth()
15
-
16
- // Redirect logged-in users to dashboard
17
- useEffect(() => {
18
- if (user && !loading) {
19
- navigate('/dashboard')
20
- }
21
- }, [user, loading, navigate])
22
-
23
- const handleGetStarted = () => {
24
- if (user) {
25
- navigate('/dashboard')
26
- } else {
27
- navigate('/auth')
28
- }
29
- }
30
-
31
- // Show loading state while checking auth
32
- if (loading) {
33
- return (
34
- <div className="min-h-screen flex items-center justify-center bg-background">
35
- <div className="text-center">
36
- <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
37
- <p className="text-muted-foreground">Loading...</p>
38
- </div>
39
- </div>
40
- )
41
- }
42
-
43
- const fadeInUp = {
44
- initial: { opacity: 0, y: 30 },
45
- animate: { opacity: 1, y: 0 },
46
- }
47
-
48
- return (
49
- <div className="min-h-screen text-gray-100 overflow-x-hidden">
50
- <AnimatedBackground />
51
-
52
- {/* Navigation */}
53
- <motion.nav
54
- initial={{ y: -100, opacity: 0 }}
55
- animate={{ y: 0, opacity: 1 }}
56
- transition={{ duration: 0.8, ease: "easeOut" }}
57
- className="sticky top-0 z-50 border-b border-gray-800 bg-gray-900/80 backdrop-blur-xl"
58
- >
59
- <div className="container mx-auto px-4 py-3 flex items-center justify-between">
60
- <motion.div
61
- className="flex items-center space-x-2"
62
- whileHover={{ scale: 1.05 }}
63
- whileTap={{ scale: 0.95 }}
64
- >
65
- <div className="w-10 h-10 flex items-center justify-center">
66
- <img src="/front-pilot.svg" alt="FrontPilot Logo" className="w-10 h-10" />
67
- </div>
68
- <div className="flex items-center">
69
- <span className="font-bold text-2xl bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400">
70
- FrontPilot
71
- </span>
72
- <span className="ml-2 text-[0.6rem] bg-yellow-500/20 text-yellow-500 font-bold px-1.5 py-0.5 rounded-full backdrop-blur-sm border border-yellow-500/30">BETA</span>
73
- </div>
74
- </motion.div>
75
- <div className="flex items-center space-x-3">
76
- <Button
77
- variant="ghost"
78
- onClick={() => navigate('/auth')}
79
- className="text-gray-300 hover:text-white hover:bg-gray-800 transition-all"
80
- >
81
- Sign In
82
- </Button>
83
- <motion.div
84
- whileHover={{ scale: 1.05 }}
85
- whileTap={{ scale: 0.95 }}
86
- >
87
- <Button
88
- onClick={handleGetStarted}
89
- className="bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-600 text-lg py-6 px-8 shadow-xl hover:shadow-2xl"
90
- >
91
- Start Building Free
92
- </Button>
93
- </motion.div>
94
- </div>
95
- </div>
96
- </motion.nav>
97
-
98
- {/* Hero Section */}
99
- <section className="relative py-20 md:py-32 overflow-hidden">
100
- <div className="container mx-auto px-4 text-center max-w-5xl relative z-10">
101
- <motion.div
102
- variants={fadeInUp}
103
- initial="initial"
104
- animate="animate"
105
- transition={{ duration: 0.8, ease: "easeOut" }}
106
- >
107
- <Badge className="mb-6 bg-gray-800 text-blue-400 border-gray-700 px-4 py-1 text-sm">
108
- <Zap className="h-3.5 w-3.5 mr-1.5" />
109
- AI-Powered Development
110
- </Badge>
111
- </motion.div>
112
-
113
- <motion.h1
114
- variants={fadeInUp}
115
- initial="initial"
116
- animate="animate"
117
- transition={{ duration: 0.8, ease: "easeOut", delay: 0.1 }}
118
- className="text-5xl md:text-7xl font-extrabold text-white mb-6 leading-tight"
119
- >
120
- Build Websites with <span className="bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">Front Pilot</span>
121
- </motion.h1>
122
-
123
- <motion.p
124
- variants={fadeInUp}
125
- initial="initial"
126
- animate="animate"
127
- transition={{ duration: 0.8, ease: "easeOut", delay: 0.2 }}
128
- className="text-xl md:text-2xl text-gray-300 mb-10 max-w-3xl mx-auto"
129
- >
130
- Create stunning websites in seconds with our AI-powered prototyping tool.
131
- No coding required - just describe what you want and watch it come to life.
132
- </motion.p>
133
-
134
- <motion.div
135
- variants={fadeInUp}
136
- initial="initial"
137
- animate="animate"
138
- transition={{ duration: 0.8, ease: "easeOut", delay: 0.3 }}
139
- className="flex justify-center"
140
- >
141
- <motion.div
142
- whileHover={{ scale: 1.05 }}
143
- whileTap={{ scale: 0.95 }}
144
- >
145
- <Button
146
- size="lg"
147
- onClick={handleGetStarted}
148
- className="bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-600 text-lg py-6 px-8 shadow-xl hover:shadow-2xl"
149
- >
150
- Start Building Free
151
- </Button>
152
- </motion.div>
153
- </motion.div>
154
- </div>
155
- </section>
156
-
157
- {/* Features Section */}
158
- <section className="py-28 relative">
159
- <div className="container mx-auto px-4">
160
- <motion.div
161
- initial={{ opacity: 0 }}
162
- whileInView={{ opacity: 1 }}
163
- viewport={{ once: true, amount: 0.3 }}
164
- transition={{ duration: 0.7 }}
165
- className="text-center mb-20"
166
- >
167
- <h2 className="text-4xl md:text-5xl font-bold text-white mb-6">
168
- Powerful Features
169
- </h2>
170
- <p className="text-xl text-gray-300 max-w-2xl mx-auto">
171
- Everything you need to create amazing websites without writing code
172
- </p>
173
- </motion.div>
174
-
175
- <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
176
- {[
177
- {
178
- icon: Code,
179
- title: "AI-Powered Creation",
180
- description: "Describe your website in plain English and our AI will generate a fully functional prototype in seconds.",
181
- delay: 0.1,
182
- upcoming: false
183
- },
184
- {
185
- icon: Zap,
186
- title: "Instant Prototyping",
187
- description: "Get a working prototype immediately. No more waiting for development cycles or design iterations.",
188
- delay: 0.2,
189
- upcoming: false
190
- },
191
- {
192
- icon: Users,
193
- title: "Team Collaboration",
194
- description: "Share projects with your team, get feedback, and iterate together in real-time.",
195
- delay: 0.3,
196
- upcoming: true
197
- }
198
- ].map((feature, index) => (
199
- <motion.div
200
- key={index}
201
- initial={{ opacity: 0, y: 50 }}
202
- whileInView={{ opacity: 1, y: 0 }}
203
- viewport={{ once: true, amount: 0.3 }}
204
- transition={{ duration: 0.5, delay: feature.delay }}
205
- whileHover={{ y: -10 }}
206
- className="relative bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-2xl p-6 overflow-hidden"
207
- >
208
- <div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-purple-500/5 opacity-0 transition-opacity duration-300 hover:opacity-100" />
209
- <div className="relative z-10">
210
- <div className="mb-4 inline-flex h-12 w-12 items-center justify-center rounded-lg bg-blue-500/10">
211
- <feature.icon className="h-6 w-6 text-blue-400" />
212
- </div>
213
- <div className="flex items-center mb-2">
214
- <h3 className="text-xl font-bold text-white">{feature.title}</h3>
215
- {feature.upcoming && (
216
- <Badge className="ml-2 text-xs bg-orange-500/20 text-orange-300 border border-orange-500/30">
217
- UPCOMING
218
- </Badge>
219
- )}
220
- </div>
221
- <p className="text-gray-300">{feature.description}</p>
222
- </div>
223
- </motion.div>
224
- ))}
225
- </div>
226
- </div>
227
- </section>
228
-
229
- {/* CTA Section */}
230
- <section className="py-28 relative overflow-hidden">
231
- <div className="container mx-auto px-4 text-center relative z-10">
232
- <motion.h2
233
- initial={{ opacity: 0, y: 30 }}
234
- whileInView={{ opacity: 1, y: 0 }}
235
- viewport={{ once: true, amount: 0.3 }}
236
- transition={{ duration: 0.7 }}
237
- className="text-4xl md:text-5xl font-bold text-white mb-6"
238
- >
239
- Ready to build your next website?
240
- </motion.h2>
241
- <motion.p
242
- initial={{ opacity: 0, y: 30 }}
243
- whileInView={{ opacity: 1, y: 0 }}
244
- viewport={{ once: true, amount: 0.3 }}
245
- transition={{ duration: 0.7, delay: 0.1 }}
246
- className="text-xl text-gray-300 mb-10 max-w-2xl mx-auto"
247
- >
248
- Join thousands of creators who are building amazing websites with FrontPilot
249
- </motion.p>
250
- <motion.div
251
- initial={{ opacity: 0, y: 30 }}
252
- whileInView={{ opacity: 1, y: 0 }}
253
- viewport={{ once: true, amount: 0.3 }}
254
- transition={{ duration: 0.7, delay: 0.2 }}
255
- whileHover={{ scale: 1.05 }}
256
- whileTap={{ scale: 0.95 }}
257
- >
258
- <Button
259
- size="lg"
260
- onClick={handleGetStarted}
261
- className="bg-white text-gray-900 hover:bg-gray-100 text-lg py-6 px-10 shadow-2xl hover:shadow-3xl"
262
- >
263
- Get Started Free
264
- </Button>
265
- </motion.div>
266
- </div>
267
- </section>
268
-
269
- {/* Footer */}
270
- <footer className="py-12 bg-gray-900/50 border-t border-gray-800">
271
- <div className="container mx-auto px-4 text-center">
272
- <div className="flex items-center justify-center space-x-3 mb-6">
273
- <div className="w-10 h-10 flex items-center justify-center">
274
- <img src="/front-pilot.svg" alt="FrontPilot Logo" className="w-10 h-10" />
275
- </div>
276
- <span className="font-bold text-2xl text-white">FrontPilot</span>
277
- </div>
278
- <p className="mb-6 max-w-lg mx-auto text-gray-400">AI-powered rapid prototyping for modern web development</p>
279
- <div className="text-sm text-gray-500">
280
- &copy; {new Date().getFullYear()} Halopai Inc. All rights reserved.
281
- </div>
282
- </div>
283
- </footer>
284
- </div>
285
- )
286
- }
287
-
288
- export default Landing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/VerifyEmail.tsx DELETED
@@ -1,159 +0,0 @@
1
- "use client"
2
-
3
- import { useState, useEffect } from "react"
4
- import { Button } from "@/components/ui/button"
5
- import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
6
- import { useNavigate } from "react-router-dom"
7
- import { useAuth } from "@/hooks/useAuth"
8
- import { motion } from "framer-motion"
9
- import AnimatedBackground from "@/components/AnimatedBackground"
10
- import { Badge } from "@/components/ui/badge"
11
- import { Mail, CheckCircle, RefreshCw } from "lucide-react"
12
-
13
- const VerifyEmail = () => {
14
- const [isVerifying, setIsVerifying] = useState(false)
15
- const [verificationStatus, setVerificationStatus] = useState<"pending" | "verified" | "error">("pending")
16
- const navigate = useNavigate()
17
- const { user, signOut } = useAuth()
18
-
19
- useEffect(() => {
20
- // If user is already verified, redirect to dashboard
21
- if (user && user.email_confirmed_at) {
22
- navigate('/dashboard')
23
- }
24
- }, [user, navigate])
25
-
26
- const handleResendEmail = async () => {
27
- setIsVerifying(true)
28
- try {
29
- // In a real app, you would call a function to resend verification email
30
- // For now, we'll just simulate it
31
- await new Promise(resolve => setTimeout(resolve, 1500))
32
- setVerificationStatus("pending")
33
- } catch (error) {
34
- setVerificationStatus("error")
35
- } finally {
36
- setIsVerifying(false)
37
- }
38
- }
39
-
40
- const handleSignOut = async () => {
41
- await signOut()
42
- navigate('/auth')
43
- }
44
-
45
- return (
46
- <div className="min-h-screen w-full flex items-center justify-center p-4 relative overflow-hidden">
47
- <AnimatedBackground />
48
-
49
- <motion.div
50
- initial={{ opacity: 0, y: 20 }}
51
- animate={{ opacity: 1, y: 0 }}
52
- transition={{ duration: 0.5 }}
53
- className="w-full max-w-md z-10"
54
- >
55
- <Card className="shadow-2xl backdrop-blur-xl border border-gray-800 bg-gray-900/80">
56
- <CardHeader className="text-center pb-6">
57
- <motion.div
58
- className="flex justify-center mb-4"
59
- whileHover={{ scale: 1.1 }}
60
- whileTap={{ scale: 0.9 }}
61
- >
62
- <div className="w-16 h-16 flex items-center justify-center">
63
- <img src="/front-pilot.svg" alt="FrontPilot Logo" className="w-16 h-16" />
64
- </div>
65
- </motion.div>
66
- <div className="flex items-center justify-center">
67
- <CardTitle className="text-3xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400">
68
- Verify Your Email
69
- </CardTitle>
70
- <span className="ml-3 text-[0.6rem] bg-yellow-500/20 text-yellow-500 font-bold px-1.5 py-0.5 rounded-full backdrop-blur-sm border border-yellow-500/30">BETA</span>
71
- </div>
72
- <CardDescription className="text-base text-gray-400">
73
- Please check your email for a verification link
74
- </CardDescription>
75
- </CardHeader>
76
-
77
- <CardContent className="space-y-6">
78
- <div className="text-center">
79
- <div className="mx-auto w-20 h-20 bg-blue-500/10 rounded-full flex items-center justify-center mb-4">
80
- <Mail className="h-10 w-10 text-blue-400" />
81
- </div>
82
- <p className="text-gray-300 mb-4">
83
- We've sent a verification email to <span className="font-medium text-white">{user?.email}</span>
84
- </p>
85
- <p className="text-sm text-gray-400">
86
- Please click the link in the email to verify your account and activate your FrontPilot access.
87
- </p>
88
- </div>
89
-
90
- <div className="bg-gray-800/50 border border-gray-700 rounded-lg p-4">
91
- <h3 className="font-medium text-gray-200 mb-2 flex items-center">
92
- <CheckCircle className="h-4 w-4 mr-2 text-green-400" />
93
- Didn't receive the email?
94
- </h3>
95
- <ul className="text-sm text-gray-400 space-y-1">
96
- <li>• Check your spam or junk folder</li>
97
- <li>• Wait a few minutes for delivery</li>
98
- <li>• Ensure you entered the correct email address</li>
99
- </ul>
100
- </div>
101
- </CardContent>
102
-
103
- <CardFooter className="flex flex-col pt-4">
104
- <motion.div
105
- whileHover={{ scale: 1.02 }}
106
- whileTap={{ scale: 0.98 }}
107
- className="w-full"
108
- >
109
- <Button
110
- onClick={() => window.location.reload()}
111
- className="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-600 py-6 text-lg shadow-lg hover:shadow-xl"
112
- >
113
- I've Verified My Email - Continue
114
- </Button>
115
- </motion.div>
116
-
117
- <motion.div
118
- whileHover={{ scale: 1.02 }}
119
- whileTap={{ scale: 0.98 }}
120
- className="mt-4 w-full"
121
- >
122
- <Button
123
- variant="outline"
124
- onClick={handleResendEmail}
125
- disabled={isVerifying}
126
- className="w-full border-gray-600 text-gray-300 hover:bg-gray-800 hover:text-white hover:border-gray-500 py-5"
127
- >
128
- {isVerifying ? (
129
- <>
130
- <RefreshCw className="h-4 w-4 mr-2 animate-spin" />
131
- Sending...
132
- </>
133
- ) : (
134
- "Resend Verification Email"
135
- )}
136
- </Button>
137
- </motion.div>
138
-
139
- <motion.div
140
- whileHover={{ scale: 1.05 }}
141
- whileTap={{ scale: 0.9 }}
142
- className="mt-4 w-full"
143
- >
144
- <Button
145
- variant="ghost"
146
- onClick={handleSignOut}
147
- className="w-full text-gray-400 hover:text-white hover:bg-gray-800/50 py-5"
148
- >
149
- Sign Out
150
- </Button>
151
- </motion.div>
152
- </CardFooter>
153
- </Card>
154
- </motion.div>
155
- </div>
156
- )
157
- }
158
-
159
- export default VerifyEmail
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/types/project.ts CHANGED
@@ -3,28 +3,14 @@ export interface ProjectFile {
3
  code: string;
4
  }
5
 
6
- export interface Project {
7
- id: string;
8
- userId: string;
9
- name: string;
10
- status: 'pending' | 'completed' | 'failed';
11
- prompt: string;
12
- code: string;
13
- createdAt: Date;
14
- updatedAt: Date;
15
- messages: ChatMessage[];
16
  }
17
 
18
  export interface ProjectVersion {
19
  id: string;
20
- projectId: string;
21
- code: string;
22
  messages: ChatMessage[];
23
  createdAt: string;
24
- // Removed 'name' property as it doesn't exist in the database schema
25
- }
26
-
27
- export interface ChatMessage {
28
- role: 'user' | 'assistant';
29
- content: string;
30
  }
 
3
  code: string;
4
  }
5
 
6
+ export interface ChatMessage {
7
+ role: 'user' | 'assistant';
8
+ content: string;
 
 
 
 
 
 
 
9
  }
10
 
11
  export interface ProjectVersion {
12
  id: string;
13
+ code: string; // This will be a JSON string of ProjectFile[]
 
14
  messages: ChatMessage[];
15
  createdAt: string;
 
 
 
 
 
 
16
  }