7a5f1e48dd
[migration-approved]
Templates (Migrations 123-136):
- 123 GO-GF (Geschäftsordnung Geschäftsführung)
- 124 SHA (Shareholders' Agreement, 56 Platzhalter)
- 125 Satzung (Articles of Association mit UG-Variante)
- 126 GF-Dienstvertrag (Trennungsprinzip Organ/Anstellung)
- 127 Arbeitsvertrag (AGG-neutral, NachwG, eAU)
- 128 Gesellschafterliste (§ 40 GmbHG)
- 129 GF-Bestellungsbeschluss (mit § 6 Abs. 2 Versicherung)
- 130 HRB-Anmeldung (§§ 7, 8, 39 GmbHG, § 12 HGB)
- 131 IP-Assignment Agreement (Gründer→GmbH)
- 132 Term Sheet (Pre-Seed/Seed VC-Standard)
- 133 Wandeldarlehensvertrag (Convertible Loan)
- 134 Beteiligungsvertrag (Subscription Agreement)
- 135 ESOP/VSOP-Plan (3 Varianten)
- 136 Cap Table
Kategorisierung (Migrations 137-138):
- ALTER TABLE compliance_legal_templates ADD lifecycle_stage TEXT[],
functional_category TEXT (mit CHECK Constraints + GIN-Index)
- Backfill aller 105 Templates: lifecycle_stage (pre_founding|founding|
startup|kmu|konzern) + functional_category (founding_legal|employment|
investor_funding|...)
Backend Founding-Wizard Service:
- template_renderer.py: Handlebars-light ({{VAR}}, {{#IF FLAG}}...{{/IF}})
- wizard_to_context.py: Mapping Wizard-State → SCREAMING_SNAKE_CASE Vars
- markdown_to_docx.py: Markdown → DOCX via python-docx
- founding_wizard_routes.py: POST /v1/founding-wizard/generate
→ liefert base64-DOCX-Files für ausgewählte Templates
Frontend Founding-Wizard (/sdk/founding-wizard):
- 8-Step Wizard (Basics, Gesellschafter, GF, Kapital, Notar, SHA, GF-Verträge, Generate)
- useFoundingWizardForm Hook mit localStorage-Persistenz
- TypeScript Code-Registry (template-categories.ts) als Backup zur DB
- Word-Download via data:URLs (base64)
Tests:
- 20 Unit-Tests grün (Renderer, Context-Mapping, DOCX-Conversion)
- Playwright E2E-Test mit 2-Mann GmbH (Benjamin + Sharang) Test-Daten
142 lines
5.7 KiB
TypeScript
142 lines
5.7 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { useFoundingWizardForm } from './_hooks/useFoundingWizardForm'
|
|
import { StepBasics } from './_components/StepBasics'
|
|
import { StepGesellschafter } from './_components/StepGesellschafter'
|
|
import { StepCapital, StepGFAssignment, StepGFContracts, StepNotar, StepSHAConfig } from './_components/StepsSimpleConfig'
|
|
import { StepGenerate } from './_components/StepGenerate'
|
|
|
|
export default function FoundingWizardPage() {
|
|
const {
|
|
state, hydrated, generating, error,
|
|
update, nextStep, prevStep, reset,
|
|
addGesellschafter, updateGesellschafter, removeGesellschafter,
|
|
upsertGFContract,
|
|
canProceed, generateDocuments,
|
|
gf_list, steps,
|
|
} = useFoundingWizardForm()
|
|
|
|
if (!hydrated) return null
|
|
|
|
const isLastStep = state.current_step === steps.length
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 py-8" data-testid="founding-wizard">
|
|
<div className="max-w-5xl mx-auto px-4">
|
|
{/* Header */}
|
|
<div className="mb-8 flex justify-between items-start">
|
|
<div>
|
|
<h1 className="text-3xl font-bold text-gray-900">Gründungs-Wizard</h1>
|
|
<p className="text-gray-600 mt-2">
|
|
Erstellt alle Notartermin-Dokumente für Deine GmbH/UG-Gründung in 8 Schritten.
|
|
</p>
|
|
</div>
|
|
<button
|
|
data-testid="reset-wizard"
|
|
onClick={() => { if (confirm('Wizard-Daten zurücksetzen?')) reset() }}
|
|
className="text-sm text-gray-500 hover:text-red-600"
|
|
>
|
|
Zurücksetzen
|
|
</button>
|
|
</div>
|
|
|
|
{/* Progress Steps */}
|
|
<div className="mb-8" data-testid="wizard-progress">
|
|
<div className="flex items-center justify-between">
|
|
{steps.map((step, idx) => (
|
|
<React.Fragment key={step.id}>
|
|
<button
|
|
type="button"
|
|
onClick={() => state.current_step > step.id && update('current_step', step.id)}
|
|
className="flex items-center"
|
|
data-testid={`step-indicator-${step.id}`}
|
|
>
|
|
<div className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-medium ${
|
|
step.id < state.current_step ? 'bg-purple-600 text-white' :
|
|
step.id === state.current_step ? 'bg-purple-100 text-purple-600 border-2 border-purple-600' :
|
|
'bg-gray-100 text-gray-400'
|
|
}`}>
|
|
{step.id < state.current_step ? '✓' : step.id}
|
|
</div>
|
|
<div className="ml-2 hidden md:block text-left">
|
|
<div className={`text-xs font-medium ${step.id <= state.current_step ? 'text-gray-900' : 'text-gray-400'}`}>
|
|
{step.name}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
{idx < steps.length - 1 && (
|
|
<div className={`flex-1 h-0.5 mx-2 ${step.id < state.current_step ? 'bg-purple-600' : 'bg-gray-200'}`} />
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Step Content */}
|
|
<div className="bg-white rounded-xl border border-gray-200 p-8">
|
|
<div className="mb-6">
|
|
<h2 className="text-xl font-semibold text-gray-900">
|
|
{steps[state.current_step - 1]?.name}
|
|
</h2>
|
|
<p className="text-gray-500 text-sm">{steps[state.current_step - 1]?.description}</p>
|
|
</div>
|
|
|
|
<div data-testid={`step-content-${state.current_step}`}>
|
|
{state.current_step === 1 && <StepBasics state={state} update={update} />}
|
|
{state.current_step === 2 && (
|
|
<StepGesellschafter
|
|
state={state}
|
|
addGesellschafter={addGesellschafter}
|
|
updateGesellschafter={updateGesellschafter}
|
|
removeGesellschafter={removeGesellschafter}
|
|
/>
|
|
)}
|
|
{state.current_step === 3 && <StepGFAssignment state={state} update={update} />}
|
|
{state.current_step === 4 && <StepCapital state={state} update={update} />}
|
|
{state.current_step === 5 && <StepNotar state={state} update={update} />}
|
|
{state.current_step === 6 && <StepSHAConfig state={state} update={update} />}
|
|
{state.current_step === 7 && (
|
|
<StepGFContracts state={state} update={update} gf_list={gf_list} upsertGFContract={upsertGFContract} />
|
|
)}
|
|
{state.current_step === 8 && (
|
|
<StepGenerate
|
|
state={state}
|
|
update={update}
|
|
generating={generating}
|
|
error={error}
|
|
onGenerate={generateDocuments}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
{!isLastStep && (
|
|
<div className="flex justify-between items-center mt-8 pt-6 border-t border-gray-200">
|
|
<button
|
|
data-testid="prev-step"
|
|
onClick={prevStep}
|
|
disabled={state.current_step === 1}
|
|
className="px-6 py-3 text-gray-600 hover:text-gray-900 disabled:opacity-50"
|
|
>
|
|
Zurück
|
|
</button>
|
|
<span className="text-xs text-gray-400">
|
|
Schritt {state.current_step} von {steps.length}
|
|
</span>
|
|
<button
|
|
data-testid="next-step"
|
|
onClick={nextStep}
|
|
disabled={!canProceed}
|
|
className="px-8 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
|
|
>
|
|
Weiter
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|