fix(content): implement state refresh on update to prevent data loss on tab switch

This commit is contained in:
2026-01-20 15:35:33 +00:00
parent eacd572124
commit c2fc5efc02
4 changed files with 336 additions and 144 deletions

View File

@@ -39,21 +39,25 @@ interface ContentAsset {
// --- SUB-COMPONENTS ---
function SEOPlanner({ project, setLoading }: { project: ContentProject, setLoading: (b: boolean) => void }) {
function SEOPlanner({ project, setLoading, onUpdate }: { project: ContentProject, setLoading: (b: boolean) => void, onUpdate: () => void }) {
const [keywords, setKeywords] = useState<string[]>(project.seo_strategy?.seed_keywords || []);
const generateKeywords = async () => {
setLoading(true);
try {
// FIX: Relative path
const res = await fetch('api/seo_brainstorming', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ projectId: project.id })
});
const data = await res.json();
setKeywords(data.keywords || []);
} catch (err) { console.error(err); }
if (data.error) {
alert(`Error: ${data.error}`);
} else {
setKeywords(data.keywords || []);
onUpdate();
}
} catch (err) { console.error(err); alert("Network Error"); }
setLoading(false);
};
@@ -90,24 +94,25 @@ function SEOPlanner({ project, setLoading }: { project: ContentProject, setLoadi
);
}
function WebsiteBuilder({ project, setLoading }: { project: ContentProject, setLoading: (b: boolean) => void }) {
function WebsiteBuilder({ project, setLoading, onUpdate }: { project: ContentProject, setLoading: (b: boolean) => void, onUpdate: () => void }) {
const [sections, setSections] = useState<ContentAsset[]>(project.assets || []);
const [editingContent, setEditingContent] = useState<{ [key: string]: string }>({});
useEffect(() => {
const newEditing: { [key: string]: string } = {};
if (sections) {
sections.forEach(s => {
// When project updates (e.g. via onUpdate->Parent Refresh), update local sections
if (project.assets) {
setSections(project.assets);
const newEditing: { [key: string]: string } = {};
project.assets.forEach(s => {
newEditing[s.section_key] = s.content;
});
setEditingContent(newEditing);
}
setEditingContent(newEditing);
}, [sections]);
}, [project.assets]);
const generateSection = async (key: string) => {
setLoading(true);
try {
// FIX: Relative path
const res = await fetch('api/generate_section', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -118,11 +123,14 @@ function WebsiteBuilder({ project, setLoading }: { project: ContentProject, setL
})
});
const data = await res.json();
setSections(prev => {
const other = prev.filter(s => s.section_key !== key);
return [...other, { id: Date.now(), section_key: key, content: data.content, status: 'draft' }];
});
} catch (err) { console.error(err); }
if (data.error) {
alert(`Error: ${data.error}`);
return;
}
onUpdate(); // Refresh parent to get fresh data from DB
} catch (err) { console.error(err); alert("Network Error"); }
setLoading(false);
};
@@ -136,7 +144,6 @@ function WebsiteBuilder({ project, setLoading }: { project: ContentProject, setL
setLoading(true);
try {
// FIX: Relative path
await fetch('api/generate_section', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -146,8 +153,9 @@ function WebsiteBuilder({ project, setLoading }: { project: ContentProject, setL
manualContent: content
})
});
onUpdate(); // Refresh parent
alert("Saved successfully!");
} catch (err) { console.error(err); }
} catch (err) { console.error(err); alert("Network Error"); }
setLoading(false);
};
@@ -234,7 +242,7 @@ function WebsiteBuilder({ project, setLoading }: { project: ContentProject, setL
);
}
function ProjectDashboard({ project, onBack, setLoading }: { project: ContentProject, onBack: () => void, setLoading: (b: boolean) => void }) {
function ProjectDashboard({ project, onBack, setLoading, onRefresh }: { project: ContentProject, onBack: () => void, setLoading: (b: boolean) => void, onRefresh: () => void }) {
const [activeTab, setActiveTab] = useState<'SEO' | 'WEBSITE' | 'SOCIAL'>('SEO');
return (
@@ -280,8 +288,8 @@ function ProjectDashboard({ project, onBack, setLoading }: { project: ContentPro
{/* Tab Content */}
<div className="mt-8 pt-8 border-t border-slate-700">
{activeTab === 'SEO' && <SEOPlanner project={project} setLoading={setLoading} />}
{activeTab === 'WEBSITE' && <WebsiteBuilder project={project} setLoading={setLoading} />}
{activeTab === 'SEO' && <SEOPlanner project={project} setLoading={setLoading} onUpdate={onRefresh} />}
{activeTab === 'WEBSITE' && <WebsiteBuilder project={project} setLoading={setLoading} onUpdate={onRefresh} />}
{activeTab === 'SOCIAL' && (
<div className="text-center py-20 bg-slate-900/30 rounded-2xl border border-slate-700 border-dashed">
<Edit3 size={48} className="mx-auto text-slate-700 mb-4" />
@@ -310,7 +318,6 @@ export default function App() {
const fetchContentProjects = async () => {
setLoading(true);
try {
// FIX: Relative path
const res = await fetch('api/list_content_projects', { method: 'POST', body: '{}', headers: {'Content-Type': 'application/json'} });
const data = await res.json();
setContentProjects(data.projects || []);
@@ -321,7 +328,6 @@ export default function App() {
const fetchGtmProjects = async () => {
setLoading(true);
try {
// FIX: Relative path
const res = await fetch('api/list_gtm_projects', { method: 'POST', body: '{}', headers: {'Content-Type': 'application/json'} });
const data = await res.json();
setGtmProjects(data.projects || []);
@@ -333,37 +339,50 @@ export default function App() {
const handleImport = async (gtmId: string) => {
setLoading(true);
try {
// FIX: Relative path
const res = await fetch('api/import_project', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ gtmProjectId: gtmId })
});
const data = await res.json();
if (data.id) {
if (data.error) {
alert(`Import Error: ${data.error}`);
} else if (data.id) {
await fetchContentProjects();
setView('LIST');
}
} catch (err) { console.error(err); }
} catch (err) { console.error(err); alert("Network Error"); }
setLoading(false);
};
const loadProject = async (id: number) => {
setLoading(true);
try {
// FIX: Relative path
const res = await fetch('api/load_project', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ projectId: id })
});
const data = await res.json();
setSelectedProject(data);
setView('DETAILS');
} catch (err) { console.error(err); }
if (data.error) {
alert(`Load Error: ${data.error}`);
} else {
setSelectedProject(data);
setView('DETAILS');
}
} catch (err) { console.error(err); alert("Network Error"); }
setLoading(false);
};
// Wrapper for refreshing data inside Dashboard
const handleRefreshProject = async () => {
if (selectedProject) {
await loadProject(selectedProject.id);
}
};
return (
<div className="min-h-screen bg-slate-900 text-slate-100 font-sans selection:bg-blue-500/30">
{/* Header */}
@@ -501,6 +520,7 @@ export default function App() {
project={selectedProject}
onBack={() => setView('LIST')}
setLoading={setLoading}
onRefresh={handleRefreshProject}
/>
)}
</main>