This commit is contained in:
tejas.prakash 2025-08-21 15:28:43 +05:30
parent de360a574e
commit 1022b3df3a
4 changed files with 116 additions and 89 deletions

View File

@ -203,7 +203,7 @@ export function EditTemplateForm({ template, onSubmit, onCancel }: EditTemplateF
type="button" type="button"
variant="outline" variant="outline"
onClick={onCancel} onClick={onCancel}
className="border-white/20 text-white hover:bg-white/10" className="border-white/20 text-white hover:bg-white/10 cursor-pointer"
disabled={loading} disabled={loading}
> >
<X className="mr-2 h-4 w-4" /> <X className="mr-2 h-4 w-4" />
@ -211,7 +211,7 @@ export function EditTemplateForm({ template, onSubmit, onCancel }: EditTemplateF
</Button> </Button>
<Button <Button
type="submit" type="submit"
className="bg-orange-500 hover:bg-orange-400 text-black font-semibold" className="bg-orange-500 hover:bg-orange-400 text-black font-semibold cursor-pointer"
disabled={loading} disabled={loading}
> >
<Save className="mr-2 h-4 w-4" /> <Save className="mr-2 h-4 w-4" />

View File

@ -44,28 +44,46 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
const [editingTemplate, setEditingTemplate] = useState<DatabaseTemplate | null>(null) const [editingTemplate, setEditingTemplate] = useState<DatabaseTemplate | null>(null)
const [deletingTemplate, setDeletingTemplate] = useState<DatabaseTemplate | null>(null) const [deletingTemplate, setDeletingTemplate] = useState<DatabaseTemplate | null>(null)
const [deleteLoading, setDeleteLoading] = useState(false) const [deleteLoading, setDeleteLoading] = useState(false)
const [databaseTemplates, setDatabaseTemplates] = useState<Template[]>([])
const [templatesLoading, setTemplatesLoading] = useState(true)
const { templates: dbTemplates, loading, error, getTemplatesForUI, createTemplate, updateTemplate, deleteTemplate } = useTemplates() const { templates: dbTemplates, loading, error, getTemplatesForUI, createTemplate, updateTemplate, deleteTemplate } = useTemplates()
// Get templates from database and convert to UI format // Load templates with features when dbTemplates changes
const databaseTemplates = getTemplatesForUI() useEffect(() => {
const loadTemplates = async () => {
if (Object.keys(dbTemplates).length > 0) {
setTemplatesLoading(true)
try {
const templatesWithFeatures = await getTemplatesForUI()
setDatabaseTemplates(templatesWithFeatures)
} catch (error) {
console.error('Error loading templates with features:', error)
setDatabaseTemplates([])
} finally {
setTemplatesLoading(false)
}
}
}
loadTemplates()
}, [dbTemplates])
// Fallback static templates if database is empty or loading // Fallback static templates if database is empty or loading
const fallbackTemplates: Template[] = [ const fallbackTemplates: Template[] = [
{ id: "marketing-website", title: "Marketing Website", description: "Professional marketing site with CMS and lead generation", category: "Marketing", features: ["Content Management", "Contact Forms", "SEO Optimization", "Analytics Integration"], complexity: 2, timeEstimate: "1-2 weeks", techStack: ["Next.js", "Sanity CMS", "Tailwind CSS", "Vercel"], popularity: 95, lastUpdated: "2024-01-15" }, { id: "marketing-website", title: "Marketing Website", description: "Professional marketing site with CMS and lead generation", category: "Marketing", features: ["Content Management", "Contact Forms", "SEO Optimization", "Analytics Integration"], complexity: 2, timeEstimate: "1-2 weeks", techStack: ["Next.js", "Sanity CMS", "Tailwind CSS", "Vercel"], popularity: 95, lastUpdated: "2024-01-15" },
{ id: "saas-platform", title: "SaaS Platform", description: "Complete SaaS application with user management, billing, and analytics", category: "SaaS Platform", features: ["User Authentication", "Payment Processing", "Analytics Integration", "API Management"], complexity: 5, timeEstimate: "4-6 weeks", techStack: ["Next.js", "PostgreSQL", "Stripe", "NextAuth.js"], popularity: 92, lastUpdated: "2024-01-15" }, { id: "saas-platform", title: "SaaS Platform", description: "Complete SaaS application with user management, billing, and analytics", category: "SaaS Platform", features: ["User Authentication", "Payment Processing", "Analytics Integration", "API Management"], complexity: 5, timeEstimate: "4-6 weeks", techStack: ["Next.js", "PostgreSQL", "Stripe", "NextAuth.js"], popularity: 92, lastUpdated: "2024-01-15" },
] ]
// Use database templates if available, otherwise fallback // Use database templates if available, otherwise fallback
const templates: Template[] = databaseTemplates.length > 0 ? databaseTemplates : fallbackTemplates const templates: Template[] = databaseTemplates.length > 0 ? databaseTemplates : fallbackTemplates
// Generate dynamic categories from templates // Generate dynamic categories from templates
const getCategories = () => { const getCategories = () => {
const categoryMap = new Map<string, { name: string; icon: any; count: number }>() const categoryMap = new Map<string, { name: string; icon: any; count: number }>()
// Add "All Templates" category // Add "All Templates" category
categoryMap.set("all", { name: "All Templates", icon: Globe, count: templates.length }) categoryMap.set("all", { name: "All Templates", icon: Globe, count: templates.length })
// Add categories from templates // Add categories from templates
templates.forEach(template => { templates.forEach(template => {
if (!categoryMap.has(template.category)) { if (!categoryMap.has(template.category)) {
@ -78,21 +96,21 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
} else if (template.category.toLowerCase().includes('food') || template.category.toLowerCase().includes('delivery')) { } else if (template.category.toLowerCase().includes('food') || template.category.toLowerCase().includes('delivery')) {
icon = Users icon = Users
} }
categoryMap.set(template.category, { categoryMap.set(template.category, {
name: template.category, name: template.category,
icon, icon,
count: templates.filter(t => t.category === template.category).length count: templates.filter(t => t.category === template.category).length
}) })
} }
}) })
return Array.from(categoryMap.entries()).map(([id, data]) => ({ return Array.from(categoryMap.entries()).map(([id, data]) => ({
id, id,
...data ...data
})) }))
} }
const categories = getCategories() const categories = getCategories()
const filteredTemplates = templates.filter((template) => { const filteredTemplates = templates.filter((template) => {
@ -136,7 +154,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
const handleDeleteTemplate = async () => { const handleDeleteTemplate = async () => {
if (!deletingTemplate) return if (!deletingTemplate) return
setDeleteLoading(true) setDeleteLoading(true)
try { try {
await deleteTemplate(deletingTemplate.id) await deleteTemplate(deletingTemplate.id)
@ -149,12 +167,12 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
} }
// Show loading state // Show loading state
if (loading) { if (loading || templatesLoading) {
return ( return (
<div className="max-w-7xl mx-auto space-y-6"> <div className="max-w-7xl mx-auto space-y-6">
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<h1 className="text-4xl font-bold text-white">Loading Templates...</h1> <h1 className="text-4xl font-bold text-white">Loading Templates...</h1>
<p className="text-xl text-white/60">Fetching templates from database</p> <p className="text-xl text-white/60">Fetching templates and features from database</p>
</div> </div>
<div className="flex justify-center"> <div className="flex justify-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-orange-500"></div> <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-orange-500"></div>
@ -189,7 +207,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
<h1 className="text-4xl font-bold text-white">Create Custom Template</h1> <h1 className="text-4xl font-bold text-white">Create Custom Template</h1>
<p className="text-xl text-white/60">Design your own project template</p> <p className="text-xl text-white/60">Design your own project template</p>
</div> </div>
<CustomTemplateForm <CustomTemplateForm
onSubmit={handleCreateTemplate} onSubmit={handleCreateTemplate}
onCancel={() => setShowCustomForm(false)} onCancel={() => setShowCustomForm(false)}
/> />
@ -205,7 +223,7 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
<h1 className="text-4xl font-bold text-white">Edit Template</h1> <h1 className="text-4xl font-bold text-white">Edit Template</h1>
<p className="text-xl text-white/60">Modify your template settings</p> <p className="text-xl text-white/60">Modify your template settings</p>
</div> </div>
<EditTemplateForm <EditTemplateForm
template={editingTemplate} template={editingTemplate}
onSubmit={handleUpdateTemplate} onSubmit={handleUpdateTemplate}
onCancel={() => setEditingTemplate(null)} onCancel={() => setEditingTemplate(null)}
@ -268,11 +286,10 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
<button <button
key={category.id} key={category.id}
onClick={() => setSelectedCategory(category.id)} onClick={() => setSelectedCategory(category.id)}
className={`flex items-center space-x-3 px-6 py-3 rounded-xl border transition-all cursor-pointer ${ className={`flex items-center space-x-3 px-6 py-3 rounded-xl border transition-all cursor-pointer ${active
active
? "bg-orange-500 text-black border-orange-500" ? "bg-orange-500 text-black border-orange-500"
: "bg-white/5 text-white/80 border-white/10 hover:bg-white/10" : "bg-white/5 text-white/80 border-white/10 hover:bg-white/10"
}`} }`}
> >
<div className={`p-2 rounded-lg ${active ? "bg-white/10" : "bg-white/10"}`}> <div className={`p-2 rounded-lg ${active ? "bg-white/10" : "bg-white/10"}`}>
<Icon className="h-5 w-5" /> <Icon className="h-5 w-5" />
@ -372,25 +389,6 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
)} )}
</div> </div>
</div> </div>
<div>
<h4 className="font-semibold text-sm text-white mb-1 flex items-center">
<span className="w-2 h-2 bg-emerald-500 rounded-full mr-2"></span>
Tech Stack
</h4>
<div className="flex flex-wrap gap-1">
{template.techStack.slice(0, 3).map((tech, index) => (
<Badge key={index} variant="secondary" className="text-xs bg-white/10 text-white px-3 py-1 rounded-full font-medium">
{tech}
</Badge>
))}
{template.techStack.length > 3 && (
<Badge variant="secondary" className="text-xs bg-white/10 text-white px-3 py-1 rounded-full">
+{template.techStack.length - 3}
</Badge>
)}
</div>
</div>
</div> </div>
</div> </div>
@ -591,17 +589,24 @@ function FeatureSelectionStep({
<div className="grid grid-cols-1 md:grid-cols-3 gap-3"> <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<Input placeholder="Feature name" value={newFeature.name} onChange={(e) => setNewFeature({ ...newFeature, name: e.target.value })} className="bg-white/5 border-white/10 text-white placeholder:text-white/40" /> <Input placeholder="Feature name" value={newFeature.name} onChange={(e) => setNewFeature({ ...newFeature, name: e.target.value })} className="bg-white/5 border-white/10 text-white placeholder:text-white/40" />
<Input placeholder="Description (optional)" value={newFeature.description} onChange={(e) => setNewFeature({ ...newFeature, description: e.target.value })} className="bg-white/5 border-white/10 text-white placeholder:text-white/40" /> <Input placeholder="Description (optional)" value={newFeature.description} onChange={(e) => setNewFeature({ ...newFeature, description: e.target.value })} className="bg-white/5 border-white/10 text-white placeholder:text-white/40" />
<select value={newFeature.complexity} onChange={(e) => setNewFeature({ ...newFeature, complexity: e.target.value as any })} className="bg-white/5 border-white/10 text-white rounded-md px-3"> <select
<option value="low">low</option> value={newFeature.complexity}
<option value="medium">medium</option> onChange={(e) =>
<option value="high">high</option> setNewFeature({ ...newFeature, complexity: e.target.value as any })
}
className="bg-gray-900 border border-white text-white rounded-md px-3 py-2 focus:ring-2 focus:ring-white focus:border-white cursor-pointer"
>
<option className="bg-gray-900 text-white" value="low">low</option>
<option className="bg-gray-900 text-white" value="medium">medium</option>
<option className="bg-gray-900 text-white" value="high">high</option>
</select> </select>
</div> </div>
<div className="flex justify-between items-center gap-3 flex-wrap"> <div className="flex justify-between items-center gap-3 flex-wrap">
<div className="text-white/60 text-sm">Custom features are saved to this template.</div> <div className="text-white/60 text-sm">Custom features are saved to this template.</div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="outline" onClick={() => setShowAIModal(true)} className="border-white/20 text-white hover:bg-white/10">Analyze with AI</Button> <Button variant="outline" onClick={() => setShowAIModal(true)} className="border-white/20 text-white hover:bg-white/10 cursor-pointer">Analyze with AI</Button>
<Button onClick={handleAddCustom} className="bg-orange-500 hover:bg-orange-400 text-black">Add Feature</Button> <Button onClick={handleAddCustom} className="bg-orange-500 hover:bg-orange-400 text-black cursor-pointer">Add Feature</Button>
</div> </div>
</div> </div>
</div> </div>
@ -618,11 +623,11 @@ function FeatureSelectionStep({
<div className="text-center py-4"> <div className="text-center py-4">
<div className="space-x-4"> <div className="space-x-4">
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button> <Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10 cursor-pointer">Back</Button>
<Button <Button
onClick={() => onNext(features.filter(f => selectedIds.has(f.id)))} onClick={() => onNext(features.filter(f => selectedIds.has(f.id)))}
disabled={selectedIds.size < 3} disabled={selectedIds.size < 3}
className={`bg-orange-500 hover:bg-orange-400 text-black font-semibold py-2 rounded-lg shadow ${selectedIds.size < 3 ? 'opacity-50 cursor-not-allowed' : ''}`} className={`bg-orange-500 hover:bg-orange-400 text-black cursor-pointer font-semibold py-2 rounded-lg shadow ${selectedIds.size < 3 ? 'opacity-50 cursor-not-allowed' : ''}`}
> >
Continue Continue
</Button> </Button>
@ -730,8 +735,8 @@ function BusinessQuestionsStep({
(selected as any[]).some((f: any) => f.complexity === 'high') (selected as any[]).some((f: any) => f.complexity === 'high')
? 'high' ? 'high'
: (selected as any[]).some((f: any) => f.complexity === 'medium') : (selected as any[]).some((f: any) => f.complexity === 'medium')
? 'medium' ? 'medium'
: 'low', : 'low',
logicRules: (selected as any[]).flatMap((f: any) => f.logicRules || []), logicRules: (selected as any[]).flatMap((f: any) => f.logicRules || []),
} }
@ -826,18 +831,18 @@ function TechStackSummaryStep({
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div> <div>
<h4 className="font-semibold text-white/80 mb-2">Core Feature</h4> <h4 className="font-semibold text-white/80 mb-2">Core Feature</h4>
<div className="bg-blue-500/10 rounded-lg p-4"> <div className="bg-orange-500 rounded-lg p-4">
<div className="font-medium text-blue-200">{functional.feature_name}</div> <div className="font-medium text-white-200">{functional.feature_name}</div>
<div className="text-blue-300 text-sm mt-1">{functional.description}</div> <div className="text-white-300 text-sm mt-1">{functional.description}</div>
</div> </div>
</div> </div>
<div> <div>
<h4 className="font-semibold text-white/80 mb-2">Complexity Level</h4> {/* <h4 className="font-semibold text-white/80 mb-2">Complexity Level</h4>
<div className="bg-purple-500/10 rounded-lg p-4"> <div className="rounded-lg p-4">
<span className="inline-block px-3 py-1 rounded-full text-sm font-medium bg-white/10 text-white"> <span className="inline-block px-3 py-1 rounded-full text-sm font-medium bg-white/10 text-white">
{(functional.complexity_level || 'medium').toUpperCase()} {(functional.complexity_level || 'medium').toUpperCase()}
</span> </span>
</div> </div> */}
</div> </div>
</div> </div>
@ -920,8 +925,8 @@ function TechStackSummaryStep({
<div className="text-center py-6"> <div className="text-center py-6">
<div className="space-x-4"> <div className="space-x-4">
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">Back</Button> <Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10 cursor-pointer">Back</Button>
<Button onClick={onGenerate} className="bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-2 rounded-lg font-semibold">Generate Architecture Design </Button> <Button onClick={onGenerate} className="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-6 py-2 rounded-lg font-semibold cursor-pointer">Generate Architecture Design </Button>
</div> </div>
<div className="text-white/60 text-sm mt-2">AI will design complete architecture</div> <div className="text-white/60 text-sm mt-2">AI will design complete architecture</div>
</div> </div>
@ -1007,21 +1012,19 @@ export function MainDashboard() {
<li key={step.id} className="flex items-center"> <li key={step.id} className="flex items-center">
<div className="flex items-center"> <div className="flex items-center">
<div <div
className={`flex items-center justify-center w-10 h-10 rounded-full border-2 transition-all ${ className={`flex items-center justify-center w-10 h-10 rounded-full border-2 transition-all ${currentStep >= step.id
currentStep >= step.id
? "bg-orange-500 border-orange-500 text-white shadow-lg" ? "bg-orange-500 border-orange-500 text-white shadow-lg"
: currentStep === step.id - 1 : currentStep === step.id - 1
? "border-orange-300 text-orange-400" ? "border-orange-300 text-orange-400"
: "border-white/30 text-white/40" : "border-white/30 text-white/40"
}`} }`}
> >
<span className="text-sm font-semibold">{step.id}</span> <span className="text-sm font-semibold">{step.id}</span>
</div> </div>
<div className="ml-4"> <div className="ml-4">
<p <p
className={`text-sm font-semibold ${ className={`text-sm font-semibold ${currentStep >= step.id ? "text-orange-400" : "text-white/60"
currentStep >= step.id ? "text-orange-400" : "text-white/60" }`}
}`}
> >
{step.name} {step.name}
</p> </p>
@ -1152,11 +1155,10 @@ function ArchitectureDesignerStep({ recommendations, onBack }: { recommendations
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id as any)} onClick={() => setActiveTab(tab.id as any)}
className={`py-2 px-1 border-b-2 text-sm transition-colors ${ className={`py-2 px-1 border-b-2 text-sm transition-colors ${activeTab === (tab.id as any)
activeTab === (tab.id as any)
? 'border-orange-400 text-orange-400' ? 'border-orange-400 text-orange-400'
: 'border-transparent text-white/60 hover:text-white/80 hover:border-white/20' : 'border-transparent text-white/60 hover:text-white/80 hover:border-white/20'
}`} }`}
> >
{tab.name} {tab.name}
</button> </button>

View File

@ -60,7 +60,7 @@ export function useTemplates() {
} }
// Convert database templates to the format expected by the UI // Convert database templates to the format expected by the UI
const getTemplatesForUI = () => { const getTemplatesForUI = async () => {
const allTemplates: Array<DatabaseTemplate & { const allTemplates: Array<DatabaseTemplate & {
features: string[] features: string[]
complexity: number complexity: number
@ -70,22 +70,42 @@ export function useTemplates() {
lastUpdated?: string lastUpdated?: string
}> = [] }> = []
Object.entries(templates).forEach(([category, categoryTemplates]) => { for (const [category, categoryTemplates] of Object.entries(templates)) {
categoryTemplates.forEach((template) => { for (const template of categoryTemplates) {
// Convert database template to UI format try {
const uiTemplate = { // Fetch features for this template
...template, const features = await templateService.getFeaturesForTemplate(template.id)
features: [], // Will be populated when template is selected const featureNames = features.map(f => f.name)
complexity: 3, // Default complexity
timeEstimate: "2-4 weeks", // Default time estimate // Convert database template to UI format
techStack: ["Next.js", "PostgreSQL", "Tailwind CSS"], // Default tech stack const uiTemplate = {
popularity: template.avg_rating ? Math.round(template.avg_rating * 20) : 75, // Convert rating to popularity ...template,
lastUpdated: template.updated_at ? new Date(template.updated_at).toISOString().split('T')[0] : undefined, features: featureNames, // Use actual features from API
featureCount: (template as any).feature_count ?? 0 complexity: 3, // Default complexity
timeEstimate: "2-4 weeks", // Default time estimate
techStack: ["Next.js", "PostgreSQL", "Tailwind CSS"], // Default tech stack
popularity: template.avg_rating ? Math.round(template.avg_rating * 20) : 75, // Convert rating to popularity
lastUpdated: template.updated_at ? new Date(template.updated_at).toISOString().split('T')[0] : undefined,
featureCount: featureNames.length
}
allTemplates.push(uiTemplate)
} catch (error) {
console.error(`Error fetching features for template ${template.id}:`, error)
// Fallback with empty features
const uiTemplate = {
...template,
features: [],
complexity: 3,
timeEstimate: "2-4 weeks",
techStack: ["Next.js", "PostgreSQL", "Tailwind CSS"],
popularity: template.avg_rating ? Math.round(template.avg_rating * 20) : 75,
lastUpdated: template.updated_at ? new Date(template.updated_at).toISOString().split('T')[0] : undefined,
featureCount: 0
}
allTemplates.push(uiTemplate)
} }
allTemplates.push(uiTemplate) }
}) }
})
return allTemplates return allTemplates
} }

View File

@ -149,15 +149,20 @@ class TemplateService {
} }
try { try {
const merged = await this.makeRequest<TemplateFeature[]>(`/api/features/templates/${templateId}/merged`) const merged = await this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`)
return dedupe(merged) return dedupe(merged)
} catch { } catch {
// Fallback to default-only if merged endpoint unsupported // Fallback to default-only if merged endpoint unsupported
const defaults = await this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`) const defaults = await this.makeRequest<TemplateFeature[]>(`/api/features/templates/${templateId}/merged`)
return dedupe(defaults) return dedupe(defaults)
} }
} }
// Default-only features for template (does not include custom/merged)
// async getDefaultFeaturesForTemplate(templateId: string): Promise<TemplateFeature[]> {
// return this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`)
// }
async searchFeatures(searchTerm: string, templateId?: string): Promise<TemplateFeature[]> { async searchFeatures(searchTerm: string, templateId?: string): Promise<TemplateFeature[]> {
const q = encodeURIComponent(searchTerm) const q = encodeURIComponent(searchTerm)
const extra = templateId ? `&template_id=${encodeURIComponent(templateId)}` : '' const extra = templateId ? `&template_id=${encodeURIComponent(templateId)}` : ''