fix(content): implement state refresh on update to prevent data loss on tab switch
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user