4ed39d2616
Phase 4: extract config defaults, Google Consent Mode helper, and framework adapter internals into sibling files so every source file is under the hard cap. Public API surface preserved; all 135 tests green, tsup build + tsc typecheck clean. - core/ConsentManager 525 -> 467 LOC (extract config + google helpers) - react/index 511 LOC -> 199 LOC barrel + components/hooks/context - vue/index 511 LOC -> 32 LOC barrel + components/composables/context/plugin - angular/index 509 LOC -> 45 LOC barrel + interface/service/module/templates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
191 lines
4.7 KiB
TypeScript
191 lines
4.7 KiB
TypeScript
/**
|
|
* React UI components for the consent SDK.
|
|
*
|
|
* Phase 4: extracted from index.tsx to keep the main file under 500 LOC.
|
|
* Exports ConsentGate, ConsentPlaceholder, and ConsentBanner (all headless).
|
|
*/
|
|
|
|
import type { FC, ReactNode } from 'react';
|
|
import type {
|
|
ConsentCategories,
|
|
ConsentCategory,
|
|
ConsentState,
|
|
} from '../types';
|
|
import { useConsent } from './hooks';
|
|
|
|
// =============================================================================
|
|
// ConsentGate
|
|
// =============================================================================
|
|
|
|
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.
|
|
*/
|
|
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}</>;
|
|
};
|
|
|
|
// =============================================================================
|
|
// ConsentPlaceholder
|
|
// =============================================================================
|
|
|
|
interface ConsentPlaceholderProps {
|
|
category: ConsentCategory;
|
|
message?: string;
|
|
buttonText?: string;
|
|
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>
|
|
);
|
|
};
|
|
|
|
// =============================================================================
|
|
// ConsentBanner (headless)
|
|
// =============================================================================
|
|
|
|
export interface ConsentBannerRenderProps {
|
|
isVisible: boolean;
|
|
consent: ConsentState | null;
|
|
needsConsent: boolean;
|
|
onAcceptAll: () => void;
|
|
onRejectAll: () => void;
|
|
onSaveSelection: (categories: Partial<ConsentCategories>) => void;
|
|
onShowSettings: () => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
interface ConsentBannerProps {
|
|
render?: (props: ConsentBannerRenderProps) => ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* ConsentBanner - Headless Banner-Komponente.
|
|
* Kann mit eigener UI gerendert werden oder nutzt Default-UI.
|
|
*/
|
|
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,
|
|
};
|
|
|
|
if (render) {
|
|
return <>{render(renderProps)}</>;
|
|
}
|
|
|
|
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>
|
|
);
|
|
};
|