frontend changes

This commit is contained in:
Chandini 2025-09-15 12:45:52 +05:30
parent f0cc077b63
commit 952f260b5c
8 changed files with 169 additions and 47 deletions

10
package-lock.json generated
View File

@ -12,7 +12,6 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@next/font": "^14.2.15",
"@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-context-menu": "^2.2.16",
@ -948,15 +947,6 @@
"fast-glob": "3.3.1" "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": { "node_modules/@next/swc-darwin-arm64": {
"version": "15.4.6", "version": "15.4.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.6.tgz", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.6.tgz",

View File

@ -13,7 +13,6 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@next/font": "^14.2.15",
"@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-context-menu": "^2.2.16",

View File

@ -11,6 +11,7 @@ import { ArrowLeft, Plus, Edit, Trash2 } from 'lucide-react'
import { DatabaseTemplate, TemplateFeature } from '@/lib/template-service' import { DatabaseTemplate, TemplateFeature } from '@/lib/template-service'
import { AICustomFeatureCreator } from '@/components/ai/AICustomFeatureCreator' import { AICustomFeatureCreator } from '@/components/ai/AICustomFeatureCreator'
import { getApiUrl } from '@/config/backend' import { getApiUrl } from '@/config/backend'
import { getAccessToken } from '@/components/apis/authApiClients'
interface AdminFeatureSelectionProps { interface AdminFeatureSelectionProps {
template: { template: {
@ -31,17 +32,42 @@ interface AdminFeatureSelectionProps {
export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectionProps) { export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectionProps) {
// Admin template service functions using admin API endpoints // Admin template service functions using admin API endpoints
const fetchFeatures = async (templateId: string): Promise<TemplateFeature[]> => { const fetchFeatures = async (templateId: string): Promise<TemplateFeature[]> => {
const response = await fetch(getApiUrl(`api/templates/${templateId}/features`)) const token = getAccessToken()
const headers: Record<string, string> = { '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') if (!response.ok) throw new Error('Failed to fetch features')
const data = await response.json() const data = await response.json()
console.log('🔍 Raw API response for features:', data)
// Handle different response structures // 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<TemplateFeature>) => { const createFeature = async (templateId: string, feature: Partial<TemplateFeature>) => {
const token = getAccessToken()
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const response = await fetch(getApiUrl('api/features'), { const response = await fetch(getApiUrl('api/features'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers,
body: JSON.stringify({ ...feature, template_id: templateId }) body: JSON.stringify({ ...feature, template_id: templateId })
}) })
if (!response.ok) throw new Error('Failed to create feature') 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<TemplateFeature>) => { const updateFeature = async (templateId: string, featureId: string, feature: Partial<TemplateFeature>) => {
const response = await fetch(getApiUrl(`api/templates/${templateId}/features/${featureId}`), { const token = getAccessToken()
const headers: Record<string, string> = { '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', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers,
body: JSON.stringify(feature) 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 deleteFeature = async (templateId: string, featureId: string) => {
const response = await fetch(getApiUrl(`api/templates/${templateId}/features/${featureId}`), { const token = getAccessToken()
method: 'DELETE' const headers: Record<string, string> = { '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') if (!response.ok) throw new Error('Failed to delete feature')
} }
const bulkCreateFeatures = async (templateId: string, features: Partial<TemplateFeature>[]) => { const bulkCreateFeatures = async (templateId: string, features: Partial<TemplateFeature>[]) => {
const response = await fetch(getApiUrl(`api/templates/${templateId}/features/bulk`), { const token = getAccessToken()
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const response = await fetch(getApiUrl(`api/admin/templates/${templateId}/features/bulk`), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers,
body: JSON.stringify({ features }) body: JSON.stringify({ features })
}) })
if (!response.ok) throw new Error('Failed to create 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<Set<string>>(new Set()) const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [showAIModal, setShowAIModal] = useState(false) const [showAIModal, setShowAIModal] = useState(false)
const [editingFeature, setEditingFeature] = useState<TemplateFeature | null>(null) const [editingFeature, setEditingFeature] = useState<TemplateFeature | null>(null)
const [expandedDescriptions, setExpandedDescriptions] = useState<Set<string>>(new Set())
const load = async () => { const load = async () => {
try { 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 <p>{description}</p>
}
return (
<div>
<p>
{isExpanded ? description : `${description.substring(0, maxLength)}...`}
</p>
<Button
variant="ghost"
size="sm"
onClick={() => toggleDescription(feature.id)}
className="p-0 h-auto text-orange-400 hover:text-orange-300 hover:bg-transparent mt-1"
>
{isExpanded ? 'Show less' : 'Show more'}
</Button>
</div>
)
}
if (loading) { if (loading) {
return <div className="text-center py-20 text-white/60">Loading features...</div> return <div className="text-center py-20 text-white/60">Loading features...</div>
} }
@ -239,7 +332,13 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
size="sm" size="sm"
variant="outline" variant="outline"
className="border-blue-500 text-blue-300 hover:bg-blue-500/10" 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);
}}
> >
<Edit className="h-3 w-3" /> <Edit className="h-3 w-3" />
</Button> </Button>
@ -255,7 +354,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-white/80 text-sm space-y-2"> <CardContent className="text-white/80 text-sm space-y-2">
<p>{f.description || 'No description provided.'}</p> <TruncatedDescription feature={f} />
<div className="flex gap-2 text-xs"> <div className="flex gap-2 text-xs">
<Badge variant="outline" className="bg-white/5 border-white/10">{f.feature_type}</Badge> <Badge variant="outline" className="bg-white/5 border-white/10">{f.feature_type}</Badge>
<Badge variant="outline" className="bg-white/5 border-white/10">{f.complexity}</Badge> <Badge variant="outline" className="bg-white/5 border-white/10">{f.complexity}</Badge>
@ -271,11 +370,11 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
<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 essential features.</p> <p className="text-xl text-white/60 max-w-3xl mx-auto">add essential features.</p>
</div> </div>
{section('Essential Features', essentialFeatures)} {section('Essential Features', essentialFeatures)}
{section('Suggested Features', suggestedFeatures)} {/* {section('Suggested Features', suggestedFeatures)} */}
<div className="bg-white/5 border border-white/10 rounded-xl p-4 space-y-3"> <div className="bg-white/5 border border-white/10 rounded-xl p-4 space-y-3">
<h3 className="text-white font-semibold">Add Essential Feature</h3> <h3 className="text-white font-semibold">Add Essential Feature</h3>
@ -297,15 +396,19 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
projectType={template.type || template.title} projectType={template.type || template.title}
editingFeature={editingFeature as any} editingFeature={editingFeature as any}
onAdd={async (payload) => { onAdd={async (payload) => {
await handleUpdate(editingFeature, { console.log('🔍 Payload from AICustomFeatureCreator:', payload);
const updateData = {
name: payload.name, name: payload.name,
description: payload.description, description: payload.description,
complexity: payload.complexity, complexity: payload.complexity,
// Map AI form structure to backend fields // Map AI form structure to backend fields
business_rules: payload.business_rules, 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, logic_rules: payload.logic_rules,
} as any) };
console.log('🔍 Update data being passed to handleUpdate:', updateData);
await handleUpdate(editingFeature, updateData)
setEditingFeature(null) setEditingFeature(null)
}} }}
onClose={() => setEditingFeature(null)} onClose={() => setEditingFeature(null)}

View File

@ -74,25 +74,40 @@ export function AICustomFeatureCreator({
console.log('📋 Found business_rules field:', editingFeature.business_rules) console.log('📋 Found business_rules field:', editingFeature.business_rules)
businessRules = Array.isArray(editingFeature.business_rules) businessRules = Array.isArray(editingFeature.business_rules)
? 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 // Then try additional_business_rules from feature_business_rules table
else if ((editingFeature as any).additional_business_rules) { else if ((editingFeature as any).additional_business_rules) {
console.log('📋 Found additional_business_rules field:', (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) businessRules = Array.isArray((editingFeature as any).additional_business_rules)
? (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) console.log('📋 Parsed business rules:', businessRules)
if (businessRules && Array.isArray(businessRules) && businessRules.length > 0) { if (businessRules && Array.isArray(businessRules) && businessRules.length > 0) {
const requirements = businessRules.map((rule: any) => ({ const requirements = businessRules.map((rule: any) => ({
text: rule.requirement || rule.text || '', text: rule.requirement || rule.text || rule.name || `Requirement`,
rules: rule.rules || [] rules: Array.isArray(rule.rules) ? rule.rules : (rule.rules ? [rule.rules] : [])
})) }))
console.log('📋 Mapped requirements:', requirements) console.log('📋 Mapped requirements:', requirements)
return requirements return requirements.length > 0 ? requirements : [{ text: '', rules: [] }]
} }
} catch (error) { } catch (error) {
console.error('Error parsing business rules:', error) console.error('Error parsing business rules:', error)

View File

@ -26,17 +26,26 @@ export const getAccessToken = () => {
hasToken: !!token, hasToken: !!token,
tokenLength: token?.length || 0, tokenLength: token?.length || 0,
tokenStart: token?.substring(0, 20) + '...' || 'No token', tokenStart: token?.substring(0, 20) + '...' || 'No token',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString()
localStorageToken: token,
moduleToken: accessToken
}); });
if (token) { if (token) {
accessToken = token; // Update the module variable // Validate token format (basic JWT structure check)
console.log('🔐 [getAccessToken] Updated module token from localStorage'); const parts = token.split('.');
return token; // Return the fresh token from localStorage 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 { } else {
console.log('🔐 [getAccessToken] No token found in localStorage'); 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) => { const addAuthTokenInterceptor = (client: typeof authApiClient) => {
client.interceptors.request.use( client.interceptors.request.use(
(config) => { (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 = config.headers || {};
config.headers.Authorization = `Bearer ${accessToken}`; config.headers.Authorization = `Bearer ${freshToken}`;
} }
return config; return config;
}, },
@ -104,9 +115,11 @@ const addTokenRefreshInterceptor = (client: typeof authApiClient) => {
if (error.response?.status === 401 && !originalRequest._retry && !isRefreshEndpoint) { if (error.response?.status === 401 && !originalRequest._retry && !isRefreshEndpoint) {
originalRequest._retry = true; originalRequest._retry = true;
try { try {
if (refreshToken) { // Always get fresh refresh token from localStorage
const freshRefreshToken = getRefreshToken();
if (freshRefreshToken) {
const response = await client.post('/api/auth/refresh', { const response = await client.post('/api/auth/refresh', {
refreshToken: refreshToken refreshToken: freshRefreshToken
}); });
const { accessToken: newAccessToken, refreshToken: newRefreshToken } = response.data.data.tokens; const { accessToken: newAccessToken, refreshToken: newRefreshToken } = response.data.data.tokens;
setTokens(newAccessToken, newRefreshToken); setTokens(newAccessToken, newRefreshToken);

View File

@ -4,7 +4,7 @@
*/ */
// 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.11:8000'; export const BACKEND_URL = 'http://192.168.1.25:8000';
// export const BACKEND_URL = 'https://backend.codenuk.com'; // export const BACKEND_URL = 'https://backend.codenuk.com';

View File

@ -350,7 +350,7 @@ export const adminApi = {
technical_requirements?: Record<string, unknown>; technical_requirements?: Record<string, unknown>;
feature_type?: string; feature_type?: string;
}): Promise<AdminFeature> => { }): Promise<AdminFeature> => {
const response = await apiCall<AdminFeature>(`/api/templates/${templateId}/features/${featureId}`, { const response = await apiCall<AdminFeature>(`/api/templates/${templateId}`, {
method: 'PUT', method: 'PUT',
body: JSON.stringify(updateData), body: JSON.stringify(updateData),
}); });

View File

@ -36,6 +36,8 @@ export interface TemplateFeature {
created_by_user: boolean created_by_user: boolean
created_at: string created_at: string
updated_at: string updated_at: string
business_rules?: any
technical_requirements?: any
} }
export type CreateFeaturePayload = Partial<TemplateFeature> & { export type CreateFeaturePayload = Partial<TemplateFeature> & {