fix(sdk): Auto-save company profile to SDK context and backend
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 51s
CI/CD / test-python-backend-compliance (push) Successful in 38s
CI/CD / test-python-document-crawler (push) Successful in 27s
CI/CD / test-python-dsms-gateway (push) Successful in 28s
CI/CD / deploy-hetzner (push) Failing after 6s

Profile data was lost when navigating away because it was only saved
to SDK context on explicit button click (Next/Save). Scope data
persisted because it auto-synced on every change.

Added two debounced auto-save mechanisms:
- SDK context sync (500ms) — survives in-app navigation
- Backend save (2s) — survives page reload/session change

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-11 08:33:12 +01:00
parent 091f093e1b
commit 7afbcfd9f5

View File

@@ -1,6 +1,6 @@
'use client'
import React, { useState, useEffect } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import { useSDK } from '@/lib/sdk'
import {
CompanyProfile,
@@ -2469,6 +2469,39 @@ export default function CompanyProfilePage() {
setFormData(prev => ({ ...prev, ...updates }))
}
// ---------------------------------------------------------------------------
// Auto-save: sync formData to SDK context (debounced) so data survives navigation.
// This mirrors the pattern used by compliance-scope/page.tsx.
// ---------------------------------------------------------------------------
const autoSaveRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const initialLoadDone = useRef(false)
useEffect(() => {
// Skip the initial load — only auto-save after user has started editing
if (!initialLoadDone.current) {
// Mark initial load done after first formData update (from backend or SDK state)
if (formData.companyName !== undefined) {
initialLoadDone.current = true
}
return
}
// Debounce: sync to SDK context after 500ms of inactivity
if (autoSaveRef.current) clearTimeout(autoSaveRef.current)
autoSaveRef.current = setTimeout(() => {
// Only sync if there's meaningful data (not just defaults)
const hasData = formData.companyName || (formData.industry && formData.industry.length > 0)
if (hasData) {
setCompanyProfile({ ...formData, isComplete: false, completedAt: null } as CompanyProfile)
}
}, 500)
return () => {
if (autoSaveRef.current) clearTimeout(autoSaveRef.current)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formData])
// Shared payload builder for draft saves and final save (DRY)
const buildProfilePayload = (isComplete: boolean) => ({
project_id: projectId || null,
@@ -2537,7 +2570,39 @@ export default function CompanyProfilePage() {
} : {}),
})
// Auto-save draft to backend (fire-and-forget, non-blocking)
// ---------------------------------------------------------------------------
// Auto-save draft to backend (debounced, 2s after last change)
// ---------------------------------------------------------------------------
const backendAutoSaveRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => {
if (!initialLoadDone.current) return
const hasData = formData.companyName || (formData.industry && formData.industry.length > 0)
if (!hasData) return
if (backendAutoSaveRef.current) clearTimeout(backendAutoSaveRef.current)
backendAutoSaveRef.current = setTimeout(async () => {
try {
await fetch(profileApiUrl(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(buildProfilePayload(false)),
})
setDraftSaveStatus('saved')
if (draftSaveTimerRef.current) clearTimeout(draftSaveTimerRef.current)
draftSaveTimerRef.current = setTimeout(() => setDraftSaveStatus('idle'), 3000)
} catch {
// Silent fail for auto-save — user can still manually save via Next
}
}, 2000)
return () => {
if (backendAutoSaveRef.current) clearTimeout(backendAutoSaveRef.current)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formData])
const [draftSaveStatus, setDraftSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle')
const draftSaveTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)