All four files split into focused sibling modules so every file lands
comfortably under the 300-LOC soft target (hard cap 500):
hooks.ts (474→43) → hooks-core / hooks-dsgvo / hooks-compliance
hooks-rag-security / hooks-ui
dsr-portal.ts (464→129) → dsr-portal-translations / dsr-portal-render
provider.tsx (462→247) → provider-effects / provider-callbacks
sync.ts (435→299) → sync-storage / sync-conflict
Zero behaviour changes. All public APIs remain importable from the
original paths (hooks.ts re-exports every hook, provider.tsx keeps all
named exports, sync.ts preserves StateSyncManager + factory).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
227 lines
6.8 KiB
TypeScript
227 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* useCallback factories extracted from ComplianceProvider.
|
|
*
|
|
* Each function creates and returns a memoised callback so provider.tsx
|
|
* stays under 300 LOC.
|
|
*/
|
|
|
|
import { useCallback, RefObject } from 'react'
|
|
import type {
|
|
SDKState,
|
|
CheckpointStatus,
|
|
UseCaseAssessment,
|
|
Risk,
|
|
Control,
|
|
} from '@breakpilot/compliance-sdk-types'
|
|
import { ComplianceClient, StateSyncManager } from '@breakpilot/compliance-sdk-core'
|
|
import { SDK_STORAGE_KEY } from './provider-context'
|
|
|
|
// =============================================================================
|
|
// CHECKPOINT CALLBACKS
|
|
// =============================================================================
|
|
|
|
export function useValidateCheckpoint(
|
|
state: SDKState,
|
|
enableBackendSync: boolean,
|
|
client: ComplianceClient,
|
|
dispatch: React.Dispatch<{ type: string; payload?: unknown }>
|
|
) {
|
|
return useCallback(
|
|
async (checkpointId: string): Promise<CheckpointStatus> => {
|
|
if (enableBackendSync) {
|
|
try {
|
|
const result = await client.validateCheckpoint(checkpointId, state)
|
|
const status: CheckpointStatus = {
|
|
checkpointId: result.checkpointId,
|
|
passed: result.passed,
|
|
validatedAt: new Date(result.validatedAt),
|
|
validatedBy: result.validatedBy,
|
|
errors: result.errors,
|
|
warnings: result.warnings,
|
|
}
|
|
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
|
|
return status
|
|
} catch {
|
|
// Fall through to local validation
|
|
}
|
|
}
|
|
|
|
const status: CheckpointStatus = {
|
|
checkpointId,
|
|
passed: true,
|
|
validatedAt: new Date(),
|
|
validatedBy: 'SYSTEM',
|
|
errors: [],
|
|
warnings: [],
|
|
}
|
|
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } })
|
|
return status
|
|
},
|
|
[state, enableBackendSync, client, dispatch]
|
|
)
|
|
}
|
|
|
|
export function useOverrideCheckpoint(
|
|
state: SDKState,
|
|
dispatch: React.Dispatch<{ type: string; payload?: unknown }>
|
|
) {
|
|
return useCallback(
|
|
async (checkpointId: string, reason: string): Promise<void> => {
|
|
const existingStatus = state.checkpoints[checkpointId]
|
|
const overriddenStatus: CheckpointStatus = {
|
|
...existingStatus,
|
|
checkpointId,
|
|
passed: true,
|
|
overrideReason: reason,
|
|
overriddenBy: state.userId,
|
|
overriddenAt: new Date(),
|
|
errors: [],
|
|
warnings: existingStatus?.warnings || [],
|
|
}
|
|
dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status: overriddenStatus } })
|
|
},
|
|
[state.checkpoints, state.userId, dispatch]
|
|
)
|
|
}
|
|
|
|
export function useGetCheckpointStatus(state: SDKState) {
|
|
return useCallback(
|
|
(checkpointId: string): CheckpointStatus | undefined => state.checkpoints[checkpointId],
|
|
[state.checkpoints]
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// STATE UPDATE CALLBACKS
|
|
// =============================================================================
|
|
|
|
export function useUpdateUseCase(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) {
|
|
return useCallback(
|
|
(id: string, data: Partial<UseCaseAssessment>) => {
|
|
dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } })
|
|
},
|
|
[dispatch]
|
|
)
|
|
}
|
|
|
|
export function useAddRisk(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) {
|
|
return useCallback(
|
|
(risk: Risk) => {
|
|
dispatch({ type: 'ADD_RISK', payload: risk })
|
|
},
|
|
[dispatch]
|
|
)
|
|
}
|
|
|
|
export function useUpdateControl(dispatch: React.Dispatch<{ type: string; payload?: unknown }>) {
|
|
return useCallback(
|
|
(id: string, data: Partial<Control>) => {
|
|
dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } })
|
|
},
|
|
[dispatch]
|
|
)
|
|
}
|
|
|
|
// =============================================================================
|
|
// PERSISTENCE CALLBACKS
|
|
// =============================================================================
|
|
|
|
export function useSaveState(
|
|
state: SDKState,
|
|
tenantId: string,
|
|
enableBackendSync: boolean,
|
|
syncManagerRef: RefObject<StateSyncManager | null>,
|
|
setError: (e: Error) => void
|
|
) {
|
|
return useCallback(async (): Promise<void> => {
|
|
try {
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem(`${SDK_STORAGE_KEY}-${tenantId}`, JSON.stringify(state))
|
|
}
|
|
|
|
if (enableBackendSync && syncManagerRef.current) {
|
|
await syncManagerRef.current.forceSync(state)
|
|
}
|
|
} catch (err) {
|
|
setError(err as Error)
|
|
throw err
|
|
}
|
|
}, [state, tenantId, enableBackendSync, syncManagerRef, setError])
|
|
}
|
|
|
|
export function useLoadState(
|
|
tenantId: string,
|
|
enableBackendSync: boolean,
|
|
syncManagerRef: RefObject<StateSyncManager | null>,
|
|
setIsLoading: (v: boolean) => void,
|
|
dispatch: React.Dispatch<{ type: string; payload?: unknown }>,
|
|
setError: (e: Error) => void
|
|
) {
|
|
return useCallback(async (): Promise<void> => {
|
|
setIsLoading(true)
|
|
try {
|
|
if (enableBackendSync && syncManagerRef.current) {
|
|
const serverState = await syncManagerRef.current.loadFromServer()
|
|
if (serverState) {
|
|
dispatch({ type: 'SET_STATE', payload: serverState })
|
|
return
|
|
}
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
const stored = localStorage.getItem(`${SDK_STORAGE_KEY}-${tenantId}`)
|
|
if (stored) {
|
|
dispatch({ type: 'SET_STATE', payload: JSON.parse(stored) })
|
|
}
|
|
}
|
|
} catch (err) {
|
|
setError(err as Error)
|
|
throw err
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}, [tenantId, enableBackendSync, syncManagerRef, setIsLoading, dispatch, setError])
|
|
}
|
|
|
|
export function useResetState(
|
|
tenantId: string,
|
|
dispatch: React.Dispatch<{ type: string; payload?: unknown }>
|
|
) {
|
|
return useCallback(() => {
|
|
dispatch({ type: 'RESET_STATE' })
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.removeItem(`${SDK_STORAGE_KEY}-${tenantId}`)
|
|
}
|
|
}, [tenantId, dispatch])
|
|
}
|
|
|
|
// =============================================================================
|
|
// SYNC & EXPORT CALLBACKS
|
|
// =============================================================================
|
|
|
|
export function useForceSyncToServer(
|
|
state: SDKState,
|
|
enableBackendSync: boolean,
|
|
syncManagerRef: RefObject<StateSyncManager | null>
|
|
) {
|
|
return useCallback(async (): Promise<void> => {
|
|
if (enableBackendSync && syncManagerRef.current) {
|
|
await syncManagerRef.current.forceSync(state)
|
|
}
|
|
}, [state, enableBackendSync, syncManagerRef])
|
|
}
|
|
|
|
export function useExportState(state: SDKState, client: ComplianceClient) {
|
|
return useCallback(
|
|
async (format: 'json' | 'pdf' | 'zip'): Promise<Blob> => {
|
|
if (format === 'json') {
|
|
return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json' })
|
|
}
|
|
return client.exportState(format)
|
|
},
|
|
[state, client]
|
|
)
|
|
}
|