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/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",

View File

@ -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",

View File

@ -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<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')
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<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'), {
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<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',
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<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')
}
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',
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<Set<string>>(new Set())
const [showAIModal, setShowAIModal] = useState(false)
const [editingFeature, setEditingFeature] = useState<TemplateFeature | null>(null)
const [expandedDescriptions, setExpandedDescriptions] = useState<Set<string>>(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 <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) {
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"
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);
}}
>
<Edit className="h-3 w-3" />
</Button>
@ -255,7 +354,7 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
</CardTitle>
</CardHeader>
<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">
<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>
@ -271,11 +370,11 @@ export function AdminFeatureSelection({ template, onBack }: AdminFeatureSelectio
<div className="max-w-7xl mx-auto space-y-8">
<div className="text-center space-y-4">
<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>
{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">
<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}
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)}

View File

@ -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)

View File

@ -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);

View File

@ -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';

View File

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

View File

@ -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<TemplateFeature> & {