'use client' import React, { createContext, useContext, useReducer, useEffect, useCallback, useMemo, useRef } from 'react' import { useRouter, usePathname } from 'next/navigation' import { SDKState, SDKAction, SDKStep, CheckpointStatus, UseCaseAssessment, Risk, Control, UserPreferences, CustomerType, CompanyProfile, ImportedDocument, GapAnalysis, SDKPackageId, ProjectInfo, SDK_STEPS, SDK_PACKAGES, getStepById, getStepByUrl, getNextStep, getPreviousStep, getCompletionPercentage, getPhaseCompletionPercentage, getPackageCompletionPercentage, getStepsForPackage, } from './types' import { exportToPDF, exportToZIP } from './export' import { SDKApiClient, getSDKApiClient, resetSDKApiClient } from './api-client' import { StateSyncManager, createStateSyncManager, SyncState } from './sync' import { generateDemoState, seedDemoData as seedDemoDataApi, clearDemoData as clearDemoDataApi } from './demo-data' // ============================================================================= // INITIAL STATE // ============================================================================= const initialPreferences: UserPreferences = { language: 'de', theme: 'light', compactMode: false, showHints: true, autoSave: true, autoValidate: true, allowParallelWork: true, // Standard: Paralleles Arbeiten erlaubt } const initialState: SDKState = { // Metadata version: '1.0.0', projectVersion: 1, lastModified: new Date(), // Tenant & User tenantId: '', userId: '', subscription: 'PROFESSIONAL', // Project Context projectId: '', projectInfo: null, // Customer Type customerType: null, // Company Profile companyProfile: null, // Compliance Scope complianceScope: null, // Source Policy sourcePolicy: null, // Progress currentPhase: 1, currentStep: 'company-profile', completedSteps: [], checkpoints: {}, // Imported Documents (for existing customers) importedDocuments: [], gapAnalysis: null, // Phase 1 Data useCases: [], activeUseCase: null, screening: null, modules: [], requirements: [], controls: [], evidence: [], checklist: [], risks: [], // Phase 2 Data aiActClassification: null, obligations: [], dsfa: null, toms: [], retentionPolicies: [], vvt: [], documents: [], cookieBanner: null, consents: [], dsrConfig: null, escalationWorkflows: [], // IACE (Industrial AI Compliance Engine) iaceProjects: [], // RAG Corpus Versioning ragCorpusStatus: null, // Security sbom: null, securityIssues: [], securityBacklog: [], // Catalog Manager customCatalogs: {}, // UI State commandBarHistory: [], recentSearches: [], preferences: initialPreferences, } // ============================================================================= // EXTENDED ACTION TYPES // ============================================================================= // Extended action type to include demo data loading type ExtendedSDKAction = | SDKAction | { type: 'LOAD_DEMO_DATA'; payload: Partial } // ============================================================================= // REDUCER // ============================================================================= function sdkReducer(state: SDKState, action: ExtendedSDKAction): SDKState { const updateState = (updates: Partial): SDKState => ({ ...state, ...updates, lastModified: new Date(), }) switch (action.type) { case 'SET_STATE': return updateState(action.payload) case 'LOAD_DEMO_DATA': // Load demo data while preserving user preferences return { ...initialState, ...action.payload, tenantId: state.tenantId, userId: state.userId, preferences: state.preferences, lastModified: new Date(), } case 'SET_CURRENT_STEP': { const step = getStepById(action.payload) return updateState({ currentStep: action.payload, currentPhase: step?.phase || state.currentPhase, }) } case 'COMPLETE_STEP': if (state.completedSteps.includes(action.payload)) { return state } return updateState({ completedSteps: [...state.completedSteps, action.payload], }) case 'SET_CHECKPOINT_STATUS': return updateState({ checkpoints: { ...state.checkpoints, [action.payload.id]: action.payload.status, }, }) case 'SET_CUSTOMER_TYPE': return updateState({ customerType: action.payload }) case 'SET_COMPANY_PROFILE': return updateState({ companyProfile: action.payload }) case 'UPDATE_COMPANY_PROFILE': return updateState({ companyProfile: state.companyProfile ? { ...state.companyProfile, ...action.payload } : null, }) case 'SET_COMPLIANCE_SCOPE': return updateState({ complianceScope: action.payload }) case 'UPDATE_COMPLIANCE_SCOPE': return updateState({ complianceScope: state.complianceScope ? { ...state.complianceScope, ...action.payload } : null, }) case 'ADD_IMPORTED_DOCUMENT': return updateState({ importedDocuments: [...state.importedDocuments, action.payload], }) case 'UPDATE_IMPORTED_DOCUMENT': return updateState({ importedDocuments: state.importedDocuments.map(doc => doc.id === action.payload.id ? { ...doc, ...action.payload.data } : doc ), }) case 'DELETE_IMPORTED_DOCUMENT': return updateState({ importedDocuments: state.importedDocuments.filter(doc => doc.id !== action.payload), }) case 'SET_GAP_ANALYSIS': return updateState({ gapAnalysis: action.payload }) case 'ADD_USE_CASE': return updateState({ useCases: [...state.useCases, action.payload], }) case 'UPDATE_USE_CASE': return updateState({ useCases: state.useCases.map(uc => uc.id === action.payload.id ? { ...uc, ...action.payload.data } : uc ), }) case 'DELETE_USE_CASE': return updateState({ useCases: state.useCases.filter(uc => uc.id !== action.payload), activeUseCase: state.activeUseCase === action.payload ? null : state.activeUseCase, }) case 'SET_ACTIVE_USE_CASE': return updateState({ activeUseCase: action.payload }) case 'SET_SCREENING': return updateState({ screening: action.payload }) case 'ADD_MODULE': return updateState({ modules: [...state.modules, action.payload], }) case 'UPDATE_MODULE': return updateState({ modules: state.modules.map(m => m.id === action.payload.id ? { ...m, ...action.payload.data } : m ), }) case 'ADD_REQUIREMENT': return updateState({ requirements: [...state.requirements, action.payload], }) case 'UPDATE_REQUIREMENT': return updateState({ requirements: state.requirements.map(r => r.id === action.payload.id ? { ...r, ...action.payload.data } : r ), }) case 'ADD_CONTROL': return updateState({ controls: [...state.controls, action.payload], }) case 'UPDATE_CONTROL': return updateState({ controls: state.controls.map(c => c.id === action.payload.id ? { ...c, ...action.payload.data } : c ), }) case 'ADD_EVIDENCE': return updateState({ evidence: [...state.evidence, action.payload], }) case 'UPDATE_EVIDENCE': return updateState({ evidence: state.evidence.map(e => e.id === action.payload.id ? { ...e, ...action.payload.data } : e ), }) case 'DELETE_EVIDENCE': return updateState({ evidence: state.evidence.filter(e => e.id !== action.payload), }) case 'ADD_RISK': return updateState({ risks: [...state.risks, action.payload], }) case 'UPDATE_RISK': return updateState({ risks: state.risks.map(r => r.id === action.payload.id ? { ...r, ...action.payload.data } : r ), }) case 'DELETE_RISK': return updateState({ risks: state.risks.filter(r => r.id !== action.payload), }) case 'SET_AI_ACT_RESULT': return updateState({ aiActClassification: action.payload }) case 'ADD_OBLIGATION': return updateState({ obligations: [...state.obligations, action.payload], }) case 'UPDATE_OBLIGATION': return updateState({ obligations: state.obligations.map(o => o.id === action.payload.id ? { ...o, ...action.payload.data } : o ), }) case 'SET_DSFA': return updateState({ dsfa: action.payload }) case 'ADD_TOM': return updateState({ toms: [...state.toms, action.payload], }) case 'UPDATE_TOM': return updateState({ toms: state.toms.map(t => t.id === action.payload.id ? { ...t, ...action.payload.data } : t ), }) case 'ADD_RETENTION_POLICY': return updateState({ retentionPolicies: [...state.retentionPolicies, action.payload], }) case 'UPDATE_RETENTION_POLICY': return updateState({ retentionPolicies: state.retentionPolicies.map(p => p.id === action.payload.id ? { ...p, ...action.payload.data } : p ), }) case 'ADD_PROCESSING_ACTIVITY': return updateState({ vvt: [...state.vvt, action.payload], }) case 'UPDATE_PROCESSING_ACTIVITY': return updateState({ vvt: state.vvt.map(p => p.id === action.payload.id ? { ...p, ...action.payload.data } : p ), }) case 'ADD_DOCUMENT': return updateState({ documents: [...state.documents, action.payload], }) case 'UPDATE_DOCUMENT': return updateState({ documents: state.documents.map(d => d.id === action.payload.id ? { ...d, ...action.payload.data } : d ), }) case 'SET_COOKIE_BANNER': return updateState({ cookieBanner: action.payload }) case 'SET_DSR_CONFIG': return updateState({ dsrConfig: action.payload }) case 'ADD_ESCALATION_WORKFLOW': return updateState({ escalationWorkflows: [...state.escalationWorkflows, action.payload], }) case 'UPDATE_ESCALATION_WORKFLOW': return updateState({ escalationWorkflows: state.escalationWorkflows.map(w => w.id === action.payload.id ? { ...w, ...action.payload.data } : w ), }) case 'ADD_SECURITY_ISSUE': return updateState({ securityIssues: [...state.securityIssues, action.payload], }) case 'UPDATE_SECURITY_ISSUE': return updateState({ securityIssues: state.securityIssues.map(i => i.id === action.payload.id ? { ...i, ...action.payload.data } : i ), }) case 'ADD_BACKLOG_ITEM': return updateState({ securityBacklog: [...state.securityBacklog, action.payload], }) case 'UPDATE_BACKLOG_ITEM': return updateState({ securityBacklog: state.securityBacklog.map(i => i.id === action.payload.id ? { ...i, ...action.payload.data } : i ), }) case 'ADD_COMMAND_HISTORY': return updateState({ commandBarHistory: [action.payload, ...state.commandBarHistory].slice(0, 50), }) case 'SET_PREFERENCES': return updateState({ preferences: { ...state.preferences, ...action.payload }, }) case 'ADD_CUSTOM_CATALOG_ENTRY': { const entry = action.payload const existing = state.customCatalogs[entry.catalogId] || [] return updateState({ customCatalogs: { ...state.customCatalogs, [entry.catalogId]: [...existing, entry], }, }) } case 'UPDATE_CUSTOM_CATALOG_ENTRY': { const { catalogId, entryId, data } = action.payload const entries = state.customCatalogs[catalogId] || [] return updateState({ customCatalogs: { ...state.customCatalogs, [catalogId]: entries.map(e => e.id === entryId ? { ...e, data: { ...e.data, ...data }, updatedAt: new Date().toISOString() } : e ), }, }) } case 'DELETE_CUSTOM_CATALOG_ENTRY': { const { catalogId, entryId } = action.payload const items = state.customCatalogs[catalogId] || [] return updateState({ customCatalogs: { ...state.customCatalogs, [catalogId]: items.filter(e => e.id !== entryId), }, }) } case 'RESET_STATE': return { ...initialState, lastModified: new Date() } default: return state } } // ============================================================================= // CONTEXT TYPES // ============================================================================= interface SDKContextValue { state: SDKState dispatch: React.Dispatch // Navigation currentStep: SDKStep | undefined goToStep: (stepId: string) => void goToNextStep: () => void goToPreviousStep: () => void canGoNext: boolean canGoPrevious: boolean // Progress completionPercentage: number phase1Completion: number phase2Completion: number packageCompletion: Record // Customer Type setCustomerType: (type: CustomerType) => void // Company Profile setCompanyProfile: (profile: CompanyProfile) => void updateCompanyProfile: (updates: Partial) => void // Compliance Scope setComplianceScope: (scope: import('./compliance-scope-types').ComplianceScopeState) => void updateComplianceScope: (updates: Partial) => void // Import (for existing customers) addImportedDocument: (doc: ImportedDocument) => void setGapAnalysis: (analysis: GapAnalysis) => void // Checkpoints validateCheckpoint: (checkpointId: string) => Promise overrideCheckpoint: (checkpointId: string, reason: string) => Promise getCheckpointStatus: (checkpointId: string) => CheckpointStatus | undefined // State Updates updateUseCase: (id: string, data: Partial) => void addRisk: (risk: Risk) => void updateControl: (id: string, data: Partial) => void // Persistence saveState: () => Promise loadState: () => Promise // Demo Data loadDemoData: (demoState: Partial) => void seedDemoData: () => Promise<{ success: boolean; message: string }> clearDemoData: () => Promise isDemoDataLoaded: boolean // Sync syncState: SyncState forceSyncToServer: () => Promise isOnline: boolean // Export exportState: (format: 'json' | 'pdf' | 'zip') => Promise // Command Bar isCommandBarOpen: boolean setCommandBarOpen: (open: boolean) => void // Project Management projectId: string | undefined createProject: (name: string, customerType: CustomerType, copyFromProjectId?: string) => Promise listProjects: () => Promise switchProject: (projectId: string) => void archiveProject: (projectId: string) => Promise restoreProject: (projectId: string) => Promise permanentlyDeleteProject: (projectId: string) => Promise } const SDKContext = createContext(null) // ============================================================================= // PROVIDER // ============================================================================= const SDK_STORAGE_KEY = 'ai-compliance-sdk-state' interface SDKProviderProps { children: React.ReactNode tenantId?: string userId?: string projectId?: string enableBackendSync?: boolean } export function SDKProvider({ children, tenantId = '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e', userId = 'default', projectId, enableBackendSync = false, }: SDKProviderProps) { const router = useRouter() const pathname = usePathname() const [state, dispatch] = useReducer(sdkReducer, { ...initialState, tenantId, userId, projectId: projectId || '', }) const [isCommandBarOpen, setCommandBarOpen] = React.useState(false) const [isInitialized, setIsInitialized] = React.useState(false) const [syncState, setSyncState] = React.useState({ status: 'idle', lastSyncedAt: null, localVersion: 0, serverVersion: 0, pendingChanges: 0, error: null, }) const [isOnline, setIsOnline] = React.useState(true) // Refs for sync manager and API client const apiClientRef = useRef(null) const syncManagerRef = useRef(null) // Initialize API client and sync manager useEffect(() => { if (enableBackendSync && typeof window !== 'undefined') { apiClientRef.current = getSDKApiClient(tenantId, projectId) syncManagerRef.current = createStateSyncManager( apiClientRef.current, tenantId, { debounceMs: 2000, maxRetries: 3, }, { onSyncStart: () => { setSyncState(prev => ({ ...prev, status: 'syncing' })) }, onSyncComplete: (syncedState) => { setSyncState(prev => ({ ...prev, status: 'idle', lastSyncedAt: new Date(), pendingChanges: 0, })) // Update state if it differs from current if (syncedState.lastModified > state.lastModified) { dispatch({ type: 'SET_STATE', payload: syncedState }) } }, onSyncError: (error) => { setSyncState(prev => ({ ...prev, status: 'error', error: error.message, })) }, onConflict: () => { setSyncState(prev => ({ ...prev, status: 'conflict' })) }, onOffline: () => { setIsOnline(false) setSyncState(prev => ({ ...prev, status: 'offline' })) }, onOnline: () => { setIsOnline(true) setSyncState(prev => ({ ...prev, status: 'idle' })) }, }, projectId ) } return () => { if (syncManagerRef.current) { syncManagerRef.current.destroy() syncManagerRef.current = null } if (enableBackendSync) { resetSDKApiClient() apiClientRef.current = null } } }, [enableBackendSync, tenantId, projectId]) // Sync current step with URL useEffect(() => { if (pathname) { const step = getStepByUrl(pathname) if (step && step.id !== state.currentStep) { dispatch({ type: 'SET_CURRENT_STEP', payload: step.id }) } } }, [pathname, state.currentStep]) // Storage key — per tenant+project const storageKey = projectId ? `${SDK_STORAGE_KEY}-${tenantId}-${projectId}` : `${SDK_STORAGE_KEY}-${tenantId}` // Load state on mount (localStorage first, then server) useEffect(() => { const loadInitialState = async () => { try { // First, try loading from localStorage const stored = localStorage.getItem(storageKey) if (stored) { const parsed = JSON.parse(stored) if (parsed.lastModified) { parsed.lastModified = new Date(parsed.lastModified) } dispatch({ type: 'SET_STATE', payload: parsed }) } // Then, try loading from server if backend sync is enabled if (enableBackendSync && syncManagerRef.current) { const serverState = await syncManagerRef.current.loadFromServer() if (serverState) { // Server state is newer, use it const localTime = stored ? new Date(JSON.parse(stored).lastModified).getTime() : 0 const serverTime = new Date(serverState.lastModified).getTime() if (serverTime > localTime) { dispatch({ type: 'SET_STATE', payload: serverState }) } } } // Load project metadata (name, status, etc.) from backend if (enableBackendSync && projectId && apiClientRef.current) { try { const info = await apiClientRef.current.getProject(projectId) dispatch({ type: 'SET_STATE', payload: { projectInfo: info } }) } catch (err) { console.warn('Failed to load project info:', err) } } } catch (error) { console.error('Failed to load SDK state:', error) } setIsInitialized(true) } loadInitialState() }, [tenantId, projectId, enableBackendSync, storageKey]) // Auto-save to localStorage and sync to server useEffect(() => { if (!isInitialized || !state.preferences.autoSave) return const saveTimeout = setTimeout(() => { try { // Save to localStorage (per tenant+project) localStorage.setItem(storageKey, JSON.stringify(state)) // Sync to server if backend sync is enabled if (enableBackendSync && syncManagerRef.current) { syncManagerRef.current.queueSync(state) } } catch (error) { console.error('Failed to save SDK state:', error) } }, 1000) return () => clearTimeout(saveTimeout) }, [state, tenantId, projectId, isInitialized, enableBackendSync, storageKey]) // Keyboard shortcut for Command Bar useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault() setCommandBarOpen(prev => !prev) } if (e.key === 'Escape' && isCommandBarOpen) { setCommandBarOpen(false) } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [isCommandBarOpen]) // Navigation const currentStep = useMemo(() => getStepById(state.currentStep), [state.currentStep]) const goToStep = useCallback( (stepId: string) => { const step = getStepById(stepId) if (step) { dispatch({ type: 'SET_CURRENT_STEP', payload: stepId }) const url = projectId ? `${step.url}?project=${projectId}` : step.url router.push(url) } }, [router, projectId] ) const goToNextStep = useCallback(() => { const nextStep = getNextStep(state.currentStep, state) if (nextStep) { goToStep(nextStep.id) } }, [state, goToStep]) const goToPreviousStep = useCallback(() => { const prevStep = getPreviousStep(state.currentStep, state) if (prevStep) { goToStep(prevStep.id) } }, [state, goToStep]) const canGoNext = useMemo(() => { return getNextStep(state.currentStep, state) !== undefined }, [state]) const canGoPrevious = useMemo(() => { return getPreviousStep(state.currentStep, state) !== undefined }, [state]) // Progress const completionPercentage = useMemo(() => getCompletionPercentage(state), [state]) const phase1Completion = useMemo(() => getPhaseCompletionPercentage(state, 1), [state]) const phase2Completion = useMemo(() => getPhaseCompletionPercentage(state, 2), [state]) // Package Completion const packageCompletion = useMemo(() => { const completion: Record = { 'vorbereitung': getPackageCompletionPercentage(state, 'vorbereitung'), 'analyse': getPackageCompletionPercentage(state, 'analyse'), 'dokumentation': getPackageCompletionPercentage(state, 'dokumentation'), 'rechtliche-texte': getPackageCompletionPercentage(state, 'rechtliche-texte'), 'betrieb': getPackageCompletionPercentage(state, 'betrieb'), } return completion }, [state]) // Customer Type const setCustomerType = useCallback((type: CustomerType) => { dispatch({ type: 'SET_CUSTOMER_TYPE', payload: type }) }, []) // Company Profile const setCompanyProfile = useCallback((profile: CompanyProfile) => { dispatch({ type: 'SET_COMPANY_PROFILE', payload: profile }) }, []) const updateCompanyProfile = useCallback((updates: Partial) => { dispatch({ type: 'UPDATE_COMPANY_PROFILE', payload: updates }) }, []) // Compliance Scope const setComplianceScope = useCallback((scope: import('./compliance-scope-types').ComplianceScopeState) => { dispatch({ type: 'SET_COMPLIANCE_SCOPE', payload: scope }) }, []) const updateComplianceScope = useCallback((updates: Partial) => { dispatch({ type: 'UPDATE_COMPLIANCE_SCOPE', payload: updates }) }, []) // Import Document const addImportedDocument = useCallback((doc: ImportedDocument) => { dispatch({ type: 'ADD_IMPORTED_DOCUMENT', payload: doc }) }, []) // Gap Analysis const setGapAnalysis = useCallback((analysis: GapAnalysis) => { dispatch({ type: 'SET_GAP_ANALYSIS', payload: analysis }) }, []) // Checkpoints const validateCheckpoint = useCallback( async (checkpointId: string): Promise => { // Try backend validation if available if (enableBackendSync && apiClientRef.current) { try { const result = await apiClientRef.current.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 back to local validation } } // Local validation const status: CheckpointStatus = { checkpointId, passed: true, validatedAt: new Date(), validatedBy: 'SYSTEM', errors: [], warnings: [], } switch (checkpointId) { case 'CP-PROF': if (!state.companyProfile || !state.companyProfile.isComplete) { status.passed = false status.errors.push({ ruleId: 'prof-complete', field: 'companyProfile', message: 'Unternehmensprofil muss vollständig ausgefüllt werden', severity: 'ERROR', }) } break case 'CP-UC': if (state.useCases.length === 0) { status.passed = false status.errors.push({ ruleId: 'uc-min-count', field: 'useCases', message: 'Mindestens ein Anwendungsfall muss erstellt werden', severity: 'ERROR', }) } break case 'CP-SCAN': if (!state.screening || state.screening.status !== 'COMPLETED') { status.passed = false status.errors.push({ ruleId: 'scan-complete', field: 'screening', message: 'Security Scan muss abgeschlossen sein', severity: 'ERROR', }) } break case 'CP-MOD': if (state.modules.length === 0) { status.passed = false status.errors.push({ ruleId: 'mod-min-count', field: 'modules', message: 'Mindestens ein Modul muss zugewiesen werden', severity: 'ERROR', }) } break case 'CP-RISK': const criticalRisks = state.risks.filter( r => r.severity === 'CRITICAL' || r.severity === 'HIGH' ) const unmitigatedRisks = criticalRisks.filter( r => r.mitigation.length === 0 ) if (unmitigatedRisks.length > 0) { status.passed = false status.errors.push({ ruleId: 'critical-risks-mitigated', field: 'risks', message: `${unmitigatedRisks.length} kritische Risiken ohne Mitigationsmaßnahmen`, severity: 'ERROR', }) } break } dispatch({ type: 'SET_CHECKPOINT_STATUS', payload: { id: checkpointId, status } }) return status }, [state, enableBackendSync] ) const overrideCheckpoint = useCallback( async (checkpointId: string, reason: string): Promise => { 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] ) const getCheckpointStatus = useCallback( (checkpointId: string): CheckpointStatus | undefined => { return state.checkpoints[checkpointId] }, [state.checkpoints] ) // State Updates const updateUseCase = useCallback( (id: string, data: Partial) => { dispatch({ type: 'UPDATE_USE_CASE', payload: { id, data } }) }, [] ) const addRisk = useCallback((risk: Risk) => { dispatch({ type: 'ADD_RISK', payload: risk }) }, []) const updateControl = useCallback( (id: string, data: Partial) => { dispatch({ type: 'UPDATE_CONTROL', payload: { id, data } }) }, [] ) // Demo Data Loading const loadDemoData = useCallback((demoState: Partial) => { dispatch({ type: 'LOAD_DEMO_DATA', payload: demoState }) }, []) // Seed demo data via API (stores like real customer data) const seedDemoData = useCallback(async (): Promise<{ success: boolean; message: string }> => { try { // Generate demo state const demoState = generateDemoState(tenantId, userId) as SDKState // Save via API (same path as real customer data) if (enableBackendSync && apiClientRef.current) { await apiClientRef.current.saveState(demoState) } // Also save to localStorage for immediate availability localStorage.setItem(storageKey, JSON.stringify(demoState)) // Update local state dispatch({ type: 'LOAD_DEMO_DATA', payload: demoState }) return { success: true, message: `Demo-Daten erfolgreich geladen für Tenant ${tenantId}` } } catch (error) { console.error('Failed to seed demo data:', error) return { success: false, message: error instanceof Error ? error.message : 'Unbekannter Fehler beim Laden der Demo-Daten', } } }, [tenantId, userId, enableBackendSync, storageKey]) // Clear demo data const clearDemoData = useCallback(async (): Promise => { try { // Delete from API if (enableBackendSync && apiClientRef.current) { await apiClientRef.current.deleteState() } // Clear localStorage localStorage.removeItem(storageKey) // Reset local state dispatch({ type: 'RESET_STATE' }) return true } catch (error) { console.error('Failed to clear demo data:', error) return false } }, [storageKey, enableBackendSync]) // Check if demo data is loaded (has use cases with demo- prefix) const isDemoDataLoaded = useMemo(() => { return state.useCases.length > 0 && state.useCases.some(uc => uc.id.startsWith('demo-')) }, [state.useCases]) // Persistence const saveState = useCallback(async (): Promise => { try { localStorage.setItem(storageKey, JSON.stringify(state)) if (enableBackendSync && syncManagerRef.current) { await syncManagerRef.current.forcSync(state) } } catch (error) { console.error('Failed to save SDK state:', error) throw error } }, [state, storageKey, enableBackendSync]) const loadState = useCallback(async (): Promise => { try { if (enableBackendSync && syncManagerRef.current) { const serverState = await syncManagerRef.current.loadFromServer() if (serverState) { dispatch({ type: 'SET_STATE', payload: serverState }) return } } // Fall back to localStorage const stored = localStorage.getItem(storageKey) if (stored) { const parsed = JSON.parse(stored) dispatch({ type: 'SET_STATE', payload: parsed }) } } catch (error) { console.error('Failed to load SDK state:', error) throw error } }, [storageKey, enableBackendSync]) // Force sync to server const forceSyncToServer = useCallback(async (): Promise => { if (enableBackendSync && syncManagerRef.current) { await syncManagerRef.current.forcSync(state) } }, [state, enableBackendSync]) // Project Management const createProject = useCallback( async (name: string, customerType: CustomerType, copyFromProjectId?: string): Promise => { if (!apiClientRef.current && enableBackendSync) { apiClientRef.current = getSDKApiClient(tenantId, projectId) } if (!apiClientRef.current) { throw new Error('Backend sync not enabled') } return apiClientRef.current.createProject({ name, customer_type: customerType, copy_from_project_id: copyFromProjectId, }) }, [enableBackendSync, tenantId, projectId] ) const listProjectsFn = useCallback(async (): Promise => { // Ensure API client exists (may not be set yet if useEffect hasn't fired) if (!apiClientRef.current && enableBackendSync) { apiClientRef.current = getSDKApiClient(tenantId, projectId) } if (!apiClientRef.current) { return [] } const result = await apiClientRef.current.listProjects() return result.projects }, [enableBackendSync, tenantId, projectId]) const switchProject = useCallback( (newProjectId: string) => { // Navigate to the SDK dashboard with the new project const params = new URLSearchParams(window.location.search) params.set('project', newProjectId) router.push(`/sdk?${params.toString()}`) }, [router] ) const archiveProjectFn = useCallback( async (archiveId: string): Promise => { if (!apiClientRef.current && enableBackendSync) { apiClientRef.current = getSDKApiClient(tenantId, projectId) } if (!apiClientRef.current) { throw new Error('Backend sync not enabled') } await apiClientRef.current.archiveProject(archiveId) }, [enableBackendSync, tenantId, projectId] ) const restoreProjectFn = useCallback( async (restoreId: string): Promise => { if (!apiClientRef.current && enableBackendSync) { apiClientRef.current = getSDKApiClient(tenantId, projectId) } if (!apiClientRef.current) { throw new Error('Backend sync not enabled') } return apiClientRef.current.restoreProject(restoreId) }, [enableBackendSync, tenantId, projectId] ) const permanentlyDeleteProjectFn = useCallback( async (deleteId: string): Promise => { if (!apiClientRef.current && enableBackendSync) { apiClientRef.current = getSDKApiClient(tenantId, projectId) } if (!apiClientRef.current) { throw new Error('Backend sync not enabled') } await apiClientRef.current.permanentlyDeleteProject(deleteId) }, [enableBackendSync, tenantId, projectId] ) // Export const exportState = useCallback( async (format: 'json' | 'pdf' | 'zip'): Promise => { switch (format) { case 'json': return new Blob([JSON.stringify(state, null, 2)], { type: 'application/json', }) case 'pdf': return exportToPDF(state) case 'zip': return exportToZIP(state) default: throw new Error(`Unknown export format: ${format}`) } }, [state] ) const value: SDKContextValue = { state, dispatch, currentStep, goToStep, goToNextStep, goToPreviousStep, canGoNext, canGoPrevious, completionPercentage, phase1Completion, phase2Completion, packageCompletion, setCustomerType, setCompanyProfile, updateCompanyProfile, setComplianceScope, updateComplianceScope, addImportedDocument, setGapAnalysis, validateCheckpoint, overrideCheckpoint, getCheckpointStatus, updateUseCase, addRisk, updateControl, saveState, loadState, loadDemoData, seedDemoData, clearDemoData, isDemoDataLoaded, syncState, forceSyncToServer, isOnline, exportState, isCommandBarOpen, setCommandBarOpen, projectId, createProject, listProjects: listProjectsFn, switchProject, archiveProject: archiveProjectFn, restoreProject: restoreProjectFn, permanentlyDeleteProject: permanentlyDeleteProjectFn, } return {children} } // ============================================================================= // HOOK // ============================================================================= export function useSDK(): SDKContextValue { const context = useContext(SDKContext) if (!context) { throw new Error('useSDK must be used within SDKProvider') } return context } // ============================================================================= // EXPORTS // ============================================================================= export { SDKContext, initialState }