fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { SDKDocsSidebar } from '@/components/developers/SDKDocsSidebar'
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
|
||||
function CopyButton({ text }: { text: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
return (
|
||||
<button onClick={handleCopy} className="p-2 hover:bg-gray-700 rounded transition-colors">
|
||||
{copied ? <Check className="w-4 h-4 text-green-400" /> : <Copy className="w-4 h-4 text-gray-400" />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeBlock({ code, filename }: { code: string; filename?: string }) {
|
||||
return (
|
||||
<div className="bg-gray-900 rounded-lg overflow-hidden">
|
||||
{filename && (
|
||||
<div className="px-4 py-2 bg-gray-800 text-gray-400 text-xs border-b border-gray-700">
|
||||
{filename}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<div className="absolute top-2 right-2">
|
||||
<CopyButton text={code} />
|
||||
</div>
|
||||
<pre className="p-4 text-sm text-gray-300 font-mono overflow-x-auto">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AngularIntegrationPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<SDKDocsSidebar />
|
||||
|
||||
<main className="flex-1 ml-64">
|
||||
<div className="max-w-4xl mx-auto px-8 py-12">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-red-500 flex items-center justify-center">
|
||||
<span className="text-white font-bold">A</span>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Angular Integration</h1>
|
||||
</div>
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
Service und Module fuer Angular 14+ Projekte.
|
||||
</p>
|
||||
|
||||
{/* Installation */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Installation</h2>
|
||||
<CodeBlock code="npm install @breakpilot/consent-sdk" />
|
||||
</section>
|
||||
|
||||
{/* Module Setup */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Module Setup</h2>
|
||||
<CodeBlock
|
||||
filename="app.module.ts"
|
||||
code={`import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ConsentModule } from '@breakpilot/consent-sdk/angular';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
ConsentModule.forRoot({
|
||||
apiEndpoint: environment.consentApi,
|
||||
siteId: 'my-site',
|
||||
debug: !environment.production,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppModule {}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Standalone Setup */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Standalone Setup (Angular 15+)</h2>
|
||||
<CodeBlock
|
||||
filename="app.config.ts"
|
||||
code={`import { ApplicationConfig } from '@angular/core';
|
||||
import { provideConsent } from '@breakpilot/consent-sdk/angular';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideConsent({
|
||||
apiEndpoint: environment.consentApi,
|
||||
siteId: 'my-site',
|
||||
debug: !environment.production,
|
||||
}),
|
||||
],
|
||||
};`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Service Usage */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Service Usage</h2>
|
||||
<CodeBlock
|
||||
filename="components/analytics.component.ts"
|
||||
code={`import { Component, OnInit } from '@angular/core';
|
||||
import { ConsentService } from '@breakpilot/consent-sdk/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-analytics',
|
||||
template: \`
|
||||
<div *ngIf="hasAnalyticsConsent$ | async">
|
||||
<!-- Analytics Code hier -->
|
||||
</div>
|
||||
\`,
|
||||
})
|
||||
export class AnalyticsComponent implements OnInit {
|
||||
hasAnalyticsConsent$ = this.consentService.hasConsent$('analytics');
|
||||
|
||||
constructor(private consentService: ConsentService) {}
|
||||
|
||||
async loadAnalytics() {
|
||||
if (await this.consentService.hasConsent('analytics')) {
|
||||
// Load analytics
|
||||
}
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Cookie Banner */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Cookie Banner Component</h2>
|
||||
<CodeBlock
|
||||
filename="components/cookie-banner.component.ts"
|
||||
code={`import { Component } from '@angular/core';
|
||||
import { ConsentService } from '@breakpilot/consent-sdk/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cookie-banner',
|
||||
template: \`
|
||||
<div
|
||||
*ngIf="isBannerVisible$ | async"
|
||||
class="fixed bottom-0 inset-x-0 bg-white border-t shadow-lg p-4 z-50"
|
||||
>
|
||||
<div class="max-w-4xl mx-auto flex items-center justify-between">
|
||||
<p class="text-sm text-gray-600">
|
||||
Wir verwenden Cookies um Ihr Erlebnis zu verbessern.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button (click)="rejectAll()" class="px-4 py-2 border rounded">
|
||||
Ablehnen
|
||||
</button>
|
||||
<button (click)="showSettings()" class="px-4 py-2 border rounded">
|
||||
Einstellungen
|
||||
</button>
|
||||
<button (click)="acceptAll()" class="px-4 py-2 bg-blue-600 text-white rounded">
|
||||
Alle akzeptieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
\`,
|
||||
})
|
||||
export class CookieBannerComponent {
|
||||
isBannerVisible$ = this.consentService.isBannerVisible$;
|
||||
|
||||
constructor(private consentService: ConsentService) {}
|
||||
|
||||
async acceptAll() {
|
||||
await this.consentService.acceptAll();
|
||||
}
|
||||
|
||||
async rejectAll() {
|
||||
await this.consentService.rejectAll();
|
||||
}
|
||||
|
||||
showSettings() {
|
||||
this.consentService.showSettings();
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Directive */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">ConsentGate Directive</h2>
|
||||
<CodeBlock
|
||||
filename="template.html"
|
||||
code={`<!-- Zeigt Element nur wenn Consent vorhanden -->
|
||||
<iframe
|
||||
*consentGate="'social'"
|
||||
src="https://www.youtube.com/embed/VIDEO_ID"
|
||||
width="560"
|
||||
height="315"
|
||||
></iframe>
|
||||
|
||||
<!-- Mit Custom Fallback -->
|
||||
<div *consentGate="'analytics'; else noConsent">
|
||||
<app-analytics-dashboard></app-analytics-dashboard>
|
||||
</div>
|
||||
<ng-template #noConsent>
|
||||
<div class="bg-gray-100 p-4 rounded-lg text-center">
|
||||
<p>Bitte stimmen Sie Statistik-Cookies zu.</p>
|
||||
<button (click)="showSettings()">Einstellungen</button>
|
||||
</div>
|
||||
</ng-template>`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Service API */}
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Service API</h2>
|
||||
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Property/Method
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Typ
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Beschreibung
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">consent$</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Observable<ConsentState></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Observable des aktuellen Consent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">hasConsent$()</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Observable<boolean></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Reaktive Consent-Pruefung</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">hasConsent()</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Promise<boolean></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Async Consent-Pruefung</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">isBannerVisible$</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Observable<boolean></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Banner-Sichtbarkeit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">acceptAll()</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Promise<void></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Akzeptiert alle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">rejectAll()</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Promise<void></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Lehnt alle ab</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">setConsent()</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Promise<void></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Setzt spezifische Kategorien</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { SDKDocsSidebar } from '@/components/developers/SDKDocsSidebar'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
name: 'React',
|
||||
href: '/developers/sdk/consent/frameworks/react',
|
||||
logo: '/logos/react.svg',
|
||||
description: 'Hooks und Provider fuer React 17+ und Next.js',
|
||||
features: ['ConsentProvider', 'useConsent Hook', 'ConsentGate Component'],
|
||||
color: 'bg-cyan-500',
|
||||
},
|
||||
{
|
||||
name: 'Vue 3',
|
||||
href: '/developers/sdk/consent/frameworks/vue',
|
||||
logo: '/logos/vue.svg',
|
||||
description: 'Composables und Plugin fuer Vue 3 und Nuxt',
|
||||
features: ['Vue Plugin', 'useConsent Composable', 'ConsentGate Component'],
|
||||
color: 'bg-emerald-500',
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
href: '/developers/sdk/consent/frameworks/angular',
|
||||
logo: '/logos/angular.svg',
|
||||
description: 'Service und Module fuer Angular 14+',
|
||||
features: ['ConsentService', 'ConsentModule', 'Dependency Injection'],
|
||||
color: 'bg-red-500',
|
||||
},
|
||||
]
|
||||
|
||||
export default function FrameworksPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<SDKDocsSidebar />
|
||||
|
||||
<main className="flex-1 ml-64">
|
||||
<div className="max-w-4xl mx-auto px-8 py-12">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">Framework Integration</h1>
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
Das Consent SDK bietet native Integrationen fuer alle gaengigen Frontend-Frameworks.
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{frameworks.map((framework) => (
|
||||
<Link
|
||||
key={framework.name}
|
||||
href={framework.href}
|
||||
className="block bg-white rounded-xl border border-gray-200 p-6 hover:border-violet-300 hover:shadow-md transition-all group"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className={`w-12 h-12 rounded-xl ${framework.color} flex items-center justify-center shrink-0`}>
|
||||
<span className="text-white font-bold text-lg">{framework.name[0]}</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-xl font-semibold text-gray-900 group-hover:text-violet-600 transition-colors">
|
||||
{framework.name}
|
||||
</h2>
|
||||
<ChevronRight className="w-5 h-5 text-gray-400 group-hover:text-violet-600 transition-colors" />
|
||||
</div>
|
||||
<p className="text-gray-600 mt-1">{framework.description}</p>
|
||||
<div className="flex flex-wrap gap-2 mt-3">
|
||||
{framework.features.map((feature) => (
|
||||
<span
|
||||
key={feature}
|
||||
className="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-md"
|
||||
>
|
||||
{feature}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Vanilla JS Note */}
|
||||
<div className="mt-8 p-4 bg-blue-50 border border-blue-200 rounded-xl">
|
||||
<h3 className="font-medium text-blue-900">Vanilla JavaScript</h3>
|
||||
<p className="text-sm text-blue-700 mt-1">
|
||||
Sie koennen das SDK auch ohne Framework verwenden. Importieren Sie einfach den ConsentManager direkt
|
||||
aus dem Hauptpaket. Siehe{' '}
|
||||
<Link href="/developers/sdk/consent/installation" className="underline">
|
||||
Installation
|
||||
</Link>{' '}
|
||||
fuer Details.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { SDKDocsSidebar } from '@/components/developers/SDKDocsSidebar'
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
|
||||
function CopyButton({ text }: { text: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
return (
|
||||
<button onClick={handleCopy} className="p-2 hover:bg-gray-700 rounded transition-colors">
|
||||
{copied ? <Check className="w-4 h-4 text-green-400" /> : <Copy className="w-4 h-4 text-gray-400" />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeBlock({ code, filename }: { code: string; filename?: string }) {
|
||||
return (
|
||||
<div className="bg-gray-900 rounded-lg overflow-hidden">
|
||||
{filename && (
|
||||
<div className="px-4 py-2 bg-gray-800 text-gray-400 text-xs border-b border-gray-700">
|
||||
{filename}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<div className="absolute top-2 right-2">
|
||||
<CopyButton text={code} />
|
||||
</div>
|
||||
<pre className="p-4 text-sm text-gray-300 font-mono overflow-x-auto">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ReactIntegrationPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<SDKDocsSidebar />
|
||||
|
||||
<main className="flex-1 ml-64">
|
||||
<div className="max-w-4xl mx-auto px-8 py-12">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-cyan-500 flex items-center justify-center">
|
||||
<span className="text-white font-bold">R</span>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">React Integration</h1>
|
||||
</div>
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
Hooks und Provider fuer React 17+ und Next.js Projekte.
|
||||
</p>
|
||||
|
||||
{/* Installation */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Installation</h2>
|
||||
<CodeBlock code="npm install @breakpilot/consent-sdk" />
|
||||
</section>
|
||||
|
||||
{/* Provider Setup */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Provider Setup</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Umschliessen Sie Ihre App mit dem ConsentProvider:
|
||||
</p>
|
||||
<CodeBlock
|
||||
filename="app/layout.tsx"
|
||||
code={`import { ConsentProvider } from '@breakpilot/consent-sdk/react';
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="de">
|
||||
<body>
|
||||
<ConsentProvider
|
||||
config={{
|
||||
apiEndpoint: process.env.NEXT_PUBLIC_CONSENT_API!,
|
||||
siteId: 'my-site',
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ConsentProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* useConsent Hook */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">useConsent Hook</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Verwenden Sie den Hook in jeder Komponente:
|
||||
</p>
|
||||
<CodeBlock
|
||||
filename="components/Analytics.tsx"
|
||||
code={`import { useConsent } from '@breakpilot/consent-sdk/react';
|
||||
|
||||
export function Analytics() {
|
||||
const { hasConsent, acceptAll, rejectAll, showSettings } = useConsent();
|
||||
|
||||
if (!hasConsent('analytics')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
);
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* ConsentGate */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">ConsentGate Component</h2>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Zeigt Inhalte nur wenn Consent vorhanden ist:
|
||||
</p>
|
||||
<CodeBlock
|
||||
filename="components/YouTubeEmbed.tsx"
|
||||
code={`import { ConsentGate } from '@breakpilot/consent-sdk/react';
|
||||
|
||||
export function YouTubeEmbed({ videoId }: { videoId: string }) {
|
||||
return (
|
||||
<ConsentGate
|
||||
category="social"
|
||||
fallback={
|
||||
<div className="bg-gray-100 p-4 rounded-lg text-center">
|
||||
<p>Video erfordert Ihre Zustimmung.</p>
|
||||
<button onClick={() => showSettings()}>
|
||||
Einstellungen oeffnen
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<iframe
|
||||
src={\`https://www.youtube.com/embed/\${videoId}\`}
|
||||
width="560"
|
||||
height="315"
|
||||
allowFullScreen
|
||||
/>
|
||||
</ConsentGate>
|
||||
);
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Custom Banner */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Custom Cookie Banner</h2>
|
||||
<CodeBlock
|
||||
filename="components/CookieBanner.tsx"
|
||||
code={`import { useConsent } from '@breakpilot/consent-sdk/react';
|
||||
|
||||
export function CookieBanner() {
|
||||
const {
|
||||
isBannerVisible,
|
||||
acceptAll,
|
||||
rejectAll,
|
||||
showSettings,
|
||||
} = useConsent();
|
||||
|
||||
if (!isBannerVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 inset-x-0 bg-white border-t shadow-lg p-4">
|
||||
<div className="max-w-4xl mx-auto flex items-center justify-between">
|
||||
<p className="text-sm text-gray-600">
|
||||
Wir verwenden Cookies um Ihr Erlebnis zu verbessern.
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={rejectAll}
|
||||
className="px-4 py-2 text-sm border rounded"
|
||||
>
|
||||
Ablehnen
|
||||
</button>
|
||||
<button
|
||||
onClick={showSettings}
|
||||
className="px-4 py-2 text-sm border rounded"
|
||||
>
|
||||
Einstellungen
|
||||
</button>
|
||||
<button
|
||||
onClick={acceptAll}
|
||||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded"
|
||||
>
|
||||
Alle akzeptieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Hook API */}
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Hook API</h2>
|
||||
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Property
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Typ
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Beschreibung
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">hasConsent</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">(category) => boolean</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Prueft Consent fuer Kategorie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">consent</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">ConsentState | null</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Aktueller Consent-Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">acceptAll</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">() => Promise</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Akzeptiert alle Kategorien</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">rejectAll</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">() => Promise</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Lehnt alle ab (ausser essential)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">setConsent</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">(input) => Promise</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Setzt spezifische Kategorien</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">isBannerVisible</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">boolean</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Banner sichtbar?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">showBanner</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">() => void</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Zeigt den Banner</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">showSettings</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">() => void</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Oeffnet Einstellungen</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { SDKDocsSidebar } from '@/components/developers/SDKDocsSidebar'
|
||||
import { Copy, Check } from 'lucide-react'
|
||||
|
||||
function CopyButton({ text }: { text: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
return (
|
||||
<button onClick={handleCopy} className="p-2 hover:bg-gray-700 rounded transition-colors">
|
||||
{copied ? <Check className="w-4 h-4 text-green-400" /> : <Copy className="w-4 h-4 text-gray-400" />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function CodeBlock({ code, filename }: { code: string; filename?: string }) {
|
||||
return (
|
||||
<div className="bg-gray-900 rounded-lg overflow-hidden">
|
||||
{filename && (
|
||||
<div className="px-4 py-2 bg-gray-800 text-gray-400 text-xs border-b border-gray-700">
|
||||
{filename}
|
||||
</div>
|
||||
)}
|
||||
<div className="relative">
|
||||
<div className="absolute top-2 right-2">
|
||||
<CopyButton text={code} />
|
||||
</div>
|
||||
<pre className="p-4 text-sm text-gray-300 font-mono overflow-x-auto">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function VueIntegrationPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<SDKDocsSidebar />
|
||||
|
||||
<main className="flex-1 ml-64">
|
||||
<div className="max-w-4xl mx-auto px-8 py-12">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-lg bg-emerald-500 flex items-center justify-center">
|
||||
<span className="text-white font-bold">V</span>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">Vue 3 Integration</h1>
|
||||
</div>
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
Composables und Plugin fuer Vue 3 und Nuxt Projekte.
|
||||
</p>
|
||||
|
||||
{/* Installation */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Installation</h2>
|
||||
<CodeBlock code="npm install @breakpilot/consent-sdk" />
|
||||
</section>
|
||||
|
||||
{/* Plugin Setup */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Plugin Setup</h2>
|
||||
<CodeBlock
|
||||
filename="main.ts"
|
||||
code={`import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import { ConsentPlugin } from '@breakpilot/consent-sdk/vue';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(ConsentPlugin, {
|
||||
apiEndpoint: import.meta.env.VITE_CONSENT_API,
|
||||
siteId: 'my-site',
|
||||
debug: import.meta.env.DEV,
|
||||
});
|
||||
|
||||
app.mount('#app');`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Composable */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">useConsent Composable</h2>
|
||||
<CodeBlock
|
||||
filename="components/Analytics.vue"
|
||||
code={`<script setup lang="ts">
|
||||
import { useConsent } from '@breakpilot/consent-sdk/vue';
|
||||
|
||||
const { hasConsent, acceptAll, rejectAll } = useConsent();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasConsent('analytics')">
|
||||
<!-- Analytics Code hier -->
|
||||
</div>
|
||||
</template>`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Cookie Banner */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Cookie Banner Component</h2>
|
||||
<CodeBlock
|
||||
filename="components/CookieBanner.vue"
|
||||
code={`<script setup lang="ts">
|
||||
import { useConsent } from '@breakpilot/consent-sdk/vue';
|
||||
|
||||
const {
|
||||
isBannerVisible,
|
||||
acceptAll,
|
||||
rejectAll,
|
||||
showSettings,
|
||||
} = useConsent();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="slide">
|
||||
<div
|
||||
v-if="isBannerVisible"
|
||||
class="fixed bottom-0 inset-x-0 bg-white border-t shadow-lg p-4 z-50"
|
||||
>
|
||||
<div class="max-w-4xl mx-auto flex items-center justify-between">
|
||||
<p class="text-sm text-gray-600">
|
||||
Wir verwenden Cookies um Ihr Erlebnis zu verbessern.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@click="rejectAll"
|
||||
class="px-4 py-2 text-sm border rounded hover:bg-gray-50"
|
||||
>
|
||||
Ablehnen
|
||||
</button>
|
||||
<button
|
||||
@click="showSettings"
|
||||
class="px-4 py-2 text-sm border rounded hover:bg-gray-50"
|
||||
>
|
||||
Einstellungen
|
||||
</button>
|
||||
<button
|
||||
@click="acceptAll"
|
||||
class="px-4 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700"
|
||||
>
|
||||
Alle akzeptieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
</style>`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* ConsentGate */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">ConsentGate Component</h2>
|
||||
<CodeBlock
|
||||
filename="components/YouTubeEmbed.vue"
|
||||
code={`<script setup lang="ts">
|
||||
import { ConsentGate } from '@breakpilot/consent-sdk/vue';
|
||||
|
||||
defineProps<{
|
||||
videoId: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConsentGate category="social">
|
||||
<template #default>
|
||||
<iframe
|
||||
:src="\`https://www.youtube.com/embed/\${videoId}\`"
|
||||
width="560"
|
||||
height="315"
|
||||
allowfullscreen
|
||||
/>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="bg-gray-100 p-4 rounded-lg text-center">
|
||||
<p>Video erfordert Ihre Zustimmung.</p>
|
||||
<button class="mt-2 px-4 py-2 bg-blue-600 text-white rounded">
|
||||
Zustimmen
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</ConsentGate>
|
||||
</template>`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Nuxt */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Nuxt 3 Setup</h2>
|
||||
<CodeBlock
|
||||
filename="plugins/consent.client.ts"
|
||||
code={`import { ConsentPlugin } from '@breakpilot/consent-sdk/vue';
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(ConsentPlugin, {
|
||||
apiEndpoint: useRuntimeConfig().public.consentApi,
|
||||
siteId: 'my-site',
|
||||
});
|
||||
});`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Composable API */}
|
||||
<section>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-4">Composable API</h2>
|
||||
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Property
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Typ
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Beschreibung
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">hasConsent</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">(category) => boolean</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Reaktive Consent-Pruefung</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">consent</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Ref<ConsentState></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Reaktiver Consent-Status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">isBannerVisible</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">Ref<boolean></code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Reaktive Banner-Sichtbarkeit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">acceptAll</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">() => Promise</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Akzeptiert alle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">rejectAll</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">() => Promise</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Lehnt alle ab</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="px-6 py-4"><code className="text-violet-600">setConsent</code></td>
|
||||
<td className="px-6 py-4"><code className="text-gray-500">(input) => Promise</code></td>
|
||||
<td className="px-6 py-4 text-sm text-gray-600">Setzt spezifische Kategorien</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user