frontend changes
This commit is contained in:
parent
f0cc077b63
commit
952f260b5c
10
package-lock.json
generated
10
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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()
|
||||||
if (!response.ok) throw new Error('Failed to fetch features')
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||||
const data = await response.json()
|
if (token) {
|
||||||
// Handle different response structures
|
headers['Authorization'] = `Bearer ${token}`
|
||||||
return Array.isArray(data) ? data : (data.data || data.features || [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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)}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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> & {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user