-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
254 lines (228 loc) · 10.5 KB
/
App.tsx
File metadata and controls
254 lines (228 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import React, { useState, useMemo } from 'react';
import PathSelector from './components/PathSelector';
import CapTableView from './components/CapTableView';
import VestingClock from './components/VestingClock';
import MilestoneControls from './components/MilestoneControls';
import PartnerCapitalInfusion from './components/JaviersCapitalInfusion';
import ExitCalculator from './components/ExitCalculator';
import Warnings from './components/Warnings';
import ComparisonPanel from './components/ComparisonPanel';
import FounderSetup from './components/FounderSetup';
import ScenarioSummary from './components/ScenarioSummary';
import PoolTargetInput from './components/PoolTargetInput';
import ExitWaterfall from './components/ExitWaterfall';
import MilestoneTimeline from './components/MilestoneTimeline';
import ScenarioManager, { ScenarioData } from './components/ScenarioManager';
import { Path, Milestones, CapTableEntry, InvestmentVehicle, Founder } from './types';
const initialFounders: Founder[] = [
{ id: '1', name: 'Founder 1', equity: 45 },
{ id: '2', name: 'Founder 2', equity: 45 },
];
function App() {
// State Management
const [selectedPath, setSelectedPath] = useState<Path>('A');
const [monthsElapsed, setMonthsElapsed] = useState<number>(0);
const [milestones, setMilestones] = useState<Milestones>({
capitalRaised: false,
mrrTarget: false,
});
const [investment, setInvestment] = useState<number>(50000);
const [investmentVehicle, setInvestmentVehicle] = useState<InvestmentVehicle>('priced');
const [preMoneyValuation, setPreMoneyValuation] = useState<number>(300000);
const [valuationCap, setValuationCap] = useState<number>(550000);
const [founders, setFounders] = useState<Founder[]>(initialFounders);
const [exitValuation, setExitValuation] = useState<number>(5000000);
// Constants
const STRATEGIC_CAP = 12; // Partner's equity should not exceed 12%
const POOL_MINIMUM = 8; // Unallocated pool should be at least 8%
// Memoized Calculations
const calculations = useMemo(() => {
// Investment-based Equity
let investmentEquity = 0;
let investmentTooltipPrefix = '';
if (investmentVehicle === 'priced') {
const postMoneyValuation = preMoneyValuation + investment;
investmentEquity = (investment / postMoneyValuation) * 100;
investmentTooltipPrefix = `Purchased (Priced Round): ${investmentEquity.toFixed(2)}%`;
} else { // 'safe'
investmentEquity = (investment / valuationCap) * 100;
investmentTooltipPrefix = `From SAFE: ${investmentEquity.toFixed(2)}%`;
}
// Path-dependent variables
const vestingPeriod = selectedPath === 'A' ? 24 : 48;
const cliff = selectedPath === 'A' ? 3 : 12;
const baseGrant = 5.0;
// Performance Equity (Path B only)
let performanceGrant = 0;
if (selectedPath === 'B') {
if (milestones.capitalRaised) performanceGrant += 2.5;
if (milestones.mrrTarget) performanceGrant += 2.5;
}
const totalGrantEquity = baseGrant + performanceGrant;
// Vesting Calculation
const cliffMet = monthsElapsed >= cliff;
const vestedGrantPercentage = cliffMet ? (monthsElapsed / vestingPeriod) : 0;
// Cap vesting at 100% of the grant
const vestedGrantEquity = totalGrantEquity * Math.min(vestedGrantPercentage, 1);
const partnerTotalEquity = investmentEquity + vestedGrantEquity;
// Dilution Calculation
const dilutionFactor = 1 - (partnerTotalEquity / 100);
const dilutedFounders = founders.map(f => ({
...f,
dilutedEquity: f.equity * dilutionFactor,
}));
const totalDilutedFounderEquity = dilutedFounders.reduce((sum, f) => sum + f.dilutedEquity, 0);
const unallocatedPool = 100 - totalDilutedFounderEquity - partnerTotalEquity;
// Cap Table Data
const founderEntries: CapTableEntry[] = dilutedFounders.map(founder => ({
shareholder: founder.name,
equity: founder.dilutedEquity,
color: 'hsl(142.1 76.2% 46.5%)',
tooltip: "Founder"
}));
const capTableData: CapTableEntry[] = [
...founderEntries,
{
shareholder: 'Partner',
equity: partnerTotalEquity,
color: selectedPath === 'A' ? 'hsl(221.2 83.2% 53.3%)' : 'hsl(346.8 77.2% 49.8%)',
tooltip: `${investmentTooltipPrefix}, Vested Grant: ${vestedGrantEquity.toFixed(2)}%`
},
{ shareholder: 'Unallocated Pool', equity: unallocatedPool, color: 'hsl(220 8.9% 46.1%)', tooltip: "Reserved for future hires and advisors. This is often called an 'Employee Option Pool' and is critical for attracting talent." },
];
const partnerTotalPotentialEquity = investmentEquity + totalGrantEquity;
const foundersForExitCalc = dilutedFounders.map(f => ({ name: f.name, dilutedEquity: f.dilutedEquity }));
const totalFounderEquity = totalDilutedFounderEquity;
return {
capTableData,
foundersForExitCalc,
partnerTotalEquity,
unallocatedPool,
partnerTotalPotentialEquity,
investmentEquity,
vestedGrantEquity,
totalFounderEquity,
};
}, [selectedPath, monthsElapsed, milestones, investment, preMoneyValuation, investmentVehicle, valuationCap, founders]);
const { capTableData, foundersForExitCalc, partnerTotalEquity, unallocatedPool, partnerTotalPotentialEquity, investmentEquity, vestedGrantEquity, totalFounderEquity } = calculations;
// Strategic Guardrail Checks
const isPartnerOverCap = partnerTotalPotentialEquity > STRATEGIC_CAP;
const isPoolTooSmall = unallocatedPool < POOL_MINIMUM;
// Build current scenario for saving
const currentScenarioData: ScenarioData = {
selectedPath,
investment,
investmentVehicle,
preMoneyValuation,
valuationCap,
monthsElapsed,
milestones,
founders,
};
return (
<div className="bg-gray-900 text-white min-h-screen font-sans p-4 sm:p-6 lg:p-8">
<div className="max-w-7xl mx-auto">
<header className="text-center mb-8">
<h1 className="text-4xl md:text-5xl font-extrabold tracking-tight">Equity Scenario Modeler</h1>
<p className="mt-3 text-lg text-gray-400 max-w-3xl mx-auto">This tool helps you understand the financial impact of bringing on a new partner. Adjust the sliders and settings below to see how different deal structures affect your company's ownership (cap table) and potential exit payouts.</p>
</header>
<div className="max-w-md mx-auto mb-8">
<PathSelector selectedPath={selectedPath} setSelectedPath={setSelectedPath} />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-8">
{/* Column 1: Inputs & Configuration */}
<div className="space-y-8">
<VestingClock
monthsElapsed={monthsElapsed}
setMonthsElapsed={setMonthsElapsed}
selectedPath={selectedPath}
/>
<MilestoneControls
milestones={milestones}
setMilestones={setMilestones}
isDisabled={selectedPath === 'A'}
/>
<FounderSetup founders={founders} setFounders={setFounders} />
</div>
{/* Column 2: Deal Configuration & Cap Table */}
<div className="space-y-8">
<PartnerCapitalInfusion
investment={investment}
setInvestment={setInvestment}
preMoneyValuation={preMoneyValuation}
setPreMoneyValuation={setPreMoneyValuation}
investmentVehicle={investmentVehicle}
setInvestmentVehicle={setInvestmentVehicle}
valuationCap={valuationCap}
setValuationCap={setValuationCap}
/>
<CapTableView data={capTableData} title="Live Cap Table" />
<PoolTargetInput
currentPool={unallocatedPool}
founders={founders}
partnerEquity={partnerTotalEquity}
/>
</div>
{/* Column 3: Outcomes & Analysis */}
<div className="space-y-8">
<ComparisonPanel
selectedPath={selectedPath}
partnerTotalPotentialEquity={partnerTotalPotentialEquity}
/>
<ScenarioSummary
selectedPath={selectedPath}
investmentEquity={investmentEquity}
vestedGrantEquity={vestedGrantEquity}
partnerTotalPotentialEquity={partnerTotalPotentialEquity}
unallocatedPool={unallocatedPool}
totalFounderEquity={totalFounderEquity}
/>
<MilestoneTimeline
selectedPath={selectedPath}
monthsElapsed={monthsElapsed}
milestones={milestones}
vestingSchedule={{
cliff: selectedPath === 'A' ? 3 : 12,
period: selectedPath === 'A' ? 24 : 48,
grant: 5.0,
baseGrant: 5.0,
performanceGrant: milestones.capitalRaised || milestones.mrrTarget ? 5.0 : 0,
}}
/>
</div>
{/* Column 4: Exit Analysis & Management */}
<div className="space-y-8">
<ExitWaterfall
exitValuation={exitValuation}
setExitValuation={setExitValuation}
partnerEquity={partnerTotalEquity}
foundersEquity={totalFounderEquity}
poolEquity={unallocatedPool}
/>
<ExitCalculator
selectedPath={selectedPath}
partnerVestedEquity={partnerTotalEquity}
founders={foundersForExitCalc}
/>
<ScenarioManager
currentScenario={currentScenarioData}
/>
<Warnings
isPartnerOverCap={isPartnerOverCap}
isPoolTooSmall={isPoolTooSmall}
partnerTotalPotentialEquity={partnerTotalPotentialEquity}
unallocatedPool={unallocatedPool}
selectedPath={selectedPath}
milestones={milestones}
monthsElapsed={monthsElapsed}
/>
</div>
</div>
<footer className="text-center py-8 text-gray-500 text-sm">
Designed & Powered by <a href="https://stackbilt.dev" target="_blank" rel="noopener noreferrer" className="hover:text-gray-300 underline transition-colors">Stackbilt</a>
</footer>
</div>
</div>
);
}
export default App;