Files split by agents before rate limit: - dsr/api.ts (669 → barrel + helpers) - einwilligungen/context.tsx (669 → barrel + hooks/reducer) - export.ts (753 → barrel + domain exporters) - incidents/api.ts (845 → barrel + api-helpers) - tom-generator/context.tsx (720 → barrel + hooks/reducer) - vendor-compliance/context.tsx (1010 → 234 provider + hooks/reducer) - api-docs/endpoints.ts — partially split (3 domain files created) - academy/api.ts — partially split (helpers extracted) - whistleblower/api.ts — partially split (helpers extracted) next build passes. api-client.ts (885) deferred to next session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
385 lines
12 KiB
TypeScript
385 lines
12 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Einwilligungen Provider
|
|
*
|
|
* React Context Provider fuer das Einwilligungen-Modul.
|
|
* Stellt State, computed values und Actions bereit.
|
|
*/
|
|
|
|
import {
|
|
createContext,
|
|
useReducer,
|
|
useCallback,
|
|
useMemo,
|
|
ReactNode,
|
|
Dispatch,
|
|
} from 'react'
|
|
import {
|
|
EinwilligungenState,
|
|
EinwilligungenAction,
|
|
EinwilligungenTab,
|
|
DataPoint,
|
|
CookieBannerConfig,
|
|
CompanyInfo,
|
|
SupportedLanguage,
|
|
ExportFormat,
|
|
DataPointCategory,
|
|
LegalBasis,
|
|
RiskLevel,
|
|
} from './types'
|
|
import {
|
|
PREDEFINED_DATA_POINTS,
|
|
DEFAULT_COOKIE_CATEGORIES,
|
|
createDefaultCatalog,
|
|
} from './catalog/loader'
|
|
import { einwilligungenReducer, initialState } from './reducer'
|
|
|
|
// =============================================================================
|
|
// CONTEXT
|
|
// =============================================================================
|
|
|
|
export interface EinwilligungenContextValue {
|
|
state: EinwilligungenState
|
|
dispatch: Dispatch<EinwilligungenAction>
|
|
|
|
// Computed Values
|
|
allDataPoints: DataPoint[]
|
|
selectedDataPointsData: DataPoint[]
|
|
dataPointsByCategory: Record<DataPointCategory, DataPoint[]>
|
|
categoryStats: Record<DataPointCategory, number>
|
|
riskStats: Record<RiskLevel, number>
|
|
legalBasisStats: Record<LegalBasis, number>
|
|
|
|
// Actions
|
|
initializeCatalog: (tenantId: string) => void
|
|
loadCatalog: (tenantId: string) => Promise<void>
|
|
saveCatalog: () => Promise<void>
|
|
toggleDataPoint: (id: string) => void
|
|
addCustomDataPoint: (dataPoint: DataPoint) => void
|
|
updateDataPoint: (id: string, data: Partial<DataPoint>) => void
|
|
deleteCustomDataPoint: (id: string) => void
|
|
setActiveTab: (tab: EinwilligungenTab) => void
|
|
setPreviewLanguage: (language: SupportedLanguage) => void
|
|
setPreviewFormat: (format: ExportFormat) => void
|
|
setCompanyInfo: (info: CompanyInfo) => void
|
|
generatePrivacyPolicy: () => Promise<void>
|
|
generateCookieBannerConfig: () => void
|
|
}
|
|
|
|
export const EinwilligungenContext = createContext<EinwilligungenContextValue | null>(null)
|
|
|
|
// =============================================================================
|
|
// PROVIDER
|
|
// =============================================================================
|
|
|
|
interface EinwilligungenProviderProps {
|
|
children: ReactNode
|
|
tenantId?: string
|
|
}
|
|
|
|
export function EinwilligungenProvider({ children, tenantId }: EinwilligungenProviderProps) {
|
|
const [state, dispatch] = useReducer(einwilligungenReducer, initialState)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// COMPUTED VALUES
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const allDataPoints = useMemo(() => {
|
|
if (!state.catalog) return PREDEFINED_DATA_POINTS
|
|
return [...state.catalog.dataPoints, ...state.catalog.customDataPoints]
|
|
}, [state.catalog])
|
|
|
|
const selectedDataPointsData = useMemo(() => {
|
|
return allDataPoints.filter((dp) => state.selectedDataPoints.includes(dp.id))
|
|
}, [allDataPoints, state.selectedDataPoints])
|
|
|
|
const dataPointsByCategory = useMemo(() => {
|
|
const result: Partial<Record<DataPointCategory, DataPoint[]>> = {}
|
|
const categories: DataPointCategory[] = [
|
|
'MASTER_DATA', 'CONTACT_DATA', 'AUTHENTICATION', 'CONSENT',
|
|
'COMMUNICATION', 'PAYMENT', 'USAGE_DATA', 'LOCATION',
|
|
'DEVICE_DATA', 'MARKETING', 'ANALYTICS', 'SOCIAL_MEDIA',
|
|
'HEALTH_DATA', 'EMPLOYEE_DATA', 'CONTRACT_DATA', 'LOG_DATA',
|
|
'AI_DATA', 'SECURITY',
|
|
]
|
|
for (const cat of categories) {
|
|
result[cat] = selectedDataPointsData.filter((dp) => dp.category === cat)
|
|
}
|
|
return result as Record<DataPointCategory, DataPoint[]>
|
|
}, [selectedDataPointsData])
|
|
|
|
const categoryStats = useMemo(() => {
|
|
const counts: Partial<Record<DataPointCategory, number>> = {}
|
|
for (const dp of selectedDataPointsData) {
|
|
counts[dp.category] = (counts[dp.category] || 0) + 1
|
|
}
|
|
return counts as Record<DataPointCategory, number>
|
|
}, [selectedDataPointsData])
|
|
|
|
const riskStats = useMemo(() => {
|
|
const counts: Record<RiskLevel, number> = { LOW: 0, MEDIUM: 0, HIGH: 0 }
|
|
for (const dp of selectedDataPointsData) {
|
|
counts[dp.riskLevel]++
|
|
}
|
|
return counts
|
|
}, [selectedDataPointsData])
|
|
|
|
const legalBasisStats = useMemo(() => {
|
|
const counts: Record<LegalBasis, number> = {
|
|
CONTRACT: 0, CONSENT: 0, EXPLICIT_CONSENT: 0,
|
|
LEGITIMATE_INTEREST: 0, LEGAL_OBLIGATION: 0,
|
|
VITAL_INTERESTS: 0, PUBLIC_INTEREST: 0,
|
|
}
|
|
for (const dp of selectedDataPointsData) {
|
|
counts[dp.legalBasis]++
|
|
}
|
|
return counts
|
|
}, [selectedDataPointsData])
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ACTIONS
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const initializeCatalog = useCallback(
|
|
(tid: string) => {
|
|
const catalog = createDefaultCatalog(tid)
|
|
dispatch({ type: 'SET_CATALOG', payload: catalog })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const loadCatalog = useCallback(
|
|
async (tid: string) => {
|
|
dispatch({ type: 'SET_LOADING', payload: true })
|
|
dispatch({ type: 'SET_ERROR', payload: null })
|
|
|
|
try {
|
|
const response = await fetch(`/api/sdk/v1/einwilligungen/catalog`, {
|
|
headers: { 'X-Tenant-ID': tid },
|
|
})
|
|
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
dispatch({ type: 'SET_CATALOG', payload: data.catalog })
|
|
if (data.companyInfo) {
|
|
dispatch({ type: 'SET_COMPANY_INFO', payload: data.companyInfo })
|
|
}
|
|
if (data.cookieBannerConfig) {
|
|
dispatch({ type: 'SET_COOKIE_BANNER_CONFIG', payload: data.cookieBannerConfig })
|
|
}
|
|
} else if (response.status === 404) {
|
|
initializeCatalog(tid)
|
|
} else {
|
|
throw new Error('Failed to load catalog')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading catalog:', error)
|
|
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Laden des Katalogs' })
|
|
initializeCatalog(tid)
|
|
} finally {
|
|
dispatch({ type: 'SET_LOADING', payload: false })
|
|
}
|
|
},
|
|
[dispatch, initializeCatalog]
|
|
)
|
|
|
|
const saveCatalog = useCallback(async () => {
|
|
if (!state.catalog) return
|
|
|
|
dispatch({ type: 'SET_SAVING', payload: true })
|
|
dispatch({ type: 'SET_ERROR', payload: null })
|
|
|
|
try {
|
|
const response = await fetch(`/api/sdk/v1/einwilligungen/catalog`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Tenant-ID': state.catalog.tenantId,
|
|
},
|
|
body: JSON.stringify({
|
|
catalog: state.catalog,
|
|
companyInfo: state.companyInfo,
|
|
cookieBannerConfig: state.cookieBannerConfig,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to save catalog')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving catalog:', error)
|
|
dispatch({ type: 'SET_ERROR', payload: 'Fehler beim Speichern des Katalogs' })
|
|
} finally {
|
|
dispatch({ type: 'SET_SAVING', payload: false })
|
|
}
|
|
}, [state.catalog, state.companyInfo, state.cookieBannerConfig, dispatch])
|
|
|
|
const toggleDataPoint = useCallback(
|
|
(id: string) => {
|
|
dispatch({ type: 'TOGGLE_DATA_POINT', payload: id })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const addCustomDataPoint = useCallback(
|
|
(dataPoint: DataPoint) => {
|
|
dispatch({ type: 'ADD_CUSTOM_DATA_POINT', payload: { ...dataPoint, isCustom: true } })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const updateDataPoint = useCallback(
|
|
(id: string, data: Partial<DataPoint>) => {
|
|
dispatch({ type: 'UPDATE_DATA_POINT', payload: { id, data } })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const deleteCustomDataPoint = useCallback(
|
|
(id: string) => {
|
|
dispatch({ type: 'DELETE_CUSTOM_DATA_POINT', payload: id })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const setActiveTab = useCallback(
|
|
(tab: EinwilligungenTab) => {
|
|
dispatch({ type: 'SET_ACTIVE_TAB', payload: tab })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const setPreviewLanguage = useCallback(
|
|
(language: SupportedLanguage) => {
|
|
dispatch({ type: 'SET_PREVIEW_LANGUAGE', payload: language })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const setPreviewFormat = useCallback(
|
|
(format: ExportFormat) => {
|
|
dispatch({ type: 'SET_PREVIEW_FORMAT', payload: format })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const setCompanyInfo = useCallback(
|
|
(info: CompanyInfo) => {
|
|
dispatch({ type: 'SET_COMPANY_INFO', payload: info })
|
|
},
|
|
[dispatch]
|
|
)
|
|
|
|
const generatePrivacyPolicy = useCallback(async () => {
|
|
if (!state.catalog || !state.companyInfo) {
|
|
dispatch({ type: 'SET_ERROR', payload: 'Bitte zuerst Firmendaten eingeben' })
|
|
return
|
|
}
|
|
|
|
dispatch({ type: 'SET_LOADING', payload: true })
|
|
dispatch({ type: 'SET_ERROR', payload: null })
|
|
|
|
try {
|
|
const response = await fetch(`/api/sdk/v1/einwilligungen/privacy-policy/generate`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Tenant-ID': state.catalog.tenantId,
|
|
},
|
|
body: JSON.stringify({
|
|
dataPointIds: state.selectedDataPoints,
|
|
companyInfo: state.companyInfo,
|
|
language: state.previewLanguage,
|
|
format: state.previewFormat,
|
|
}),
|
|
})
|
|
|
|
if (response.ok) {
|
|
const policy = await response.json()
|
|
dispatch({ type: 'SET_PRIVACY_POLICY', payload: policy })
|
|
} else {
|
|
throw new Error('Failed to generate privacy policy')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error generating privacy policy:', error)
|
|
dispatch({ type: 'SET_ERROR', payload: 'Fehler bei der Generierung der Datenschutzerklaerung' })
|
|
} finally {
|
|
dispatch({ type: 'SET_LOADING', payload: false })
|
|
}
|
|
}, [
|
|
state.catalog,
|
|
state.companyInfo,
|
|
state.selectedDataPoints,
|
|
state.previewLanguage,
|
|
state.previewFormat,
|
|
dispatch,
|
|
])
|
|
|
|
const generateCookieBannerConfig = useCallback(() => {
|
|
if (!state.catalog) return
|
|
|
|
const config: CookieBannerConfig = {
|
|
id: `cookie-banner-${state.catalog.tenantId}`,
|
|
tenantId: state.catalog.tenantId,
|
|
categories: DEFAULT_COOKIE_CATEGORIES.map((cat) => ({
|
|
...cat,
|
|
dataPointIds: cat.dataPointIds.filter((id) => state.selectedDataPoints.includes(id)),
|
|
})),
|
|
styling: {
|
|
position: 'BOTTOM',
|
|
theme: 'LIGHT',
|
|
primaryColor: '#6366f1',
|
|
borderRadius: 12,
|
|
},
|
|
texts: {
|
|
title: { de: 'Cookie-Einstellungen', en: 'Cookie Settings' },
|
|
description: {
|
|
de: 'Wir verwenden Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen.',
|
|
en: 'We use cookies to provide you with the best possible experience on our website.',
|
|
},
|
|
acceptAll: { de: 'Alle akzeptieren', en: 'Accept All' },
|
|
rejectAll: { de: 'Alle ablehnen', en: 'Reject All' },
|
|
customize: { de: 'Anpassen', en: 'Customize' },
|
|
save: { de: 'Auswahl speichern', en: 'Save Selection' },
|
|
privacyPolicyLink: { de: 'Datenschutzerklaerung', en: 'Privacy Policy' },
|
|
},
|
|
updatedAt: new Date(),
|
|
}
|
|
|
|
dispatch({ type: 'SET_COOKIE_BANNER_CONFIG', payload: config })
|
|
}, [state.catalog, state.selectedDataPoints, dispatch])
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CONTEXT VALUE
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const value: EinwilligungenContextValue = {
|
|
state,
|
|
dispatch,
|
|
allDataPoints,
|
|
selectedDataPointsData,
|
|
dataPointsByCategory,
|
|
categoryStats,
|
|
riskStats,
|
|
legalBasisStats,
|
|
initializeCatalog,
|
|
loadCatalog,
|
|
saveCatalog,
|
|
toggleDataPoint,
|
|
addCustomDataPoint,
|
|
updateDataPoint,
|
|
deleteCustomDataPoint,
|
|
setActiveTab,
|
|
setPreviewLanguage,
|
|
setPreviewFormat,
|
|
setCompanyInfo,
|
|
generatePrivacyPolicy,
|
|
generateCookieBannerConfig,
|
|
}
|
|
|
|
return (
|
|
<EinwilligungenContext.Provider value={value}>{children}</EinwilligungenContext.Provider>
|
|
)
|
|
}
|