diff --git a/package-lock.json b/package-lock.json index da1aa74..72a0464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", - "@next/font": "^14.2.15", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-context-menu": "^2.2.16", @@ -948,15 +947,6 @@ "fast-glob": "3.3.1" } }, - "node_modules/@next/font": { - "version": "14.2.15", - "resolved": "https://registry.npmjs.org/@next/font/-/font-14.2.15.tgz", - "integrity": "sha512-QopYhBmCDDrNDynbi+ZD1hDZXmQXVFo7TmAFp4DQgO/kogz1OLbQ92hPigJbj572eZ3GaaVxNIyYVn3/eAsehg==", - "license": "MIT", - "peerDependencies": { - "next": "*" - } - }, "node_modules/@next/swc-darwin-arm64": { "version": "15.4.6", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.6.tgz", diff --git a/package.json b/package.json index 05ae0ff..86232ea 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", - "@next/font": "^14.2.15", "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-context-menu": "^2.2.16", diff --git a/src/components/admin/admin-feature-selection.tsx b/src/components/admin/admin-feature-selection.tsx index 440ac0b..59f287d 100644 --- a/src/components/admin/admin-feature-selection.tsx +++ b/src/components/admin/admin-feature-selection.tsx @@ -11,6 +11,7 @@ import { ArrowLeft, Plus, Edit, Trash2 } from 'lucide-react' import { DatabaseTemplate, TemplateFeature } from '@/lib/template-service' import { AICustomFeatureCreator } from '@/components/ai/AICustomFeatureCreator' import { getApiUrl } from '@/config/backend' +import { getAccessToken } from '@/components/apis/authApiClients' interface AdminFeatureSelectionProps { template: { @@ -31,17 +32,42 @@ interface AdminFeatureSelectionProps { export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectionProps) { // Admin template service functions using admin API endpoints const fetchFeatures = async (templateId: string): Promise => { - const response = await fetch(getApiUrl(`api/templates/${templateId}/features`)) + const token = getAccessToken() + const headers: Record = { 'Content-Type': 'application/json' } + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + const response = await fetch(getApiUrl(`api/admin/templates/${templateId}/features`), { + headers + }) if (!response.ok) throw new Error('Failed to fetch features') const data = await response.json() + console.log('🔍 Raw API response for features:', data) // Handle different response structures - return Array.isArray(data) ? data : (data.data || data.features || []) + const features = Array.isArray(data) ? data : (data.data || data.features || []) + console.log('🔍 Processed features with rules:', features.map((f: any) => ({ + id: f.id, + name: f.name, + business_rules: f.business_rules, + technical_requirements: f.technical_requirements + }))) + return features } + // The features list already includes business_rules and technical_requirements + // No need for separate API call since the data is already available + const createFeature = async (templateId: string, feature: Partial) => { + const token = getAccessToken() + const headers: Record = { 'Content-Type': 'application/json' } + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + const response = await fetch(getApiUrl('api/features'), { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers, body: JSON.stringify({ ...feature, template_id: templateId }) }) if (!response.ok) throw new Error('Failed to create feature') @@ -49,26 +75,56 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio } const updateFeature = async (templateId: string, featureId: string, feature: Partial) => { - const response = await fetch(getApiUrl(`api/templates/${templateId}/features/${featureId}`), { + const token = getAccessToken() + const headers: Record = { 'Content-Type': 'application/json' } + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + console.log('🔄 Updating feature:', { templateId, featureId, feature }) + console.log('📤 Sending data to backend:', JSON.stringify(feature, null, 2)) + + const response = await fetch(getApiUrl(`api/admin/templates/${templateId}/features/${featureId}`), { method: 'PUT', - headers: { 'Content-Type': 'application/json' }, + headers, body: JSON.stringify(feature) }) - if (!response.ok) throw new Error('Failed to update feature') - return response.json() + + const responseText = await response.text() + console.log('📥 Backend response:', responseText) + + if (!response.ok) { + console.error('❌ Update failed:', response.status, responseText) + throw new Error(`Failed to update feature: ${responseText}`) + } + + return JSON.parse(responseText) } const deleteFeature = async (templateId: string, featureId: string) => { - const response = await fetch(getApiUrl(`api/templates/${templateId}/features/${featureId}`), { - method: 'DELETE' + const token = getAccessToken() + const headers: Record = { 'Content-Type': 'application/json' } + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + const response = await fetch(getApiUrl(`api/admin/templates/${templateId}/features/${featureId}`), { + method: 'DELETE', + headers }) if (!response.ok) throw new Error('Failed to delete feature') } const bulkCreateFeatures = async (templateId: string, features: Partial[]) => { - const response = await fetch(getApiUrl(`api/templates/${templateId}/features/bulk`), { + const token = getAccessToken() + const headers: Record = { 'Content-Type': 'application/json' } + if (token) { + headers['Authorization'] = `Bearer ${token}` + } + + const response = await fetch(getApiUrl(`api/admin/templates/${templateId}/features/bulk`), { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers, body: JSON.stringify({ features }) }) if (!response.ok) throw new Error('Failed to create features') @@ -86,6 +142,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio const [selectedIds, setSelectedIds] = useState>(new Set()) const [showAIModal, setShowAIModal] = useState(false) const [editingFeature, setEditingFeature] = useState(null) + const [expandedDescriptions, setExpandedDescriptions] = useState>(new Set()) const load = async () => { try { @@ -205,6 +262,42 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio }) } + const toggleDescription = (featureId: string) => { + setExpandedDescriptions((prev) => { + const next = new Set(prev) + if (next.has(featureId)) next.delete(featureId) + else next.add(featureId) + return next + }) + } + + const TruncatedDescription = ({ feature }: { feature: TemplateFeature }) => { + const isExpanded = expandedDescriptions.has(feature.id) + const description = feature.description || 'No description provided.' + const maxLength = 150 + const shouldTruncate = description.length > maxLength + + if (!shouldTruncate) { + return

{description}

+ } + + return ( +
+

+ {isExpanded ? description : `${description.substring(0, maxLength)}...`} +

+ +
+ ) + } + if (loading) { return
Loading features...
} @@ -239,7 +332,13 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio size="sm" variant="outline" className="border-blue-500 text-blue-300 hover:bg-blue-500/10" - onClick={(e) => { e.stopPropagation(); setEditingFeature(f) }} + onClick={(e) => { + e.stopPropagation(); + console.log('🔍 Feature being edited:', f); + console.log('🔍 Business rules:', f.business_rules); + console.log('🔍 Technical requirements:', f.technical_requirements); + setEditingFeature(f); + }} > @@ -255,7 +354,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio -

{f.description || 'No description provided.'}

+
{f.feature_type} {f.complexity} @@ -271,11 +370,11 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio

Select Features for {template.title}

-

Choose defaults or add your own essential features.

+

add essential features.

{section('Essential Features', essentialFeatures)} - {section('Suggested Features', suggestedFeatures)} + {/* {section('Suggested Features', suggestedFeatures)} */}

Add Essential Feature

@@ -297,15 +396,19 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio projectType={template.type || template.title} editingFeature={editingFeature as any} onAdd={async (payload) => { - await handleUpdate(editingFeature, { + console.log('🔍 Payload from AICustomFeatureCreator:', payload); + const updateData = { name: payload.name, description: payload.description, complexity: payload.complexity, // Map AI form structure to backend fields business_rules: payload.business_rules, - // Flatten logic rules to store if backend supports it on update + technical_requirements: payload.logic_rules, + // Also send logic_rules for backward compatibility logic_rules: payload.logic_rules, - } as any) + }; + console.log('🔍 Update data being passed to handleUpdate:', updateData); + await handleUpdate(editingFeature, updateData) setEditingFeature(null) }} onClose={() => setEditingFeature(null)} diff --git a/src/components/ai/AICustomFeatureCreator.tsx b/src/components/ai/AICustomFeatureCreator.tsx index 147ee05..2b2fdca 100644 --- a/src/components/ai/AICustomFeatureCreator.tsx +++ b/src/components/ai/AICustomFeatureCreator.tsx @@ -74,25 +74,40 @@ export function AICustomFeatureCreator({ console.log('📋 Found business_rules field:', editingFeature.business_rules) businessRules = Array.isArray(editingFeature.business_rules) ? editingFeature.business_rules - : JSON.parse(editingFeature.business_rules) + : (typeof editingFeature.business_rules === 'string' ? JSON.parse(editingFeature.business_rules) : editingFeature.business_rules) } // Then try additional_business_rules from feature_business_rules table else if ((editingFeature as any).additional_business_rules) { console.log('📋 Found additional_business_rules field:', (editingFeature as any).additional_business_rules) businessRules = Array.isArray((editingFeature as any).additional_business_rules) ? (editingFeature as any).additional_business_rules - : JSON.parse((editingFeature as any).additional_business_rules) + : (typeof (editingFeature as any).additional_business_rules === 'string' ? JSON.parse((editingFeature as any).additional_business_rules) : (editingFeature as any).additional_business_rules) + } + // Also try technical_requirements field + else if ((editingFeature as any).technical_requirements) { + console.log('📋 Found technical_requirements field:', (editingFeature as any).technical_requirements) + const techReqs = Array.isArray((editingFeature as any).technical_requirements) + ? (editingFeature as any).technical_requirements + : (typeof (editingFeature as any).technical_requirements === 'string' ? JSON.parse((editingFeature as any).technical_requirements) : (editingFeature as any).technical_requirements) + + // Convert technical requirements to business rules format + if (Array.isArray(techReqs)) { + businessRules = techReqs.map((req: string, index: number) => ({ + requirement: `Requirement ${index + 1}`, + rules: [req] + })) + } } console.log('📋 Parsed business rules:', businessRules) if (businessRules && Array.isArray(businessRules) && businessRules.length > 0) { const requirements = businessRules.map((rule: any) => ({ - text: rule.requirement || rule.text || '', - rules: rule.rules || [] + text: rule.requirement || rule.text || rule.name || `Requirement`, + rules: Array.isArray(rule.rules) ? rule.rules : (rule.rules ? [rule.rules] : []) })) console.log('📋 Mapped requirements:', requirements) - return requirements + return requirements.length > 0 ? requirements : [{ text: '', rules: [] }] } } catch (error) { console.error('Error parsing business rules:', error) diff --git a/src/components/apis/authApiClients.tsx b/src/components/apis/authApiClients.tsx index 0cd12a1..4e95edb 100644 --- a/src/components/apis/authApiClients.tsx +++ b/src/components/apis/authApiClients.tsx @@ -26,17 +26,26 @@ export const getAccessToken = () => { hasToken: !!token, tokenLength: token?.length || 0, tokenStart: token?.substring(0, 20) + '...' || 'No token', - timestamp: new Date().toISOString(), - localStorageToken: token, - moduleToken: accessToken + timestamp: new Date().toISOString() }); + if (token) { - accessToken = token; // Update the module variable - console.log('🔐 [getAccessToken] Updated module token from localStorage'); - return token; // Return the fresh token from localStorage + // Validate token format (basic JWT structure check) + const parts = token.split('.'); + if (parts.length !== 3) { + console.warn('🔐 [getAccessToken] Invalid token format, clearing...'); + clearTokens(); + return null; + } + + // Update the module variable for consistency + accessToken = token; + console.log('🔐 [getAccessToken] Valid token found and updated'); + return token; } else { console.log('🔐 [getAccessToken] No token found in localStorage'); - return null; // Return null if no token found + accessToken = null; // Ensure module variable is also null + return null; } }; @@ -84,9 +93,11 @@ export const authApiClient = axios.create({ const addAuthTokenInterceptor = (client: typeof authApiClient) => { client.interceptors.request.use( (config) => { - if (accessToken) { + // Always get fresh token from localStorage instead of using module variable + const freshToken = getAccessToken(); + if (freshToken) { config.headers = config.headers || {}; - config.headers.Authorization = `Bearer ${accessToken}`; + config.headers.Authorization = `Bearer ${freshToken}`; } return config; }, @@ -104,9 +115,11 @@ const addTokenRefreshInterceptor = (client: typeof authApiClient) => { if (error.response?.status === 401 && !originalRequest._retry && !isRefreshEndpoint) { originalRequest._retry = true; try { - if (refreshToken) { + // Always get fresh refresh token from localStorage + const freshRefreshToken = getRefreshToken(); + if (freshRefreshToken) { const response = await client.post('/api/auth/refresh', { - refreshToken: refreshToken + refreshToken: freshRefreshToken }); const { accessToken: newAccessToken, refreshToken: newRefreshToken } = response.data.data.tokens; setTokens(newAccessToken, newRefreshToken); diff --git a/src/config/backend.ts b/src/config/backend.ts index 0d3755f..0fce8d5 100644 --- a/src/config/backend.ts +++ b/src/config/backend.ts @@ -4,7 +4,7 @@ */ // Main backend URL - change this to update all API calls -export const BACKEND_URL = 'http://192.168.1.11:8000'; +export const BACKEND_URL = 'http://192.168.1.25:8000'; // export const BACKEND_URL = 'https://backend.codenuk.com'; diff --git a/src/lib/api/admin.ts b/src/lib/api/admin.ts index 2c7e9e4..045bb13 100644 --- a/src/lib/api/admin.ts +++ b/src/lib/api/admin.ts @@ -350,7 +350,7 @@ export const adminApi = { technical_requirements?: Record; feature_type?: string; }): Promise => { - const response = await apiCall(`/api/templates/${templateId}/features/${featureId}`, { + const response = await apiCall(`/api/templates/${templateId}`, { method: 'PUT', body: JSON.stringify(updateData), }); diff --git a/src/lib/template-service.ts b/src/lib/template-service.ts index 7f79de0..a625576 100644 --- a/src/lib/template-service.ts +++ b/src/lib/template-service.ts @@ -36,6 +36,8 @@ export interface TemplateFeature { created_by_user: boolean created_at: string updated_at: string + business_rules?: any + technical_requirements?: any } export type CreateFeaturePayload = Partial & {