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
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:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user