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/website/components/PricingSection.tsx
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

293 lines
9.6 KiB
TypeScript

'use client'
import { useState } from 'react'
interface Plan {
id: string
name: string
description: string
price: number
currency: string
interval: string
features: {
tasks: string
taskDescription: string
included: string[]
highlighted?: boolean
}
popular?: boolean
}
const plans: Plan[] = [
{
id: 'basic',
name: 'Basic',
description: 'Perfekt fuer den Einstieg',
price: 9.90,
currency: 'EUR',
interval: 'Monat',
features: {
tasks: '30 Aufgaben',
taskDescription: 'pro Monat',
included: [
'KI-gestuetzte Korrektur',
'Basis-Dokumentvorlagen',
'E-Mail Support',
],
},
},
{
id: 'standard',
name: 'Standard',
description: 'Fuer regelmaessige Nutzer',
price: 19.90,
currency: 'EUR',
interval: 'Monat',
popular: true,
features: {
tasks: '100 Aufgaben',
taskDescription: 'pro Monat',
included: [
'Alles aus Basic',
'Eigene Vorlagen erstellen',
'Batch-Verarbeitung',
'Bis zu 3 Teammitglieder',
'Prioritaets-Support',
],
highlighted: true,
},
},
{
id: 'premium',
name: 'Premium',
description: 'Sorglos-Tarif fuer Vielnutzer',
price: 39.90,
currency: 'EUR',
interval: 'Monat',
features: {
tasks: 'Unbegrenzt',
taskDescription: 'Fair Use',
included: [
'Alles aus Standard',
'Unbegrenzte Aufgaben (Fair Use)',
'Bis zu 10 Teammitglieder',
'Admin-Panel & Audit-Log',
'API-Zugang',
'Eigenes Branding',
'Dedizierter Support',
],
},
},
]
export default function PricingSection() {
const [selectedPlan, setSelectedPlan] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [email, setEmail] = useState('')
const [showEmailForm, setShowEmailForm] = useState(false)
const handleSelectPlan = (planId: string) => {
setSelectedPlan(planId)
setShowEmailForm(true)
}
const handleStartTrial = async (e: React.FormEvent) => {
e.preventDefault()
if (!selectedPlan || !email) return
setIsLoading(true)
try {
// Call billing service to create checkout session
const response = await fetch(`${process.env.NEXT_PUBLIC_BILLING_API_URL}/api/v1/billing/trial/start`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
plan_id: selectedPlan,
email: email,
}),
})
const data = await response.json()
if (data.checkout_url) {
// Redirect to Stripe Checkout
window.location.href = data.checkout_url
} else {
alert('Fehler beim Starten des Trials. Bitte versuchen Sie es erneut.')
}
} catch (error) {
console.error('Error starting trial:', error)
alert('Verbindungsfehler. Bitte versuchen Sie es erneut.')
} finally {
setIsLoading(false)
}
}
return (
<section id="pricing" className="py-16 px-4 sm:px-6 lg:px-8">
<div className="max-w-7xl mx-auto">
{/* Section Header */}
<div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900">
Waehlen Sie Ihren Plan
</h2>
<p className="mt-4 text-lg text-slate-600">
7 Tage kostenlos testen. Jederzeit kuendbar.
</p>
</div>
{/* Pricing Cards */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
{plans.map((plan) => (
<div
key={plan.id}
className={`relative bg-white rounded-2xl p-8 card-hover ${
plan.popular
? 'border-2 border-primary-500 shadow-xl'
: 'border border-slate-200 shadow-lg'
}`}
>
{/* Popular Badge */}
{plan.popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
<span className="bg-primary-500 text-white text-sm font-medium px-4 py-1 rounded-full">
Beliebteste Wahl
</span>
</div>
)}
{/* Plan Header */}
<div className="text-center">
<h3 className="text-xl font-semibold text-slate-900">{plan.name}</h3>
<p className="mt-1 text-slate-500 text-sm">{plan.description}</p>
</div>
{/* Price */}
<div className="mt-6 text-center">
<span className="text-4xl font-bold text-slate-900">
{plan.price.toFixed(2).replace('.', ',')}
</span>
<span className="text-slate-500 ml-1">EUR/{plan.interval}</span>
</div>
{/* Tasks Highlight */}
<div className="mt-6 bg-slate-50 rounded-xl p-4 text-center">
<div className="text-2xl font-bold text-primary-600">{plan.features.tasks}</div>
<div className="text-sm text-slate-500">{plan.features.taskDescription}</div>
</div>
{/* Features List */}
<ul className="mt-6 space-y-3">
{plan.features.included.map((feature, index) => (
<li key={index} className="flex items-start">
<svg
className="w-5 h-5 text-primary-500 mr-3 mt-0.5 flex-shrink-0"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
<span className="text-slate-600 text-sm">{feature}</span>
</li>
))}
</ul>
{/* CTA Button */}
<button
onClick={() => handleSelectPlan(plan.id)}
className={`mt-8 w-full py-3 px-4 rounded-xl font-medium transition-all btn-press ${
plan.popular
? 'bg-primary-600 text-white hover:bg-primary-700'
: 'bg-slate-100 text-slate-900 hover:bg-slate-200'
}`}
>
7 Tage kostenlos testen
</button>
</div>
))}
</div>
{/* Carryover Info */}
<div className="mt-12 text-center">
<p className="text-slate-500 text-sm">
Ungenutzte Aufgaben sammeln sich bis zu 5 Monate an.
<br />
Keine versteckten Kosten. Kreditkarte fuer Trial erforderlich.
</p>
</div>
{/* Email Form Modal */}
{showEmailForm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl p-8 max-w-md w-full shadow-2xl">
<h3 className="text-2xl font-bold text-slate-900 text-center">
Trial starten
</h3>
<p className="mt-2 text-slate-600 text-center">
{plans.find((p) => p.id === selectedPlan)?.name} - 7 Tage kostenlos
</p>
<form onSubmit={handleStartTrial} className="mt-6">
<label className="block">
<span className="text-sm font-medium text-slate-700">E-Mail-Adresse</span>
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="ihre@email.de"
className="mt-1 block w-full px-4 py-3 rounded-xl border border-slate-300 focus:border-primary-500 focus:ring-2 focus:ring-primary-500/20 transition-all"
/>
</label>
<button
type="submit"
disabled={isLoading}
className="mt-6 w-full bg-primary-600 text-white py-3 px-4 rounded-xl font-medium hover:bg-primary-700 transition-all btn-press disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<span className="flex items-center justify-center">
<svg className="animate-spin -ml-1 mr-2 h-5 w-5" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
Wird geladen...
</span>
) : (
'Weiter zu Stripe'
)}
</button>
</form>
<button
onClick={() => {
setShowEmailForm(false)
setSelectedPlan(null)
}}
className="mt-4 w-full text-slate-500 hover:text-slate-700 text-sm"
>
Abbrechen
</button>
<p className="mt-6 text-xs text-slate-400 text-center">
Mit dem Fortfahren akzeptieren Sie unsere{' '}
<a href="/agb" className="underline">AGB</a> und{' '}
<a href="/datenschutz" className="underline">Datenschutzerklaerung</a>.
</p>
</div>
</div>
)}
</div>
</section>
)
}