Initial commit: breakpilot-compliance - Compliance SDK Platform

Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -0,0 +1,511 @@
/**
* React Integration fuer @breakpilot/consent-sdk
*
* @example
* ```tsx
* import { ConsentProvider, useConsent, ConsentBanner } from '@breakpilot/consent-sdk/react';
*
* function App() {
* return (
* <ConsentProvider config={config}>
* <ConsentBanner />
* <MainContent />
* </ConsentProvider>
* );
* }
* ```
*/
import {
createContext,
useContext,
useEffect,
useState,
useCallback,
useMemo,
type ReactNode,
type FC,
} from 'react';
import { ConsentManager } from '../core/ConsentManager';
import type {
ConsentConfig,
ConsentState,
ConsentCategory,
ConsentCategories,
} from '../types';
// =============================================================================
// Context
// =============================================================================
interface ConsentContextValue {
/** ConsentManager Instanz */
manager: ConsentManager | null;
/** Aktueller Consent-State */
consent: ConsentState | null;
/** Ist SDK initialisiert? */
isInitialized: boolean;
/** Wird geladen? */
isLoading: boolean;
/** Ist Banner sichtbar? */
isBannerVisible: boolean;
/** Wird Consent benoetigt? */
needsConsent: boolean;
/** Consent fuer Kategorie pruefen */
hasConsent: (category: ConsentCategory) => boolean;
/** Alle akzeptieren */
acceptAll: () => Promise<void>;
/** Alle ablehnen */
rejectAll: () => Promise<void>;
/** Auswahl speichern */
saveSelection: (categories: Partial<ConsentCategories>) => Promise<void>;
/** Banner anzeigen */
showBanner: () => void;
/** Banner verstecken */
hideBanner: () => void;
/** Einstellungen oeffnen */
showSettings: () => void;
}
const ConsentContext = createContext<ConsentContextValue | null>(null);
// =============================================================================
// Provider
// =============================================================================
interface ConsentProviderProps {
/** SDK-Konfiguration */
config: ConsentConfig;
/** Kinder-Komponenten */
children: ReactNode;
}
/**
* ConsentProvider - Stellt Consent-Kontext bereit
*/
export const ConsentProvider: FC<ConsentProviderProps> = ({
config,
children,
}) => {
const [manager, setManager] = useState<ConsentManager | null>(null);
const [consent, setConsent] = useState<ConsentState | null>(null);
const [isInitialized, setIsInitialized] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isBannerVisible, setIsBannerVisible] = useState(false);
// Manager erstellen und initialisieren
useEffect(() => {
const consentManager = new ConsentManager(config);
setManager(consentManager);
// Events abonnieren
const unsubChange = consentManager.on('change', (newConsent) => {
setConsent(newConsent);
});
const unsubBannerShow = consentManager.on('banner_show', () => {
setIsBannerVisible(true);
});
const unsubBannerHide = consentManager.on('banner_hide', () => {
setIsBannerVisible(false);
});
// Initialisieren
consentManager
.init()
.then(() => {
setConsent(consentManager.getConsent());
setIsInitialized(true);
setIsLoading(false);
setIsBannerVisible(consentManager.isBannerVisible());
})
.catch((error) => {
console.error('Failed to initialize ConsentManager:', error);
setIsLoading(false);
});
// Cleanup
return () => {
unsubChange();
unsubBannerShow();
unsubBannerHide();
};
}, [config]);
// Callback-Funktionen
const hasConsent = useCallback(
(category: ConsentCategory): boolean => {
return manager?.hasConsent(category) ?? category === 'essential';
},
[manager]
);
const acceptAll = useCallback(async () => {
await manager?.acceptAll();
}, [manager]);
const rejectAll = useCallback(async () => {
await manager?.rejectAll();
}, [manager]);
const saveSelection = useCallback(
async (categories: Partial<ConsentCategories>) => {
await manager?.setConsent(categories);
manager?.hideBanner();
},
[manager]
);
const showBanner = useCallback(() => {
manager?.showBanner();
}, [manager]);
const hideBanner = useCallback(() => {
manager?.hideBanner();
}, [manager]);
const showSettings = useCallback(() => {
manager?.showSettings();
}, [manager]);
const needsConsent = useMemo(() => {
return manager?.needsConsent() ?? true;
}, [manager, consent]);
// Context-Wert
const contextValue = useMemo<ConsentContextValue>(
() => ({
manager,
consent,
isInitialized,
isLoading,
isBannerVisible,
needsConsent,
hasConsent,
acceptAll,
rejectAll,
saveSelection,
showBanner,
hideBanner,
showSettings,
}),
[
manager,
consent,
isInitialized,
isLoading,
isBannerVisible,
needsConsent,
hasConsent,
acceptAll,
rejectAll,
saveSelection,
showBanner,
hideBanner,
showSettings,
]
);
return (
<ConsentContext.Provider value={contextValue}>
{children}
</ConsentContext.Provider>
);
};
// =============================================================================
// Hooks
// =============================================================================
/**
* useConsent - Hook fuer Consent-Zugriff
*
* @example
* ```tsx
* const { hasConsent, acceptAll, rejectAll } = useConsent();
*
* if (hasConsent('analytics')) {
* // Analytics laden
* }
* ```
*/
export function useConsent(): ConsentContextValue;
export function useConsent(
category: ConsentCategory
): ConsentContextValue & { allowed: boolean };
export function useConsent(category?: ConsentCategory) {
const context = useContext(ConsentContext);
if (!context) {
throw new Error('useConsent must be used within a ConsentProvider');
}
if (category) {
return {
...context,
allowed: context.hasConsent(category),
};
}
return context;
}
/**
* useConsentManager - Direkter Zugriff auf ConsentManager
*/
export function useConsentManager(): ConsentManager | null {
const context = useContext(ConsentContext);
return context?.manager ?? null;
}
// =============================================================================
// Components
// =============================================================================
interface ConsentGateProps {
/** Erforderliche Kategorie */
category: ConsentCategory;
/** Inhalt bei Consent */
children: ReactNode;
/** Inhalt ohne Consent */
placeholder?: ReactNode;
/** Fallback waehrend Laden */
fallback?: ReactNode;
}
/**
* ConsentGate - Zeigt Inhalt nur bei Consent
*
* @example
* ```tsx
* <ConsentGate
* category="analytics"
* placeholder={<ConsentPlaceholder category="analytics" />}
* >
* <GoogleAnalytics />
* </ConsentGate>
* ```
*/
export const ConsentGate: FC<ConsentGateProps> = ({
category,
children,
placeholder = null,
fallback = null,
}) => {
const { hasConsent, isLoading } = useConsent();
if (isLoading) {
return <>{fallback}</>;
}
if (!hasConsent(category)) {
return <>{placeholder}</>;
}
return <>{children}</>;
};
interface ConsentPlaceholderProps {
/** Kategorie */
category: ConsentCategory;
/** Custom Nachricht */
message?: string;
/** Custom Button-Text */
buttonText?: string;
/** Custom Styling */
className?: string;
}
/**
* ConsentPlaceholder - Placeholder fuer blockierten Inhalt
*/
export const ConsentPlaceholder: FC<ConsentPlaceholderProps> = ({
category,
message,
buttonText,
className = '',
}) => {
const { showSettings } = useConsent();
const categoryNames: Record<ConsentCategory, string> = {
essential: 'Essentielle Cookies',
functional: 'Funktionale Cookies',
analytics: 'Statistik-Cookies',
marketing: 'Marketing-Cookies',
social: 'Social Media-Cookies',
};
const defaultMessage = `Dieser Inhalt erfordert ${categoryNames[category]}.`;
return (
<div className={`bp-consent-placeholder ${className}`}>
<p>{message || defaultMessage}</p>
<button type="button" onClick={showSettings}>
{buttonText || 'Cookie-Einstellungen oeffnen'}
</button>
</div>
);
};
// =============================================================================
// Banner Component (Headless)
// =============================================================================
interface ConsentBannerRenderProps {
/** Ist Banner sichtbar? */
isVisible: boolean;
/** Aktueller Consent */
consent: ConsentState | null;
/** Wird Consent benoetigt? */
needsConsent: boolean;
/** Alle akzeptieren */
onAcceptAll: () => void;
/** Alle ablehnen */
onRejectAll: () => void;
/** Auswahl speichern */
onSaveSelection: (categories: Partial<ConsentCategories>) => void;
/** Einstellungen oeffnen */
onShowSettings: () => void;
/** Banner schliessen */
onClose: () => void;
}
interface ConsentBannerProps {
/** Render-Funktion fuer Custom UI */
render?: (props: ConsentBannerRenderProps) => ReactNode;
/** Custom Styling */
className?: string;
}
/**
* ConsentBanner - Headless Banner-Komponente
*
* Kann mit eigener UI gerendert werden oder nutzt Default-UI.
*
* @example
* ```tsx
* // Mit eigener UI
* <ConsentBanner
* render={({ isVisible, onAcceptAll, onRejectAll }) => (
* isVisible && (
* <div className="my-banner">
* <button onClick={onAcceptAll}>Accept</button>
* <button onClick={onRejectAll}>Reject</button>
* </div>
* )
* )}
* />
*
* // Mit Default-UI
* <ConsentBanner />
* ```
*/
export const ConsentBanner: FC<ConsentBannerProps> = ({ render, className }) => {
const {
consent,
isBannerVisible,
needsConsent,
acceptAll,
rejectAll,
saveSelection,
showSettings,
hideBanner,
} = useConsent();
const renderProps: ConsentBannerRenderProps = {
isVisible: isBannerVisible,
consent,
needsConsent,
onAcceptAll: acceptAll,
onRejectAll: rejectAll,
onSaveSelection: saveSelection,
onShowSettings: showSettings,
onClose: hideBanner,
};
// Custom Render
if (render) {
return <>{render(renderProps)}</>;
}
// Default UI
if (!isBannerVisible) {
return null;
}
return (
<div
className={`bp-consent-banner ${className || ''}`}
role="dialog"
aria-modal="true"
aria-label="Cookie-Einstellungen"
>
<div className="bp-consent-banner-content">
<h2>Datenschutzeinstellungen</h2>
<p>
Wir nutzen Cookies und aehnliche Technologien, um Ihnen ein optimales
Nutzererlebnis zu bieten.
</p>
<div className="bp-consent-banner-actions">
<button
type="button"
className="bp-consent-btn bp-consent-btn-reject"
onClick={rejectAll}
>
Alle ablehnen
</button>
<button
type="button"
className="bp-consent-btn bp-consent-btn-settings"
onClick={showSettings}
>
Einstellungen
</button>
<button
type="button"
className="bp-consent-btn bp-consent-btn-accept"
onClick={acceptAll}
>
Alle akzeptieren
</button>
</div>
</div>
</div>
);
};
// =============================================================================
// Exports
// =============================================================================
export { ConsentContext };
export type { ConsentContextValue, ConsentBannerRenderProps };