feat: Add staged funding model, financial compute engine, annex slides and UI enhancements
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
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
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
Tests / All Checks Passed (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python 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
Security Scanning / Secret Scanning (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
Some checks failed
ci/woodpecker/push/integration Pipeline failed
ci/woodpecker/push/main Pipeline failed
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
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
Tests / All Checks Passed (push) Has been cancelled
Tests / Go Tests (push) Has been cancelled
Tests / Python 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
Security Scanning / Secret Scanning (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
Restructure financial plan from single 200k SAFE to realistic staged funding (25k Stammkapital, 25k Angel, 200k Wandeldarlehen, 1M Series A = 1.25M total). Add 60-month compute engine with CAPEX/OPEX accounting, cash constraints, hardware financing (30% upfront / 70% leasing), and revenue-based hiring caps. Rebuild TheAskSlide with 4-event funding timeline, update i18n (DE/EN), chat agent core messages, and add 15 new annex/technology slides with supporting UI components (KPICard, RunwayGauge, WaterfallChart, etc.). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,52 +1,63 @@
|
||||
'use client'
|
||||
|
||||
import { Language } from '@/lib/types'
|
||||
import { t } from '@/lib/i18n'
|
||||
import { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import { FMAssumption, Language } from '@/lib/types'
|
||||
|
||||
interface FinancialSlidersProps {
|
||||
growthRate: number
|
||||
churnRate: number
|
||||
arpu: number
|
||||
onGrowthChange: (v: number) => void
|
||||
onChurnChange: (v: number) => void
|
||||
onArpuChange: (v: number) => void
|
||||
assumptions: FMAssumption[]
|
||||
onAssumptionChange: (key: string, value: number) => void
|
||||
lang: Language
|
||||
}
|
||||
|
||||
function Slider({
|
||||
label,
|
||||
value,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
unit,
|
||||
assumption,
|
||||
onChange,
|
||||
lang,
|
||||
}: {
|
||||
label: string
|
||||
value: number
|
||||
min: number
|
||||
max: number
|
||||
step: number
|
||||
unit: string
|
||||
onChange: (v: number) => void
|
||||
assumption: FMAssumption
|
||||
onChange: (value: number) => void
|
||||
lang: Language
|
||||
}) {
|
||||
const value = typeof assumption.value === 'number' ? assumption.value : Number(assumption.value)
|
||||
const label = lang === 'de' ? assumption.label_de : assumption.label_en
|
||||
|
||||
if (assumption.value_type === 'step') {
|
||||
// Display step values as read-only list
|
||||
const steps = Array.isArray(assumption.value) ? assumption.value : []
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<p className="text-[11px] text-white/50">{label}</p>
|
||||
<div className="flex gap-1.5">
|
||||
{steps.map((s: number, i: number) => (
|
||||
<div key={i} className="flex-1 text-center">
|
||||
<p className="text-[9px] text-white/30">Y{i + 1}</p>
|
||||
<p className="text-xs text-white font-mono">{s}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-white/60">{label}</span>
|
||||
<span className="font-mono text-white">{value}{unit}</span>
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex justify-between text-[11px]">
|
||||
<span className="text-white/50">{label}</span>
|
||||
<span className="font-mono text-white">{value}{assumption.unit === 'EUR' ? ' EUR' : assumption.unit === '%' ? '%' : ` ${assumption.unit || ''}`}</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
min={assumption.min_value ?? 0}
|
||||
max={assumption.max_value ?? 100}
|
||||
step={assumption.step_size ?? 1}
|
||||
value={value}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||
className="w-full h-1.5 bg-white/10 rounded-full appearance-none cursor-pointer
|
||||
[&::-webkit-slider-thumb]:appearance-none
|
||||
[&::-webkit-slider-thumb]:w-4
|
||||
[&::-webkit-slider-thumb]:h-4
|
||||
[&::-webkit-slider-thumb]:w-3.5
|
||||
[&::-webkit-slider-thumb]:h-3.5
|
||||
[&::-webkit-slider-thumb]:rounded-full
|
||||
[&::-webkit-slider-thumb]:bg-indigo-500
|
||||
[&::-webkit-slider-thumb]:shadow-lg
|
||||
@@ -58,47 +69,76 @@ function Slider({
|
||||
)
|
||||
}
|
||||
|
||||
export default function FinancialSliders({
|
||||
growthRate,
|
||||
churnRate,
|
||||
arpu,
|
||||
onGrowthChange,
|
||||
onChurnChange,
|
||||
onArpuChange,
|
||||
lang,
|
||||
}: FinancialSlidersProps) {
|
||||
const i = t(lang)
|
||||
interface CategoryGroup {
|
||||
key: string
|
||||
label: string
|
||||
items: FMAssumption[]
|
||||
}
|
||||
|
||||
export default function FinancialSliders({ assumptions, onAssumptionChange, lang }: FinancialSlidersProps) {
|
||||
const [openCategories, setOpenCategories] = useState<Set<string>>(new Set(['revenue']))
|
||||
|
||||
// Group assumptions by category
|
||||
const categories: CategoryGroup[] = [
|
||||
{ key: 'revenue', label: lang === 'de' ? 'Revenue' : 'Revenue', items: [] },
|
||||
{ key: 'costs', label: lang === 'de' ? 'Kosten' : 'Costs', items: [] },
|
||||
{ key: 'team', label: 'Team', items: [] },
|
||||
{ key: 'admin', label: lang === 'de' ? 'Verwaltung' : 'Administration', items: [] },
|
||||
{ key: 'funding', label: 'Funding', items: [] },
|
||||
]
|
||||
|
||||
for (const a of assumptions) {
|
||||
const cat = categories.find(c => c.key === a.category) || categories[0]
|
||||
cat.items.push(a)
|
||||
}
|
||||
|
||||
const toggleCategory = (key: string) => {
|
||||
setOpenCategories(prev => {
|
||||
const next = new Set(prev)
|
||||
if (next.has(key)) next.delete(key)
|
||||
else next.add(key)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-5 p-5 bg-white/[0.05] rounded-2xl border border-white/10">
|
||||
<h4 className="text-sm font-medium text-white/60">{i.financials.adjustAssumptions}</h4>
|
||||
<Slider
|
||||
label={i.financials.sliderGrowth}
|
||||
value={growthRate}
|
||||
min={50}
|
||||
max={200}
|
||||
step={10}
|
||||
unit="%"
|
||||
onChange={onGrowthChange}
|
||||
/>
|
||||
<Slider
|
||||
label={i.financials.sliderChurn}
|
||||
value={churnRate}
|
||||
min={0}
|
||||
max={15}
|
||||
step={0.5}
|
||||
unit="%"
|
||||
onChange={onChurnChange}
|
||||
/>
|
||||
<Slider
|
||||
label={i.financials.sliderArpu}
|
||||
value={arpu}
|
||||
min={200}
|
||||
max={1500}
|
||||
step={50}
|
||||
unit=" EUR"
|
||||
onChange={onArpuChange}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
{categories.filter(c => c.items.length > 0).map((cat) => {
|
||||
const isOpen = openCategories.has(cat.key)
|
||||
return (
|
||||
<div key={cat.key} className="border border-white/[0.06] rounded-xl overflow-hidden">
|
||||
<button
|
||||
onClick={() => toggleCategory(cat.key)}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-xs text-white/60 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<span className="font-medium">{cat.label}</span>
|
||||
{isOpen ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="px-3 pb-3 space-y-3">
|
||||
{cat.items.map((a) => (
|
||||
<Slider
|
||||
key={a.key}
|
||||
assumption={a}
|
||||
onChange={(val) => onAssumptionChange(a.key, val)}
|
||||
lang={lang}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user