Updated
This commit is contained in:
parent
de360a574e
commit
1022b3df3a
@ -203,7 +203,7 @@ export function EditTemplateForm({ template, onSubmit, onCancel }: EditTemplateF
|
||||
type="button"
|
||||
variant="outline"
|
||||
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}
|
||||
>
|
||||
<X className="mr-2 h-4 w-4" />
|
||||
@ -211,7 +211,7 @@ export function EditTemplateForm({ template, onSubmit, onCancel }: EditTemplateF
|
||||
</Button>
|
||||
<Button
|
||||
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}
|
||||
>
|
||||
<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 [deletingTemplate, setDeletingTemplate] = useState<DatabaseTemplate | null>(null)
|
||||
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()
|
||||
|
||||
// Get templates from database and convert to UI format
|
||||
const databaseTemplates = getTemplatesForUI()
|
||||
// Load templates with features when dbTemplates changes
|
||||
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
|
||||
const fallbackTemplates: Template[] = [
|
||||
@ -149,12 +167,12 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
if (loading) {
|
||||
if (loading || templatesLoading) {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto space-y-6">
|
||||
<div className="text-center space-y-3">
|
||||
<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 className="flex justify-center">
|
||||
<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
|
||||
key={category.id}
|
||||
onClick={() => setSelectedCategory(category.id)}
|
||||
className={`flex items-center space-x-3 px-6 py-3 rounded-xl border transition-all cursor-pointer ${
|
||||
active
|
||||
className={`flex items-center space-x-3 px-6 py-3 rounded-xl border transition-all cursor-pointer ${active
|
||||
? "bg-orange-500 text-black border-orange-500"
|
||||
: "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"}`}>
|
||||
<Icon className="h-5 w-5" />
|
||||
@ -372,25 +389,6 @@ function TemplateSelectionStep({ onNext }: { onNext: (template: Template) => voi
|
||||
)}
|
||||
</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>
|
||||
|
||||
@ -591,17 +589,24 @@ function FeatureSelectionStep({
|
||||
<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="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">
|
||||
<option value="low">low</option>
|
||||
<option value="medium">medium</option>
|
||||
<option value="high">high</option>
|
||||
<select
|
||||
value={newFeature.complexity}
|
||||
onChange={(e) =>
|
||||
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>
|
||||
|
||||
</div>
|
||||
<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="flex gap-2">
|
||||
<Button variant="outline" onClick={() => setShowAIModal(true)} className="border-white/20 text-white hover:bg-white/10">Analyze with AI</Button>
|
||||
<Button onClick={handleAddCustom} className="bg-orange-500 hover:bg-orange-400 text-black">Add Feature</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 cursor-pointer">Add Feature</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -618,11 +623,11 @@ function FeatureSelectionStep({
|
||||
|
||||
<div className="text-center py-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={() => onNext(features.filter(f => selectedIds.has(f.id)))}
|
||||
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
|
||||
</Button>
|
||||
@ -730,8 +735,8 @@ function BusinessQuestionsStep({
|
||||
(selected as any[]).some((f: any) => f.complexity === 'high')
|
||||
? 'high'
|
||||
: (selected as any[]).some((f: any) => f.complexity === 'medium')
|
||||
? 'medium'
|
||||
: 'low',
|
||||
? 'medium'
|
||||
: 'low',
|
||||
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>
|
||||
<h4 className="font-semibold text-white/80 mb-2">Core Feature</h4>
|
||||
<div className="bg-blue-500/10 rounded-lg p-4">
|
||||
<div className="font-medium text-blue-200">{functional.feature_name}</div>
|
||||
<div className="text-blue-300 text-sm mt-1">{functional.description}</div>
|
||||
<div className="bg-orange-500 rounded-lg p-4">
|
||||
<div className="font-medium text-white-200">{functional.feature_name}</div>
|
||||
<div className="text-white-300 text-sm mt-1">{functional.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-semibold text-white/80 mb-2">Complexity Level</h4>
|
||||
<div className="bg-purple-500/10 rounded-lg p-4">
|
||||
{/* <h4 className="font-semibold text-white/80 mb-2">Complexity Level</h4>
|
||||
<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">
|
||||
{(functional.complexity_level || 'medium').toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -920,8 +925,8 @@ function TechStackSummaryStep({
|
||||
|
||||
<div className="text-center py-6">
|
||||
<div className="space-x-4">
|
||||
<Button variant="outline" onClick={onBack} className="border-white/20 text-white hover:bg-white/10">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 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-orange-500 to-orange-600 text-white px-6 py-2 rounded-lg font-semibold cursor-pointer">Generate Architecture Design →</Button>
|
||||
</div>
|
||||
<div className="text-white/60 text-sm mt-2">AI will design complete architecture</div>
|
||||
</div>
|
||||
@ -1007,21 +1012,19 @@ export function MainDashboard() {
|
||||
<li key={step.id} className="flex items-center">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`flex items-center justify-center w-10 h-10 rounded-full border-2 transition-all ${
|
||||
currentStep >= step.id
|
||||
className={`flex items-center justify-center w-10 h-10 rounded-full border-2 transition-all ${currentStep >= step.id
|
||||
? "bg-orange-500 border-orange-500 text-white shadow-lg"
|
||||
: currentStep === step.id - 1
|
||||
? "border-orange-300 text-orange-400"
|
||||
: "border-white/30 text-white/40"
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<span className="text-sm font-semibold">{step.id}</span>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p
|
||||
className={`text-sm font-semibold ${
|
||||
currentStep >= step.id ? "text-orange-400" : "text-white/60"
|
||||
}`}
|
||||
className={`text-sm font-semibold ${currentStep >= step.id ? "text-orange-400" : "text-white/60"
|
||||
}`}
|
||||
>
|
||||
{step.name}
|
||||
</p>
|
||||
@ -1152,11 +1155,10 @@ function ArchitectureDesignerStep({ recommendations, onBack }: { recommendations
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id as any)}
|
||||
className={`py-2 px-1 border-b-2 text-sm transition-colors ${
|
||||
activeTab === (tab.id as any)
|
||||
className={`py-2 px-1 border-b-2 text-sm transition-colors ${activeTab === (tab.id as any)
|
||||
? 'border-orange-400 text-orange-400'
|
||||
: 'border-transparent text-white/60 hover:text-white/80 hover:border-white/20'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{tab.name}
|
||||
</button>
|
||||
|
||||
@ -60,7 +60,7 @@ export function useTemplates() {
|
||||
}
|
||||
|
||||
// Convert database templates to the format expected by the UI
|
||||
const getTemplatesForUI = () => {
|
||||
const getTemplatesForUI = async () => {
|
||||
const allTemplates: Array<DatabaseTemplate & {
|
||||
features: string[]
|
||||
complexity: number
|
||||
@ -70,22 +70,42 @@ export function useTemplates() {
|
||||
lastUpdated?: string
|
||||
}> = []
|
||||
|
||||
Object.entries(templates).forEach(([category, categoryTemplates]) => {
|
||||
categoryTemplates.forEach((template) => {
|
||||
// Convert database template to UI format
|
||||
const uiTemplate = {
|
||||
...template,
|
||||
features: [], // Will be populated when template is selected
|
||||
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: (template as any).feature_count ?? 0
|
||||
for (const [category, categoryTemplates] of Object.entries(templates)) {
|
||||
for (const template of categoryTemplates) {
|
||||
try {
|
||||
// Fetch features for this template
|
||||
const features = await templateService.getFeaturesForTemplate(template.id)
|
||||
const featureNames = features.map(f => f.name)
|
||||
|
||||
// Convert database template to UI format
|
||||
const uiTemplate = {
|
||||
...template,
|
||||
features: featureNames, // Use actual features from API
|
||||
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
|
||||
}
|
||||
|
||||
@ -149,15 +149,20 @@ class TemplateService {
|
||||
}
|
||||
|
||||
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)
|
||||
} catch {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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[]> {
|
||||
const q = encodeURIComponent(searchTerm)
|
||||
const extra = templateId ? `&template_id=${encodeURIComponent(templateId)}` : ''
|
||||
|
||||
Loading…
Reference in New Issue
Block a user