Files
breakpilot-lehrer/website/app/admin/builds/wizard/page.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

603 lines
20 KiB
TypeScript

'use client'
/**
* Build Pipeline Wizard
*
* Interactive guide for learning about the multi-platform
* build and deployment process for Breakpilot Drive
*/
import { useState } from 'react'
import Link from 'next/link'
import AdminLayout from '@/components/admin/AdminLayout'
// ========================================
// Types
// ========================================
type WizardStep =
| 'welcome'
| 'platforms'
| 'github-actions'
| 'webgl-build'
| 'ios-build'
| 'android-build'
| 'deployment'
| 'version-sync'
| 'summary'
interface StepInfo {
id: WizardStep
title: string
description: string
}
// ========================================
// Step Configuration
// ========================================
const STEPS: StepInfo[] = [
{ id: 'welcome', title: 'Willkommen', description: 'Build Pipeline Uebersicht' },
{ id: 'platforms', title: 'Plattformen', description: 'WebGL, iOS, Android' },
{ id: 'github-actions', title: 'GitHub Actions', description: 'CI/CD Workflow' },
{ id: 'webgl-build', title: 'WebGL', description: 'Browser Build' },
{ id: 'ios-build', title: 'iOS', description: 'App Store Build' },
{ id: 'android-build', title: 'Android', description: 'Play Store Build' },
{ id: 'deployment', title: 'Deployment', description: 'Store Upload' },
{ id: 'version-sync', title: 'Versioning', description: 'Version Management' },
{ id: 'summary', title: 'Zusammenfassung', description: 'Naechste Schritte' },
]
// ========================================
// Educational Content
// ========================================
const EDUCATION_CONTENT: Record<WizardStep, { title: string; content: string; tips: string[] }> = {
'welcome': {
title: 'Multi-Platform Build Pipeline',
content: `Breakpilot Drive wird fuer drei Plattformen gebaut:
**WebGL** - Browser-basiert, in Admin Panel eingebettet
**iOS** - iPhone/iPad via App Store
**Android** - Smartphones/Tablets via Google Play
Die Build-Pipeline nutzt **GitHub Actions** mit **game-ci/unity-builder**
fuer automatisierte, reproduzierbare Builds.`,
tips: [
'WebGL ist die primaere Plattform fuer schnelles Testing',
'Mobile Builds nur bei Tags (Releases)',
'Alle Builds werden als Artifacts gespeichert'
]
},
'platforms': {
title: 'Unterstuetzte Plattformen',
content: `Jede Plattform hat spezifische Anforderungen:
**WebGL (HTML5/WASM)**
- Brotli-Kompression
- 512MB Memory
- Kein Threading (Browser-Limitation)
**iOS (iPhone/iPad)**
- Min. iOS 14.0
- ARM64 Architektur
- App Store Distribution
**Android**
- Min. Android 7.0 (API 24)
- Target: Android 14 (API 34)
- ARM64, AAB fuer Play Store`,
tips: [
'WebGL laeuft in allen modernen Browsern',
'iOS erfordert Apple Developer Account ($99/Jahr)',
'Android AAB ist Pflicht fuer Play Store'
]
},
'github-actions': {
title: 'GitHub Actions Workflow',
content: `Der CI/CD Workflow ist in Jobs aufgeteilt:
**1. version** - Ermittelt Version aus Git Tag
**2. build-webgl** - Baut Browser-Version
**3. build-ios** - Baut Xcode Projekt
**4. build-ios-ipa** - Erstellt signierte IPA
**5. build-android** - Baut AAB/APK
**6. deploy-webgl** - Deployed zu CDN
**7. upload-ios** - Laedt zu App Store Connect
**8. upload-android** - Laedt zu Google Play
Trigger:
- **Tags (v*)**: Alle Plattformen + Upload
- **Push main**: Nur WebGL
- **Manual**: Auswahlbar`,
tips: [
'Unity License muss als Secret hinterlegt sein',
'Signing-Zertifikate als Base64 Secrets',
'Cache beschleunigt Builds erheblich'
]
},
'webgl-build': {
title: 'WebGL Build',
content: `WebGL ist die schnellste Build-Variante:
**Build-Einstellungen:**
- Kompression: Brotli (beste Kompression)
- Memory: 512MB (ausreichend fuer Spiel)
- Exceptions: Nur explizite (Performance)
- Linker: WASM (WebAssembly)
**Output:**
- index.html
- Build/*.wasm.br (komprimiert)
- Build/*.data.br (Assets)
- Build/*.js (Loader)
**Deployment:**
- S3 + CloudFront CDN
- Cache: 1 Jahr fuer Assets, 1h fuer HTML`,
tips: [
'Brotli-Kompression spart ~70% Bandbreite',
'Erste Ladung ~10-15MB, danach gecached',
'Server muss Brotli-Headers unterstuetzen'
]
},
'ios-build': {
title: 'iOS Build',
content: `iOS Build erfolgt in zwei Schritten:
**Schritt 1: Unity Build**
- Erstellt Xcode Projekt
- Setzt iOS-spezifische Einstellungen
- Output: Unity-iPhone.xcodeproj
**Schritt 2: Xcode Build**
- Importiert Signing-Zertifikate
- Archiviert Projekt
- Exportiert signierte IPA
**Voraussetzungen:**
- Apple Developer Account
- Distribution Certificate (.p12)
- Provisioning Profile
- App Store Connect API Key`,
tips: [
'Zertifikate alle 1 Jahr erneuern',
'Provisioning Profile fuer jede App ID',
'TestFlight fuer Beta-Tests nutzen'
]
},
'android-build': {
title: 'Android Build',
content: `Android Build erzeugt AAB oder APK:
**AAB (App Bundle)** - Fuer Play Store
- Google optimiert fuer jedes Geraet
- Kleinere Downloads
- Pflicht seit 2021
**APK** - Fuer direkten Download
- Debug-Builds fuer Testing
- Sideloading moeglich
**Signing:**
- Keystore (.jks/.keystore)
- Key Alias und Passwoerter
- Play App Signing empfohlen
**Voraussetzungen:**
- Google Play Console Account ($25 einmalig)
- Keystore fuer App-Signatur`,
tips: [
'Keystore NIEMALS verlieren (keine Veroeffentlichung mehr)',
'Play App Signing: Google verwaltet Upload-Key',
'Internal Testing fuer schnelle Tests'
]
},
'deployment': {
title: 'Store Deployment',
content: `Automatisches Deployment zu den Stores:
**WebGL -> CDN (S3/CloudFront)**
- Sync zu S3 Bucket
- CloudFront Invalidation
- Versionierte URLs
**iOS -> App Store Connect**
- Upload via altool
- API Key Authentifizierung
- TestFlight Auto-Distribution
**Android -> Google Play**
- Upload via r0adkll/upload-google-play
- Service Account Auth
- Internal Track zuerst`,
tips: [
'WebGL ist sofort live nach Deploy',
'iOS: Review dauert 1-3 Tage',
'Android: Review dauert wenige Stunden'
]
},
'version-sync': {
title: 'Version Synchronisation',
content: `Versionen werden zentral verwaltet:
**version.json** (Runtime)
- version: Semantische Version
- build_number: Inkrementell
- platform: Build-Target
- commit_hash: Git SHA
- min_api_version: API Kompatibilitaet
**VersionManager.cs** (Unity)
- Laedt version.json zur Laufzeit
- Prueft API-Kompatibilitaet
- Zeigt Update-Hinweise
**Git Tags**
- v1.0.0 -> Version 1.0.0
- Trigger fuer Release-Builds`,
tips: [
'build_number aus GitHub Run Number',
'min_api_version fuer erzwungene Updates',
'Semantic Versioning: MAJOR.MINOR.PATCH'
]
},
'summary': {
title: 'Zusammenfassung & Naechste Schritte',
content: `Du hast gelernt:
✓ Build-Targets: WebGL, iOS, Android
✓ GitHub Actions Workflow
✓ Platform-spezifische Einstellungen
✓ Store Deployment Prozess
✓ Version Management
**Naechste Schritte:**
1. GitHub Secrets konfigurieren
2. Apple/Google Developer Accounts einrichten
3. Keystore und Zertifikate erstellen
4. Ersten Release-Tag erstellen`,
tips: [
'Dokumentation in BreakpilotDrive/ci/',
'BuildScript.cs fuer lokale Builds',
'version.json wird automatisch aktualisiert'
]
},
}
// ========================================
// Components
// ========================================
function WizardStepper({ steps, currentStep, onStepClick }: {
steps: StepInfo[]
currentStep: WizardStep
onStepClick: (step: WizardStep) => void
}) {
const currentIndex = steps.findIndex(s => s.id === currentStep)
return (
<div className="flex items-center gap-1 overflow-x-auto pb-2">
{steps.map((step, index) => {
const isActive = step.id === currentStep
const isCompleted = index < currentIndex
const isClickable = index <= currentIndex + 1
return (
<button
key={step.id}
onClick={() => isClickable && onStepClick(step.id)}
disabled={!isClickable}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all whitespace-nowrap ${
isActive
? 'bg-green-600 text-white'
: isCompleted
? 'bg-green-100 text-green-700 hover:bg-green-200'
: isClickable
? 'bg-gray-100 text-gray-600 hover:bg-gray-200'
: 'bg-gray-50 text-gray-400 cursor-not-allowed'
}`}
>
<span className={`w-6 h-6 rounded-full flex items-center justify-center text-xs ${
isActive ? 'bg-white/20' : isCompleted ? 'bg-green-200' : 'bg-gray-200'
}`}>
{isCompleted ? '✓' : index + 1}
</span>
<span className="hidden md:inline">{step.title}</span>
</button>
)
})}
</div>
)
}
function EducationCard({ title, content, tips }: { title: string; content: string; tips: string[] }) {
return (
<div className="bg-white rounded-xl shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">{title}</h2>
<div className="prose prose-sm max-w-none mb-6">
{content.split('\n\n').map((paragraph, i) => (
<p key={i} className="text-gray-700 whitespace-pre-line mb-3">
{paragraph.split('**').map((part, j) =>
j % 2 === 1 ? <strong key={j}>{part}</strong> : part
)}
</p>
))}
</div>
<div className="bg-green-50 rounded-lg p-4">
<h3 className="text-sm font-semibold text-green-800 mb-2">Tipps:</h3>
<ul className="space-y-1">
{tips.map((tip, i) => (
<li key={i} className="flex items-start gap-2 text-sm text-green-700">
<span className="text-green-500 mt-0.5"></span>
{tip}
</li>
))}
</ul>
</div>
</div>
)
}
function PlatformCards() {
const platforms = [
{
name: 'WebGL',
icon: '🌐',
status: 'Aktiv',
size: '~15 MB',
features: ['Browser-basiert', 'Sofort spielbar', 'Admin Panel Embed']
},
{
name: 'iOS',
icon: '📱',
status: 'Bereit',
size: '~80 MB',
features: ['iPhone & iPad', 'App Store', 'Push Notifications']
},
{
name: 'Android',
icon: '🤖',
status: 'Bereit',
size: '~60 MB',
features: ['Play Store', 'AAB Format', 'Wide Device Support']
},
]
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
{platforms.map((platform) => (
<div key={platform.name} className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg p-4 border border-green-100">
<div className="flex items-center gap-3 mb-3">
<span className="text-3xl">{platform.icon}</span>
<div>
<h4 className="font-bold text-gray-900">{platform.name}</h4>
<p className="text-sm text-gray-500">{platform.size}</p>
</div>
<span className="ml-auto px-2 py-1 bg-green-100 text-green-700 text-xs rounded-full">
{platform.status}
</span>
</div>
<ul className="space-y-1">
{platform.features.map((feature, i) => (
<li key={i} className="text-sm text-gray-600 flex items-center gap-2">
<span className="text-green-500"></span> {feature}
</li>
))}
</ul>
</div>
))}
</div>
)
}
function WorkflowDiagram() {
const jobs = [
{ name: 'version', icon: '🏷️', runner: 'ubuntu' },
{ name: 'build-webgl', icon: '🌐', runner: 'ubuntu' },
{ name: 'build-ios', icon: '📱', runner: 'macos' },
{ name: 'build-android', icon: '🤖', runner: 'ubuntu' },
{ name: 'deploy', icon: '🚀', runner: 'ubuntu' },
]
return (
<div className="mt-6 bg-gray-900 rounded-lg p-6">
<h3 className="text-white font-semibold mb-4">Workflow Jobs</h3>
<div className="flex flex-wrap gap-4">
{jobs.map((job, i) => (
<div key={job.name} className="flex items-center gap-2">
<div className="bg-gray-800 rounded-lg p-3 text-center min-w-[100px]">
<span className="text-2xl">{job.icon}</span>
<p className="text-white text-sm font-medium mt-1">{job.name}</p>
<p className="text-gray-500 text-xs">{job.runner}</p>
</div>
{i < jobs.length - 1 && (
<span className="text-gray-600 text-xl"></span>
)}
</div>
))}
</div>
</div>
)
}
function SecretsChecklist() {
const secrets = [
{ name: 'UNITY_LICENSE', desc: 'Unity Personal/Pro License', required: true },
{ name: 'UNITY_EMAIL', desc: 'Unity Account Email', required: true },
{ name: 'UNITY_PASSWORD', desc: 'Unity Account Password', required: true },
{ name: 'IOS_BUILD_CERTIFICATE_BASE64', desc: 'Apple Distribution Certificate', required: false },
{ name: 'IOS_PROVISION_PROFILE_BASE64', desc: 'iOS Provisioning Profile', required: false },
{ name: 'ANDROID_KEYSTORE_BASE64', desc: 'Android Signing Keystore', required: false },
{ name: 'AWS_ACCESS_KEY_ID', desc: 'AWS fuer S3/CloudFront', required: false },
]
return (
<div className="mt-6 bg-white rounded-lg border border-gray-200 overflow-hidden">
<div className="px-4 py-3 bg-gray-50 border-b">
<h3 className="font-semibold text-gray-800">GitHub Secrets Checkliste</h3>
</div>
<ul className="divide-y">
{secrets.map((secret) => (
<li key={secret.name} className="px-4 py-3 flex items-center justify-between">
<div>
<code className="text-sm bg-gray-100 px-2 py-1 rounded">{secret.name}</code>
<p className="text-sm text-gray-500 mt-1">{secret.desc}</p>
</div>
<span className={`text-xs px-2 py-1 rounded ${
secret.required ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-600'
}`}>
{secret.required ? 'Pflicht' : 'Optional'}
</span>
</li>
))}
</ul>
</div>
)
}
// ========================================
// Main Component
// ========================================
export default function BuildPipelineWizardPage() {
const [currentStep, setCurrentStep] = useState<WizardStep>('welcome')
const currentStepIndex = STEPS.findIndex(s => s.id === currentStep)
const education = EDUCATION_CONTENT[currentStep]
const goToNext = () => {
const nextIndex = currentStepIndex + 1
if (nextIndex < STEPS.length) {
setCurrentStep(STEPS[nextIndex].id)
}
}
const goToPrevious = () => {
const prevIndex = currentStepIndex - 1
if (prevIndex >= 0) {
setCurrentStep(STEPS[prevIndex].id)
}
}
return (
<AdminLayout
title="Build Pipeline Wizard"
description="Lerne die Multi-Platform Build Pipeline kennen"
>
{/* Back Link */}
<div className="mb-6">
<Link
href="/admin/game"
className="inline-flex items-center gap-2 text-gray-600 hover:text-gray-900"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Zurueck zum Game Dashboard
</Link>
</div>
{/* Stepper */}
<div className="mb-8">
<WizardStepper
steps={STEPS}
currentStep={currentStep}
onStepClick={setCurrentStep}
/>
</div>
{/* Content */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main Content */}
<div className="lg:col-span-2">
<EducationCard
title={education.title}
content={education.content}
tips={education.tips}
/>
{/* Step-specific content */}
{currentStep === 'platforms' && <PlatformCards />}
{currentStep === 'github-actions' && <WorkflowDiagram />}
{currentStep === 'deployment' && <SecretsChecklist />}
{/* Navigation */}
<div className="flex justify-between mt-8">
<button
onClick={goToPrevious}
disabled={currentStepIndex === 0}
className="px-6 py-3 rounded-lg font-medium bg-gray-100 text-gray-700 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
Zurueck
</button>
<button
onClick={goToNext}
disabled={currentStepIndex === STEPS.length - 1}
className="px-6 py-3 rounded-lg font-medium bg-green-600 text-white hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
{currentStepIndex === STEPS.length - 1 ? 'Fertig' : 'Weiter'}
</button>
</div>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Progress */}
<div className="bg-white rounded-xl shadow p-6">
<h3 className="font-semibold text-gray-900 mb-4">Fortschritt</h3>
<div className="relative pt-1">
<div className="flex mb-2 items-center justify-between">
<span className="text-xs font-semibold text-green-600">
Schritt {currentStepIndex + 1} von {STEPS.length}
</span>
<span className="text-xs font-semibold text-green-600">
{Math.round(((currentStepIndex + 1) / STEPS.length) * 100)}%
</span>
</div>
<div className="overflow-hidden h-2 mb-4 text-xs flex rounded bg-green-100">
<div
style={{ width: `${((currentStepIndex + 1) / STEPS.length) * 100}%` }}
className="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-600 transition-all duration-300"
/>
</div>
</div>
</div>
{/* Pipeline Overview */}
<div className="bg-gradient-to-br from-green-500 to-emerald-600 rounded-xl shadow p-6 text-white">
<h3 className="font-semibold mb-4">Pipeline Flow</h3>
<div className="text-sm space-y-2 font-mono">
<div className="bg-white/10 rounded px-2 py-1">Git Push/Tag</div>
<div className="text-center text-green-200"></div>
<div className="bg-white/10 rounded px-2 py-1">GitHub Actions</div>
<div className="text-center text-green-200"></div>
<div className="bg-white/10 rounded px-2 py-1">Unity Build</div>
<div className="text-center text-green-200"></div>
<div className="bg-white/10 rounded px-2 py-1">Deploy / Upload</div>
</div>
</div>
{/* Quick Links */}
<div className="bg-white rounded-xl shadow p-6">
<h3 className="font-semibold text-gray-900 mb-4">Wichtige Dateien</h3>
<ul className="space-y-2 text-sm">
<li className="text-gray-600">
<span className="text-green-600">YAML:</span> ci/build-all-platforms.yml
</li>
<li className="text-gray-600">
<span className="text-green-600">C#:</span> Assets/Editor/BuildScript.cs
</li>
<li className="text-gray-600">
<span className="text-green-600">JSON:</span> Assets/Resources/version.json
</li>
<li className="text-gray-600">
<span className="text-green-600">Plist:</span> ci/ios-export-options.plist
</li>
</ul>
</div>
</div>
</div>
</AdminLayout>
)
}