/** * Vue 3 Integration fuer @breakpilot/consent-sdk * * @example * ```vue * * * * * * * * * ``` */ import { ref, computed, readonly, inject, provide, onMounted, onUnmounted, defineComponent, h, type Ref, type InjectionKey, type PropType, } from 'vue'; import { ConsentManager } from '../core/ConsentManager'; import type { ConsentConfig, ConsentState, ConsentCategory, ConsentCategories, } from '../types'; // ============================================================================= // Injection Key // ============================================================================= const CONSENT_KEY: InjectionKey = Symbol('consent'); // ============================================================================= // Types // ============================================================================= interface ConsentContext { manager: Ref; consent: Ref; isInitialized: Ref; isLoading: Ref; isBannerVisible: Ref; needsConsent: Ref; hasConsent: (category: ConsentCategory) => boolean; acceptAll: () => Promise; rejectAll: () => Promise; saveSelection: (categories: Partial) => Promise; showBanner: () => void; hideBanner: () => void; showSettings: () => void; } // ============================================================================= // Composable: useConsent // ============================================================================= /** * Haupt-Composable fuer Consent-Zugriff * * @example * ```vue * * ``` */ export function useConsent(): ConsentContext { const context = inject(CONSENT_KEY); if (!context) { throw new Error( 'useConsent() must be used within a component that has called provideConsent() or is wrapped in ConsentProvider' ); } return context; } /** * Consent-Provider einrichten (in App.vue aufrufen) * * @example * ```vue * * ``` */ export function provideConsent(config: ConsentConfig): ConsentContext { const manager = ref(null); const consent = ref(null); const isInitialized = ref(false); const isLoading = ref(true); const isBannerVisible = ref(false); const needsConsent = computed(() => { return manager.value?.needsConsent() ?? true; }); // Initialisierung onMounted(async () => { const consentManager = new ConsentManager(config); manager.value = consentManager; // Events abonnieren const unsubChange = consentManager.on('change', (newConsent) => { consent.value = newConsent; }); const unsubBannerShow = consentManager.on('banner_show', () => { isBannerVisible.value = true; }); const unsubBannerHide = consentManager.on('banner_hide', () => { isBannerVisible.value = false; }); try { await consentManager.init(); consent.value = consentManager.getConsent(); isInitialized.value = true; isBannerVisible.value = consentManager.isBannerVisible(); } catch (error) { console.error('Failed to initialize ConsentManager:', error); } finally { isLoading.value = false; } // Cleanup bei Unmount onUnmounted(() => { unsubChange(); unsubBannerShow(); unsubBannerHide(); }); }); // Methoden const hasConsent = (category: ConsentCategory): boolean => { return manager.value?.hasConsent(category) ?? category === 'essential'; }; const acceptAll = async (): Promise => { await manager.value?.acceptAll(); }; const rejectAll = async (): Promise => { await manager.value?.rejectAll(); }; const saveSelection = async (categories: Partial): Promise => { await manager.value?.setConsent(categories); manager.value?.hideBanner(); }; const showBanner = (): void => { manager.value?.showBanner(); }; const hideBanner = (): void => { manager.value?.hideBanner(); }; const showSettings = (): void => { manager.value?.showSettings(); }; const context: ConsentContext = { manager: readonly(manager) as Ref, consent: readonly(consent) as Ref, isInitialized: readonly(isInitialized), isLoading: readonly(isLoading), isBannerVisible: readonly(isBannerVisible), needsConsent, hasConsent, acceptAll, rejectAll, saveSelection, showBanner, hideBanner, showSettings, }; provide(CONSENT_KEY, context); return context; } // ============================================================================= // Components // ============================================================================= /** * ConsentProvider - Wrapper-Komponente * * @example * ```vue * * * * ``` */ export const ConsentProvider = defineComponent({ name: 'ConsentProvider', props: { config: { type: Object as PropType, required: true, }, }, setup(props, { slots }) { provideConsent(props.config); return () => slots.default?.(); }, }); /** * ConsentGate - Zeigt Inhalt nur bei Consent * * @example * ```vue * * * * * * Bitte akzeptieren Sie Statistik-Cookies. * * * ``` */ export const ConsentGate = defineComponent({ name: 'ConsentGate', props: { category: { type: String as PropType, required: true, }, }, setup(props, { slots }) { const { hasConsent, isLoading } = useConsent(); return () => { if (isLoading.value) { return slots.fallback?.() ?? null; } if (!hasConsent(props.category)) { return slots.placeholder?.() ?? null; } return slots.default?.(); }; }, }); /** * ConsentPlaceholder - Placeholder fuer blockierten Inhalt * * @example * ```vue * * ``` */ export const ConsentPlaceholder = defineComponent({ name: 'ConsentPlaceholder', props: { category: { type: String as PropType, required: true, }, message: { type: String, default: '', }, buttonText: { type: String, default: 'Cookie-Einstellungen öffnen', }, }, setup(props) { const { showSettings } = useConsent(); const categoryNames: Record = { essential: 'Essentielle Cookies', functional: 'Funktionale Cookies', analytics: 'Statistik-Cookies', marketing: 'Marketing-Cookies', social: 'Social Media-Cookies', }; const displayMessage = computed(() => { return props.message || `Dieser Inhalt erfordert ${categoryNames[props.category]}.`; }); return () => h('div', { class: 'bp-consent-placeholder' }, [ h('p', displayMessage.value), h( 'button', { type: 'button', onClick: showSettings, }, props.buttonText ), ]); }, }); /** * ConsentBanner - Cookie-Banner Komponente * * @example * ```vue * * * * Accept * Reject * * * * ``` */ export const ConsentBanner = defineComponent({ name: 'ConsentBanner', setup(_, { slots }) { const { consent, isBannerVisible, needsConsent, acceptAll, rejectAll, saveSelection, showSettings, hideBanner, } = useConsent(); const slotProps = computed(() => ({ isVisible: isBannerVisible.value, consent: consent.value, needsConsent: needsConsent.value, onAcceptAll: acceptAll, onRejectAll: rejectAll, onSaveSelection: saveSelection, onShowSettings: showSettings, onClose: hideBanner, })); return () => { // Custom Slot if (slots.default) { return slots.default(slotProps.value); } // Default UI if (!isBannerVisible.value) { return null; } return h( 'div', { class: 'bp-consent-banner', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Cookie-Einstellungen', }, [ h('div', { class: 'bp-consent-banner-content' }, [ h('h2', 'Datenschutzeinstellungen'), h( 'p', 'Wir nutzen Cookies und ähnliche Technologien, um Ihnen ein optimales Nutzererlebnis zu bieten.' ), h('div', { class: 'bp-consent-banner-actions' }, [ h( 'button', { type: 'button', class: 'bp-consent-btn bp-consent-btn-reject', onClick: rejectAll, }, 'Alle ablehnen' ), h( 'button', { type: 'button', class: 'bp-consent-btn bp-consent-btn-settings', onClick: showSettings, }, 'Einstellungen' ), h( 'button', { type: 'button', class: 'bp-consent-btn bp-consent-btn-accept', onClick: acceptAll, }, 'Alle akzeptieren' ), ]), ]), ] ); }; }, }); // ============================================================================= // Plugin // ============================================================================= /** * Vue Plugin fuer globale Installation * * @example * ```ts * import { createApp } from 'vue'; * import { ConsentPlugin } from '@breakpilot/consent-sdk/vue'; * * const app = createApp(App); * app.use(ConsentPlugin, { * apiEndpoint: 'https://consent.example.com/api/v1', * siteId: 'site_abc123', * }); * ``` */ export const ConsentPlugin = { install(app: { provide: (key: symbol | string, value: unknown) => void }, config: ConsentConfig) { const manager = new ConsentManager(config); const consent = ref(null); const isInitialized = ref(false); const isLoading = ref(true); const isBannerVisible = ref(false); // Initialisieren manager.init().then(() => { consent.value = manager.getConsent(); isInitialized.value = true; isLoading.value = false; isBannerVisible.value = manager.isBannerVisible(); }); // Events manager.on('change', (newConsent) => { consent.value = newConsent; }); manager.on('banner_show', () => { isBannerVisible.value = true; }); manager.on('banner_hide', () => { isBannerVisible.value = false; }); const context: ConsentContext = { manager: ref(manager) as Ref, consent: consent as Ref, isInitialized, isLoading, isBannerVisible, needsConsent: computed(() => manager.needsConsent()), hasConsent: (category: ConsentCategory) => manager.hasConsent(category), acceptAll: () => manager.acceptAll(), rejectAll: () => manager.rejectAll(), saveSelection: async (categories: Partial) => { await manager.setConsent(categories); manager.hideBanner(); }, showBanner: () => manager.showBanner(), hideBanner: () => manager.hideBanner(), showSettings: () => manager.showSettings(), }; app.provide(CONSENT_KEY, context); }, }; // ============================================================================= // Exports // ============================================================================= export { CONSENT_KEY }; export type { ConsentContext };
Bitte akzeptieren Sie Statistik-Cookies.