This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/consent-sdk/src/vue/index.ts
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

512 lines
13 KiB
TypeScript

/**
* Vue 3 Integration fuer @breakpilot/consent-sdk
*
* @example
* ```vue
* <script setup>
* import { useConsent, ConsentBanner, ConsentGate } from '@breakpilot/consent-sdk/vue';
*
* const { hasConsent, acceptAll, rejectAll } = useConsent();
* </script>
*
* <template>
* <ConsentBanner />
* <ConsentGate category="analytics">
* <AnalyticsComponent />
* </ConsentGate>
* </template>
* ```
*/
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<ConsentContext> = Symbol('consent');
// =============================================================================
// Types
// =============================================================================
interface ConsentContext {
manager: Ref<ConsentManager | null>;
consent: Ref<ConsentState | null>;
isInitialized: Ref<boolean>;
isLoading: Ref<boolean>;
isBannerVisible: Ref<boolean>;
needsConsent: Ref<boolean>;
hasConsent: (category: ConsentCategory) => boolean;
acceptAll: () => Promise<void>;
rejectAll: () => Promise<void>;
saveSelection: (categories: Partial<ConsentCategories>) => Promise<void>;
showBanner: () => void;
hideBanner: () => void;
showSettings: () => void;
}
// =============================================================================
// Composable: useConsent
// =============================================================================
/**
* Haupt-Composable fuer Consent-Zugriff
*
* @example
* ```vue
* <script setup>
* const { hasConsent, acceptAll } = useConsent();
*
* if (hasConsent('analytics')) {
* // Analytics laden
* }
* </script>
* ```
*/
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
* <script setup>
* import { provideConsent } from '@breakpilot/consent-sdk/vue';
*
* provideConsent({
* apiEndpoint: 'https://consent.example.com/api/v1',
* siteId: 'site_abc123',
* });
* </script>
* ```
*/
export function provideConsent(config: ConsentConfig): ConsentContext {
const manager = ref<ConsentManager | null>(null);
const consent = ref<ConsentState | null>(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<void> => {
await manager.value?.acceptAll();
};
const rejectAll = async (): Promise<void> => {
await manager.value?.rejectAll();
};
const saveSelection = async (categories: Partial<ConsentCategories>): Promise<void> => {
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<ConsentManager | null>,
consent: readonly(consent) as Ref<ConsentState | null>,
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
* <ConsentProvider :config="config">
* <App />
* </ConsentProvider>
* ```
*/
export const ConsentProvider = defineComponent({
name: 'ConsentProvider',
props: {
config: {
type: Object as PropType<ConsentConfig>,
required: true,
},
},
setup(props, { slots }) {
provideConsent(props.config);
return () => slots.default?.();
},
});
/**
* ConsentGate - Zeigt Inhalt nur bei Consent
*
* @example
* ```vue
* <ConsentGate category="analytics">
* <template #default>
* <AnalyticsComponent />
* </template>
* <template #placeholder>
* <p>Bitte akzeptieren Sie Statistik-Cookies.</p>
* </template>
* </ConsentGate>
* ```
*/
export const ConsentGate = defineComponent({
name: 'ConsentGate',
props: {
category: {
type: String as PropType<ConsentCategory>,
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
* <ConsentPlaceholder category="marketing" />
* ```
*/
export const ConsentPlaceholder = defineComponent({
name: 'ConsentPlaceholder',
props: {
category: {
type: String as PropType<ConsentCategory>,
required: true,
},
message: {
type: String,
default: '',
},
buttonText: {
type: String,
default: 'Cookie-Einstellungen öffnen',
},
},
setup(props) {
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 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
* <ConsentBanner>
* <template #default="{ isVisible, onAcceptAll, onRejectAll, onShowSettings }">
* <div v-if="isVisible" class="my-banner">
* <button @click="onAcceptAll">Accept</button>
* <button @click="onRejectAll">Reject</button>
* </div>
* </template>
* </ConsentBanner>
* ```
*/
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<ConsentState | null>(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<ConsentManager | null>,
consent: consent as Ref<ConsentState | null>,
isInitialized,
isLoading,
isBannerVisible,
needsConsent: computed(() => manager.needsConsent()),
hasConsent: (category: ConsentCategory) => manager.hasConsent(category),
acceptAll: () => manager.acceptAll(),
rejectAll: () => manager.rejectAll(),
saveSelection: async (categories: Partial<ConsentCategories>) => {
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 };