SDK customers can now access the documentation publicly without login. The portal runs independently from admin-v2 on https://macmini:3006/. - New developer-portal/ app with 26 pages, 2 components - Docker service + nginx SSL reverse proxy on port 3006 - All /developers/* routes remapped to /* in the new app - admin-v2 developer pages remain unchanged Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
314 lines
11 KiB
TypeScript
314 lines
11 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
import { SDKDocsSidebar } from '@/components/SDKDocsSidebar'
|
|
import { Copy, Check, Smartphone } 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 FlutterSDKPage() {
|
|
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-blue-500 flex items-center justify-center">
|
|
<Smartphone className="w-6 h-6 text-white" />
|
|
</div>
|
|
<h1 className="text-3xl font-bold text-gray-900">Flutter SDK</h1>
|
|
</div>
|
|
<p className="text-lg text-gray-600 mb-8">
|
|
Cross-Platform SDK fuer Flutter 3.16+ mit iOS, Android und Web Support.
|
|
</p>
|
|
|
|
{/* Requirements */}
|
|
<section className="mb-12">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">Systemvoraussetzungen</h2>
|
|
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
<tbody className="divide-y divide-gray-200">
|
|
<tr>
|
|
<td className="px-6 py-4 text-sm font-medium text-gray-900">Dart Version</td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">3.0+</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4 text-sm font-medium text-gray-900">Flutter Version</td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">3.16+</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4 text-sm font-medium text-gray-900">Plattformen</td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">iOS, Android, Web</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Installation */}
|
|
<section className="mb-12">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">Installation</h2>
|
|
<CodeBlock
|
|
filename="pubspec.yaml"
|
|
code={`dependencies:
|
|
consent_sdk: ^1.0.0`}
|
|
/>
|
|
<div className="mt-4">
|
|
<CodeBlock code="flutter pub get" />
|
|
</div>
|
|
</section>
|
|
|
|
{/* Basic Setup */}
|
|
<section className="mb-12">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">Grundlegende Einrichtung</h2>
|
|
<CodeBlock
|
|
filename="main.dart"
|
|
code={`import 'package:flutter/material.dart';
|
|
import 'package:consent_sdk/consent_sdk.dart';
|
|
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
// Consent SDK initialisieren
|
|
await ConsentManager.instance.initialize(
|
|
config: ConsentConfig(
|
|
apiEndpoint: 'https://api.example.com/consent',
|
|
siteId: 'my-flutter-app',
|
|
),
|
|
);
|
|
|
|
runApp(const MyApp());
|
|
}
|
|
|
|
class MyApp extends StatelessWidget {
|
|
const MyApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
home: const ConsentWrapper(
|
|
child: HomeScreen(),
|
|
),
|
|
);
|
|
}
|
|
}`}
|
|
/>
|
|
</section>
|
|
|
|
{/* Widget Usage */}
|
|
<section className="mb-12">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">Widget Integration</h2>
|
|
<CodeBlock
|
|
filename="home_screen.dart"
|
|
code={`import 'package:flutter/material.dart';
|
|
import 'package:consent_sdk/consent_sdk.dart';
|
|
|
|
class HomeScreen extends StatelessWidget {
|
|
const HomeScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Column(
|
|
children: [
|
|
// StreamBuilder fuer reaktive Updates
|
|
StreamBuilder<ConsentState?>(
|
|
stream: ConsentManager.instance.consentStream,
|
|
builder: (context, snapshot) {
|
|
final consent = snapshot.data;
|
|
|
|
if (consent?.hasConsent(ConsentCategory.analytics) ?? false) {
|
|
return const AnalyticsWidget();
|
|
}
|
|
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
|
|
// ConsentGate Widget
|
|
ConsentGate(
|
|
category: ConsentCategory.marketing,
|
|
fallback: const Center(
|
|
child: Text('Marketing-Zustimmung erforderlich'),
|
|
),
|
|
child: const MarketingWidget(),
|
|
),
|
|
|
|
// Buttons
|
|
ElevatedButton(
|
|
onPressed: () => ConsentManager.instance.acceptAll(),
|
|
child: const Text('Alle akzeptieren'),
|
|
),
|
|
|
|
ElevatedButton(
|
|
onPressed: () => ConsentManager.instance.rejectAll(),
|
|
child: const Text('Alle ablehnen'),
|
|
),
|
|
|
|
TextButton(
|
|
onPressed: () => ConsentManager.instance.showSettings(context),
|
|
child: const Text('Einstellungen'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}`}
|
|
/>
|
|
</section>
|
|
|
|
{/* Custom Banner */}
|
|
<section className="mb-12">
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">Custom Cookie Banner</h2>
|
|
<CodeBlock
|
|
filename="cookie_banner.dart"
|
|
code={`import 'package:flutter/material.dart';
|
|
import 'package:consent_sdk/consent_sdk.dart';
|
|
|
|
class CustomCookieBanner extends StatelessWidget {
|
|
const CustomCookieBanner({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return StreamBuilder<bool>(
|
|
stream: ConsentManager.instance.isBannerVisibleStream,
|
|
builder: (context, snapshot) {
|
|
if (!(snapshot.data ?? false)) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Text(
|
|
'Wir verwenden Cookies um Ihr Erlebnis zu verbessern.',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () => ConsentManager.instance.rejectAll(),
|
|
child: const Text('Ablehnen'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => ConsentManager.instance.showSettings(context),
|
|
child: const Text('Einstellungen'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () => ConsentManager.instance.acceptAll(),
|
|
child: const Text('Alle akzeptieren'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}`}
|
|
/>
|
|
</section>
|
|
|
|
{/* API Reference */}
|
|
<section>
|
|
<h2 className="text-xl font-semibold text-gray-900 mb-4">API Referenz</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">Methode/Property</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">initialize()</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">SDK initialisieren (Future)</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4"><code className="text-violet-600">hasConsent()</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">Consent pruefen</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4"><code className="text-violet-600">consentStream</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">Stream fuer Consent-Updates</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4"><code className="text-violet-600">isBannerVisibleStream</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">Stream fuer 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 text-sm text-gray-600">Alle akzeptieren (Future)</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4"><code className="text-violet-600">rejectAll()</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">Alle ablehnen (Future)</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4"><code className="text-violet-600">setConsent()</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">Kategorien setzen (Future)</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="px-6 py-4"><code className="text-violet-600">showSettings()</code></td>
|
|
<td className="px-6 py-4 text-sm text-gray-600">Einstellungs-Dialog oeffnen</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
)
|
|
}
|