Updated
This commit is contained in:
parent
de360a574e
commit
1022b3df3a
@ -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" />
|
||||||
|
|||||||
@ -44,11 +44,29 @@ 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[] = [
|
||||||
@ -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>
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)}` : ''
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user