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>
This commit is contained in:
511
consent-sdk/src/vue/index.ts
Normal file
511
consent-sdk/src/vue/index.ts
Normal file
@@ -0,0 +1,511 @@
|
||||
/**
|
||||
* 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 };
|
||||
Reference in New Issue
Block a user