diff --git a/admin-compliance/app/sdk/compliance-scope/page.tsx b/admin-compliance/app/sdk/compliance-scope/page.tsx index e07c211..a3caa14 100644 --- a/admin-compliance/app/sdk/compliance-scope/page.tsx +++ b/admin-compliance/app/sdk/compliance-scope/page.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState, useEffect, useCallback, useMemo } from 'react' +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react' import { useSDK } from '@/lib/sdk' import { StepHeader, STEP_EXPLANATIONS } from '@/components/sdk/StepHeader/StepHeader' import { @@ -53,36 +53,41 @@ export default function ComplianceScopePage() { const [isLoading, setIsLoading] = useState(true) const [isEvaluating, setIsEvaluating] = useState(false) + // Guard against save-loop: tracks whether we're syncing FROM SDK → local state + const syncingFromSdk = useRef(false) + // Regulation assessment state const [applicableRegulations, setApplicableRegulations] = useState([]) const [supervisoryAuthorities, setSupervisoryAuthorities] = useState([]) const [regulationAssessmentLoading, setRegulationAssessmentLoading] = useState(false) - // Load from SDK context first (persisted via State API), then localStorage as fallback. - // Runs ONCE on mount only — empty deps breaks the dispatch→sdkState→setScopeState→dispatch loop. + // Sync from SDK context when it becomes available (handles async loading). + // The SDK context loads state from server/localStorage asynchronously, so + // sdkState.complianceScope may arrive AFTER this page has already mounted. useEffect(() => { - try { - // Priority 1: SDK context (loaded from PostgreSQL via State API) - const ctxScope = sdkState.complianceScope - if (ctxScope && ctxScope.answers?.length > 0) { - setScopeState(ctxScope) - localStorage.setItem(STORAGE_KEY, JSON.stringify(ctxScope)) - } else { - // Priority 2: localStorage fallback + const ctxScope = sdkState.complianceScope + if (ctxScope && ctxScope.answers?.length > 0) { + syncingFromSdk.current = true + setScopeState(ctxScope) + setIsLoading(false) + } else if (isLoading) { + // SDK has no scope data — try localStorage fallback, then give up + try { const stored = localStorage.getItem(STORAGE_KEY) if (stored) { const parsed = JSON.parse(stored) as ComplianceScopeState - setScopeState(parsed) - dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: parsed }) + if (parsed.answers?.length > 0) { + setScopeState(parsed) + dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: parsed }) + } } + } catch (error) { + console.error('Failed to load compliance scope from localStorage:', error) } - } catch (error) { - console.error('Failed to load compliance scope state:', error) - } finally { setIsLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, [sdkState.complianceScope]) // Fetch regulation assessment if decision exists on mount useEffect(() => { @@ -92,9 +97,14 @@ export default function ComplianceScopePage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoading]) - // Save to localStorage and SDK context whenever state changes + // Save to localStorage and SDK context whenever LOCAL state changes (user edits). + // Guarded: don't save empty state, and don't echo back what we just loaded from SDK. useEffect(() => { - if (!isLoading) { + if (!isLoading && scopeState.answers.length > 0) { + if (syncingFromSdk.current) { + syncingFromSdk.current = false + return + } try { localStorage.setItem(STORAGE_KEY, JSON.stringify(scopeState)) dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: scopeState })