frontend changes
This commit is contained in:
parent
81ad734f47
commit
8b5c53ef4c
@ -3,7 +3,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 3001",
|
"dev": "next dev -p 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start -p 3001",
|
"start": "next start -p 3001",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
|
|||||||
@ -1,18 +1,11 @@
|
|||||||
import type React from "react"
|
import type React from "react"
|
||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
import { Poppins } from "next/font/google"
|
|
||||||
import { AuthProvider } from "@/contexts/auth-context"
|
import { AuthProvider } from "@/contexts/auth-context"
|
||||||
import { AppLayout } from "@/components/layout/app-layout"
|
import { AppLayout } from "@/components/layout/app-layout"
|
||||||
import { ToastProvider } from "@/components/ui/toast"
|
import { ToastProvider } from "@/components/ui/toast"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
import "@tldraw/tldraw/tldraw.css"
|
import "@tldraw/tldraw/tldraw.css"
|
||||||
|
|
||||||
const poppins = Poppins({
|
|
||||||
subsets: ["latin"],
|
|
||||||
weight: ["300", "400", "500", "600", "700"],
|
|
||||||
variable: "--font-poppins",
|
|
||||||
})
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Codenuk - AI-Powered Project Builder",
|
title: "Codenuk - AI-Powered Project Builder",
|
||||||
description: "Build scalable applications with AI-generated architecture and code",
|
description: "Build scalable applications with AI-generated architecture and code",
|
||||||
@ -27,10 +20,16 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<style>{`
|
<style>{`
|
||||||
html {
|
html {
|
||||||
font-family: ${poppins.style.fontFamily};
|
font-family: 'Poppins', sans-serif;
|
||||||
--font-sans: ${poppins.variable};
|
--font-sans: 'Poppins', sans-serif;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@ -884,6 +884,8 @@ function FeatureSelectionStep({
|
|||||||
}: { template: Template; onNext: (selected: TemplateFeature[]) => void; onBack: () => void }) {
|
}: { template: Template; onNext: (selected: TemplateFeature[]) => void; onBack: () => void }) {
|
||||||
const { fetchFeatures, createFeature, updateFeature, deleteFeature } = useTemplates()
|
const { fetchFeatures, createFeature, updateFeature, deleteFeature } = useTemplates()
|
||||||
const [features, setFeatures] = useState<TemplateFeature[]>([])
|
const [features, setFeatures] = useState<TemplateFeature[]>([])
|
||||||
|
const [essentialFeatures, setEssentialFeatures] = useState<TemplateFeature[]>([])
|
||||||
|
const [customFeatures, setCustomFeatures] = useState<TemplateFeature[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
|
||||||
@ -917,6 +919,9 @@ function FeatureSelectionStep({
|
|||||||
})
|
})
|
||||||
console.log('[FeatureSelectionStep] All features with types:', data.map(f => ({ name: f.name, type: f.feature_type })))
|
console.log('[FeatureSelectionStep] All features with types:', data.map(f => ({ name: f.name, type: f.feature_type })))
|
||||||
setFeatures(data)
|
setFeatures(data)
|
||||||
|
// Separate custom features from essential features
|
||||||
|
setEssentialFeatures(data.filter(f => f.feature_type !== 'custom'))
|
||||||
|
setCustomFeatures(data.filter(f => f.feature_type === 'custom'))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[FeatureSelectionStep] Error loading features:', e)
|
console.error('[FeatureSelectionStep] Error loading features:', e)
|
||||||
setError(e instanceof Error ? e.message : 'Failed to load features')
|
setError(e instanceof Error ? e.message : 'Failed to load features')
|
||||||
@ -1085,10 +1090,10 @@ function FeatureSelectionStep({
|
|||||||
<div className="max-w-7xl mx-auto space-y-8">
|
<div className="max-w-7xl mx-auto space-y-8">
|
||||||
<div className="text-center space-y-4">
|
<div className="text-center space-y-4">
|
||||||
<h1 className="text-4xl font-bold text-white">Select Features for {template.title}</h1>
|
<h1 className="text-4xl font-bold text-white">Select Features for {template.title}</h1>
|
||||||
<p className="text-xl text-white/60 max-w-3xl mx-auto">Choose defaults or add your own custom features.</p>
|
<p className="text-xl text-white/60 max-w-3xl mx-auto">Choose from essential and suggested features.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{features.length > 0 && section('Template Features', features)}
|
{essentialFeatures.length > 0 && section('Essential Features', essentialFeatures)}
|
||||||
|
|
||||||
{/* Add custom feature with AI */}
|
{/* Add custom feature with AI */}
|
||||||
<div className="bg-white/5 border border-white/10 rounded-xl p-6 space-y-4">
|
<div className="bg-white/5 border border-white/10 rounded-xl p-6 space-y-4">
|
||||||
@ -1110,7 +1115,7 @@ function FeatureSelectionStep({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{section('Your Custom Features', custom)}
|
{customFeatures.length > 0 && section('Custom Features', customFeatures)}
|
||||||
|
|
||||||
{(showAIModal || editingFeature) && (
|
{(showAIModal || editingFeature) && (
|
||||||
<AICustomFeatureCreator
|
<AICustomFeatureCreator
|
||||||
@ -1138,7 +1143,7 @@ function FeatureSelectionStep({
|
|||||||
<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 cursor-pointer">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([...essentialFeatures, ...customFeatures].filter(f => selectedIds.has(f.id)))}
|
||||||
disabled={selectedIds.size < 3}
|
disabled={selectedIds.size < 3}
|
||||||
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' : ''}`}
|
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' : ''}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Main backend URL - change this to update all API calls
|
// Main backend URL - change this to update all API calls
|
||||||
export const BACKEND_URL = 'http://192.168.1.20:8000';
|
// export const BACKEND_URL = 'http://192.168.1.31:8000';
|
||||||
// export const BACKEND_URL = 'https://backend.codenuk.com';
|
export const BACKEND_URL = 'https://backend.codenuk.com';
|
||||||
|
|
||||||
|
|
||||||
// Realtime notifications socket URL (Template Manager emits notifications)
|
// Realtime notifications socket URL (Template Manager emits notifications)
|
||||||
|
|||||||
@ -384,56 +384,15 @@ class TemplateService {
|
|||||||
|
|
||||||
// Features API
|
// Features API
|
||||||
async getFeaturesForTemplate(templateId: string): Promise<TemplateFeature[]> {
|
async getFeaturesForTemplate(templateId: string): Promise<TemplateFeature[]> {
|
||||||
// Use merged endpoint to include custom features (returns custom_feature.id for custom items)
|
|
||||||
const dedupe = (items: TemplateFeature[]) => {
|
|
||||||
const byKey = new Map<string, TemplateFeature>()
|
|
||||||
|
|
||||||
const toKey = (f: TemplateFeature) => {
|
|
||||||
const normName = (f.name || '').trim().toLowerCase()
|
|
||||||
// Use normalized name for all features to enable proper deduplication
|
|
||||||
// between template_features and custom_features tables
|
|
||||||
return normName
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefer = (a: TemplateFeature, b: TemplateFeature): TemplateFeature => {
|
|
||||||
// Prefer user-created, then higher usage_count, then newer updated_at
|
|
||||||
const aUser = !!a.created_by_user
|
|
||||||
const bUser = !!b.created_by_user
|
|
||||||
if (aUser !== bUser) return aUser ? a : b
|
|
||||||
const aUsage = typeof a.usage_count === 'number' ? a.usage_count : -1
|
|
||||||
const bUsage = typeof b.usage_count === 'number' ? b.usage_count : -1
|
|
||||||
if (aUsage !== bUsage) return aUsage > bUsage ? a : b
|
|
||||||
const aTime = a.updated_at ? Date.parse(a.updated_at) : 0
|
|
||||||
const bTime = b.updated_at ? Date.parse(b.updated_at) : 0
|
|
||||||
return aTime >= bTime ? a : b
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
const key = toKey(item)
|
|
||||||
const existing = byKey.get(key)
|
|
||||||
if (!existing) {
|
|
||||||
byKey.set(key, item)
|
|
||||||
} else {
|
|
||||||
byKey.set(key, prefer(existing, item))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[getFeaturesForTemplate] Deduplication results:', {
|
|
||||||
originalCount: items.length,
|
|
||||||
deduplicatedCount: byKey.size,
|
|
||||||
duplicatesRemoved: items.length - byKey.size
|
|
||||||
})
|
|
||||||
|
|
||||||
return Array.from(byKey.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const merged = await this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`)
|
const merged = await this.makeRequest<TemplateFeature[]>(`/api/templates/${templateId}/features`)
|
||||||
return dedupe(merged)
|
console.log('[getFeaturesForTemplate] Raw merged features (no deduplication):', merged)
|
||||||
|
return merged
|
||||||
} catch {
|
} catch {
|
||||||
// Fallback to compatible route in features router
|
// Fallback to compatible route in features router
|
||||||
const defaults = await this.makeRequest<TemplateFeature[]>(`/api/features/templates/${templateId}/features`)
|
const defaults = await this.makeRequest<TemplateFeature[]>(`/api/features/templates/${templateId}/features`)
|
||||||
return dedupe(defaults)
|
console.log('[getFeaturesForTemplate] Raw fallback features (no deduplication):', defaults)
|
||||||
|
return defaults
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user