@afncdelacru/brady-chat 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1389 -1387
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1357 -1355
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/lib/BradyChatContext.tsx +1 -0
- package/src/lib/EnhancedBradyChat.tsx +34 -3
- package/src/lib/BranchProfitabilityCalculator.tsx +0 -615
- package/src/lib/CalculatorFollowUp.tsx +0 -200
- package/src/lib/CalculatorManager.tsx +0 -37
- package/src/lib/LOEarningsCalculator.tsx +0 -366
- package/src/lib/ProgressiveContactForm.tsx +0 -337
- package/src/lib/RecruitingFlowContext.tsx +0 -259
|
@@ -1,615 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { motion, AnimatePresence } from 'motion/react';
|
|
3
|
-
import { X, CheckCircle } from 'lucide-react';
|
|
4
|
-
import { CalculatorFollowUp } from './CalculatorFollowUp';
|
|
5
|
-
import { ProgressiveContactForm } from './ProgressiveContactForm';
|
|
6
|
-
import { useRecruitingFlow } from '../../../../src/contexts/RecruitingFlowContext';
|
|
7
|
-
|
|
8
|
-
interface BranchProfitabilityCalculatorProps {
|
|
9
|
-
isOpen: boolean;
|
|
10
|
-
onClose: () => void;
|
|
11
|
-
isPersonalized?: boolean;
|
|
12
|
-
defaultVolume?: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function BranchProfitabilityCalculator({
|
|
16
|
-
isOpen,
|
|
17
|
-
onClose,
|
|
18
|
-
isPersonalized = false,
|
|
19
|
-
defaultVolume = 100000000
|
|
20
|
-
}: BranchProfitabilityCalculatorProps) {
|
|
21
|
-
const { trackCalculatorOpen, trackCalculatorAdjustment, trackAction } = useRecruitingFlow();
|
|
22
|
-
|
|
23
|
-
// State for inputs
|
|
24
|
-
const [annualVolume, setAnnualVolume] = useState(defaultVolume);
|
|
25
|
-
const [branchMargin, setBranchMargin] = useState(300); // bps
|
|
26
|
-
const [loCompensation, setLOCompensation] = useState(100); // bps
|
|
27
|
-
const [lenderCredits, setLenderCredits] = useState(50); // bps
|
|
28
|
-
const [monthlyOpEx, setMonthlyOpEx] = useState(40000); // dollars per month
|
|
29
|
-
const [opexReductionEnabled, setOpexReductionEnabled] = useState(true);
|
|
30
|
-
|
|
31
|
-
// Tracking state
|
|
32
|
-
const [hasInteracted, setHasInteracted] = useState(false);
|
|
33
|
-
const [clickedPrimaryCTA, setClickedPrimaryCTA] = useState(false);
|
|
34
|
-
const [showFollowUp, setShowFollowUp] = useState(false);
|
|
35
|
-
const [showForm, setShowForm] = useState(false);
|
|
36
|
-
const [submitted, setSubmitted] = useState(false);
|
|
37
|
-
const [startTime, setStartTime] = useState<number>(0);
|
|
38
|
-
const [volumeAdjusted, setVolumeAdjusted] = useState(false);
|
|
39
|
-
const [marginAdjusted, setMarginAdjusted] = useState(false);
|
|
40
|
-
const hasTrackedOpen = useRef(false);
|
|
41
|
-
const modalRef = useRef<HTMLDivElement>(null);
|
|
42
|
-
|
|
43
|
-
// Track calculator open
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (isOpen && !hasTrackedOpen.current) {
|
|
46
|
-
trackCalculatorOpen('BM');
|
|
47
|
-
setStartTime(Date.now());
|
|
48
|
-
hasTrackedOpen.current = true;
|
|
49
|
-
} else if (!isOpen) {
|
|
50
|
-
hasTrackedOpen.current = false;
|
|
51
|
-
}
|
|
52
|
-
}, [isOpen, trackCalculatorOpen]);
|
|
53
|
-
|
|
54
|
-
// Helper function to get volume slider step
|
|
55
|
-
const getVolumeStep = (value: number) => {
|
|
56
|
-
if (value <= 60000000) return 5000000; // $5M increments up to $60M
|
|
57
|
-
return 10000000; // $10M increments above $60M
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Helper function to get OpEx slider step
|
|
61
|
-
const getOpExStep = (value: number) => {
|
|
62
|
-
if (value <= 20000) return 1000; // $1k increments up to $20k
|
|
63
|
-
return 5000; // $5k increments above $20k
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Derived calculations - MONTHLY
|
|
67
|
-
const monthlyVolume = annualVolume / 12;
|
|
68
|
-
const grossProfitBps = branchMargin - loCompensation - lenderCredits;
|
|
69
|
-
|
|
70
|
-
// Current scenario calculations (MONTHLY)
|
|
71
|
-
const currentRevenue = (monthlyVolume * branchMargin) / 10000;
|
|
72
|
-
const currentLOComp = (monthlyVolume * loCompensation) / 10000;
|
|
73
|
-
const currentLenderCredits = (monthlyVolume * lenderCredits) / 10000;
|
|
74
|
-
const currentGrossProfit = (monthlyVolume * grossProfitBps) / 10000;
|
|
75
|
-
const currentOpEx = monthlyOpEx;
|
|
76
|
-
const currentBranchProfit = currentGrossProfit - currentOpEx;
|
|
77
|
-
|
|
78
|
-
// AFN scenario calculations (MONTHLY)
|
|
79
|
-
const volumeIncrease = 0.17; // 17% midpoint of 12-22% range
|
|
80
|
-
const afnMonthlyVolume = monthlyVolume * (1 + volumeIncrease);
|
|
81
|
-
const afnRevenue = (afnMonthlyVolume * branchMargin) / 10000;
|
|
82
|
-
const afnLOComp = (afnMonthlyVolume * loCompensation) / 10000;
|
|
83
|
-
const afnLenderCredits = (afnMonthlyVolume * lenderCredits) / 10000;
|
|
84
|
-
const afnGrossProfit = (afnMonthlyVolume * grossProfitBps) / 10000;
|
|
85
|
-
|
|
86
|
-
// Operating expenses with optional reduction
|
|
87
|
-
const opexReductionFactor = opexReductionEnabled ? 0.75 : 1.0;
|
|
88
|
-
const afnOpEx = monthlyOpEx * opexReductionFactor;
|
|
89
|
-
const afnBranchProfit = afnGrossProfit - afnOpEx;
|
|
90
|
-
|
|
91
|
-
const profitDelta = afnBranchProfit - currentBranchProfit;
|
|
92
|
-
|
|
93
|
-
// Format currency
|
|
94
|
-
const formatCurrency = (value: number) => {
|
|
95
|
-
return new Intl.NumberFormat('en-US', {
|
|
96
|
-
style: 'currency',
|
|
97
|
-
currency: 'USD',
|
|
98
|
-
minimumFractionDigits: 0,
|
|
99
|
-
maximumFractionDigits: 0,
|
|
100
|
-
}).format(value);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// Format volume
|
|
104
|
-
const formatVolume = (value: number) => {
|
|
105
|
-
if (value >= 1000000) {
|
|
106
|
-
return `$${(value / 1000000).toFixed(0)}M`;
|
|
107
|
-
}
|
|
108
|
-
return formatCurrency(value);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
// Handle volume slider change with variable steps
|
|
112
|
-
const handleVolumeChange = (rawValue: number) => {
|
|
113
|
-
const step = getVolumeStep(rawValue);
|
|
114
|
-
const value = Math.round(rawValue / step) * step;
|
|
115
|
-
setAnnualVolume(value);
|
|
116
|
-
setHasInteracted(true);
|
|
117
|
-
setVolumeAdjusted(true);
|
|
118
|
-
trackCalculatorAdjustment();
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Handle OpEx slider change with variable steps
|
|
122
|
-
const handleOpExChange = (rawValue: number) => {
|
|
123
|
-
const step = getOpExStep(rawValue);
|
|
124
|
-
const value = Math.round(rawValue / step) * step;
|
|
125
|
-
setMonthlyOpEx(value);
|
|
126
|
-
setHasInteracted(true);
|
|
127
|
-
setMarginAdjusted(true);
|
|
128
|
-
trackCalculatorAdjustment();
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Handle margin changes
|
|
132
|
-
const handleMarginChange = (value: number, field: 'margin' | 'loComp' | 'credits') => {
|
|
133
|
-
if (field === 'margin') setBranchMargin(value);
|
|
134
|
-
if (field === 'loComp') setLOCompensation(value);
|
|
135
|
-
if (field === 'credits') setLenderCredits(value);
|
|
136
|
-
setHasInteracted(true);
|
|
137
|
-
setMarginAdjusted(true);
|
|
138
|
-
trackCalculatorAdjustment();
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Handle primary CTA click
|
|
142
|
-
const handlePrimaryCTA = () => {
|
|
143
|
-
setClickedPrimaryCTA(true);
|
|
144
|
-
setHasInteracted(true);
|
|
145
|
-
setShowForm(true);
|
|
146
|
-
trackAction('primary_cta_click', 'high');
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Handle close without clicking primary CTA
|
|
150
|
-
const handleClose = () => {
|
|
151
|
-
if (!showForm) {
|
|
152
|
-
onClose();
|
|
153
|
-
// If user interacted but didn't click primary CTA, show follow-up
|
|
154
|
-
if (hasInteracted && !clickedPrimaryCTA) {
|
|
155
|
-
setTimeout(() => setShowFollowUp(true), 300);
|
|
156
|
-
trackAction('close_without_primary_cta');
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
// Go back from form to calculator
|
|
160
|
-
setShowForm(false);
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
// Handle form submission
|
|
165
|
-
const handleFormSubmit = (data: any) => {
|
|
166
|
-
console.log('Form submitted:', data);
|
|
167
|
-
setSubmitted(true);
|
|
168
|
-
|
|
169
|
-
// Tag submission with calculator data
|
|
170
|
-
const submissionData = {
|
|
171
|
-
calculatorType: 'Branch P&L',
|
|
172
|
-
volumeAdjusted,
|
|
173
|
-
marginAdjusted,
|
|
174
|
-
opexReductionEnabled,
|
|
175
|
-
afnProfitDelta: profitDelta,
|
|
176
|
-
...data
|
|
177
|
-
};
|
|
178
|
-
console.log('Tagged submission:', submissionData);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Reset state when modal closes
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
if (!isOpen) {
|
|
184
|
-
setTimeout(() => {
|
|
185
|
-
setHasInteracted(false);
|
|
186
|
-
setClickedPrimaryCTA(false);
|
|
187
|
-
setShowForm(false);
|
|
188
|
-
setSubmitted(false);
|
|
189
|
-
}, 300);
|
|
190
|
-
}
|
|
191
|
-
}, [isOpen]);
|
|
192
|
-
|
|
193
|
-
if (!isOpen) return null;
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<>
|
|
197
|
-
{/* Overlay */}
|
|
198
|
-
<AnimatePresence>
|
|
199
|
-
{isOpen && (
|
|
200
|
-
<motion.div
|
|
201
|
-
initial={{ opacity: 0 }}
|
|
202
|
-
animate={{ opacity: 1 }}
|
|
203
|
-
exit={{ opacity: 0 }}
|
|
204
|
-
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50"
|
|
205
|
-
onClick={handleClose}
|
|
206
|
-
/>
|
|
207
|
-
)}
|
|
208
|
-
</AnimatePresence>
|
|
209
|
-
|
|
210
|
-
{/* Modal */}
|
|
211
|
-
<AnimatePresence>
|
|
212
|
-
{isOpen && (
|
|
213
|
-
<motion.div
|
|
214
|
-
ref={modalRef}
|
|
215
|
-
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
216
|
-
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
217
|
-
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
|
218
|
-
transition={{ duration: 0.2 }}
|
|
219
|
-
role="dialog"
|
|
220
|
-
aria-modal="true"
|
|
221
|
-
className="fixed inset-4 md:inset-auto md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:w-[900px] md:max-h-[90vh] bg-white dark:bg-zinc-900 rounded-2xl shadow-2xl z-50 flex flex-col overflow-hidden"
|
|
222
|
-
>
|
|
223
|
-
{/* Header */}
|
|
224
|
-
<div className="flex items-start justify-between p-6 border-b border-zinc-200 dark:border-zinc-800">
|
|
225
|
-
<div className="flex-1">
|
|
226
|
-
<h2 className="text-2xl text-zinc-900 dark:text-white mb-2">
|
|
227
|
-
Model Your Branch Profitability
|
|
228
|
-
</h2>
|
|
229
|
-
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
|
230
|
-
Adjust the assumptions below to see how branch economics change with higher production and lower operating expenses.
|
|
231
|
-
</p>
|
|
232
|
-
</div>
|
|
233
|
-
<button
|
|
234
|
-
onClick={handleClose}
|
|
235
|
-
className="ml-4 p-2 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 transition-colors"
|
|
236
|
-
>
|
|
237
|
-
<X className="w-5 h-5" />
|
|
238
|
-
</button>
|
|
239
|
-
</div>
|
|
240
|
-
|
|
241
|
-
{/* Content */}
|
|
242
|
-
<div className="flex-1 overflow-y-auto">
|
|
243
|
-
<div className="p-6 space-y-8">
|
|
244
|
-
{submitted ? (
|
|
245
|
-
// Success State
|
|
246
|
-
<div className="space-y-6 text-center py-8">
|
|
247
|
-
<div className="flex justify-center">
|
|
248
|
-
<CheckCircle className="w-16 h-16 text-green-500" />
|
|
249
|
-
</div>
|
|
250
|
-
<div className="space-y-2">
|
|
251
|
-
<h3 className="text-2xl text-zinc-900 dark:text-white">
|
|
252
|
-
Thanks — We'll Be In Touch
|
|
253
|
-
</h3>
|
|
254
|
-
<p className="text-base text-zinc-600 dark:text-zinc-400">
|
|
255
|
-
We'll follow up shortly to build a realistic pro forma P&L using AFN rates and support structure.
|
|
256
|
-
</p>
|
|
257
|
-
<p className="text-sm text-zinc-500 dark:text-zinc-500">
|
|
258
|
-
In the meantime, feel free to keep exploring.
|
|
259
|
-
</p>
|
|
260
|
-
</div>
|
|
261
|
-
<button
|
|
262
|
-
onClick={onClose}
|
|
263
|
-
className="w-full px-6 py-3 bg-gradient-to-r from-[#4399D1] to-[#2B7AB8] hover:from-[#2B7AB8] hover:to-[#4399D1] text-white rounded-lg transition-all duration-300 shadow-lg"
|
|
264
|
-
>
|
|
265
|
-
Continue Exploring
|
|
266
|
-
</button>
|
|
267
|
-
</div>
|
|
268
|
-
) : showForm ? (
|
|
269
|
-
// Form State
|
|
270
|
-
<div className="space-y-4">
|
|
271
|
-
<p className="text-base text-zinc-600 dark:text-zinc-400">
|
|
272
|
-
We'll help you build a realistic pro forma P&L using AFN rates, support structure, and your branch goals.
|
|
273
|
-
</p>
|
|
274
|
-
<ProgressiveContactForm
|
|
275
|
-
onSubmit={handleFormSubmit}
|
|
276
|
-
onCancel={() => setShowForm(false)}
|
|
277
|
-
primaryCTA="Review My Branch Scenario"
|
|
278
|
-
secondaryCTA="Go Back"
|
|
279
|
-
context='BM'
|
|
280
|
-
/>
|
|
281
|
-
</div>
|
|
282
|
-
) : (
|
|
283
|
-
// Calculator State
|
|
284
|
-
<>
|
|
285
|
-
{/* Section 1: Baseline Branch Inputs */}
|
|
286
|
-
<div className="space-y-6">
|
|
287
|
-
<h3 className="text-lg text-zinc-900 dark:text-white">
|
|
288
|
-
{isPersonalized ? 'Your Current Branch Assumptions' : 'Current Branch Assumptions'}
|
|
289
|
-
</h3>
|
|
290
|
-
|
|
291
|
-
{/* Two-column grid for inputs */}
|
|
292
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
293
|
-
{/* Annual Volume */}
|
|
294
|
-
<div className="space-y-3">
|
|
295
|
-
<div className="flex items-baseline justify-between">
|
|
296
|
-
<label className="text-sm text-zinc-700 dark:text-zinc-300">
|
|
297
|
-
Annual Funded Volume
|
|
298
|
-
</label>
|
|
299
|
-
<span className="text-base text-zinc-900 dark:text-white font-medium">
|
|
300
|
-
{formatVolume(annualVolume)}
|
|
301
|
-
</span>
|
|
302
|
-
</div>
|
|
303
|
-
<input
|
|
304
|
-
type="range"
|
|
305
|
-
min={20000000}
|
|
306
|
-
max={300000000}
|
|
307
|
-
step={5000000}
|
|
308
|
-
value={annualVolume}
|
|
309
|
-
onChange={(e) => handleVolumeChange(Number(e.target.value))}
|
|
310
|
-
className="w-full h-2 bg-zinc-200 dark:bg-zinc-700 rounded-lg appearance-none cursor-pointer slider-thumb"
|
|
311
|
-
/>
|
|
312
|
-
<div className="flex justify-between text-xs text-zinc-500">
|
|
313
|
-
<span>$20M</span>
|
|
314
|
-
<span>$300M</span>
|
|
315
|
-
</div>
|
|
316
|
-
</div>
|
|
317
|
-
|
|
318
|
-
{/* Branch Margin */}
|
|
319
|
-
<div className="space-y-3">
|
|
320
|
-
<div className="flex items-baseline justify-between">
|
|
321
|
-
<label className="text-sm text-zinc-700 dark:text-zinc-300">
|
|
322
|
-
Avg Branch Margin (bps)
|
|
323
|
-
</label>
|
|
324
|
-
<span className="text-base text-zinc-900 dark:text-white font-medium">
|
|
325
|
-
{branchMargin} bps
|
|
326
|
-
</span>
|
|
327
|
-
</div>
|
|
328
|
-
<input
|
|
329
|
-
type="range"
|
|
330
|
-
min={200}
|
|
331
|
-
max={500}
|
|
332
|
-
step={10}
|
|
333
|
-
value={branchMargin}
|
|
334
|
-
onChange={(e) => handleMarginChange(Number(e.target.value), 'margin')}
|
|
335
|
-
className="w-full h-2 bg-zinc-200 dark:bg-zinc-700 rounded-lg appearance-none cursor-pointer slider-thumb"
|
|
336
|
-
/>
|
|
337
|
-
<p className="text-xs text-zinc-500">
|
|
338
|
-
Blended margin across conv. and govt loans
|
|
339
|
-
</p>
|
|
340
|
-
</div>
|
|
341
|
-
|
|
342
|
-
{/* LO Compensation */}
|
|
343
|
-
<div className="space-y-3">
|
|
344
|
-
<div className="flex items-baseline justify-between">
|
|
345
|
-
<label className="text-sm text-zinc-700 dark:text-zinc-300">
|
|
346
|
-
LO Commission (bps)
|
|
347
|
-
</label>
|
|
348
|
-
<span className="text-base text-zinc-900 dark:text-white font-medium">
|
|
349
|
-
{loCompensation} bps
|
|
350
|
-
</span>
|
|
351
|
-
</div>
|
|
352
|
-
<input
|
|
353
|
-
type="range"
|
|
354
|
-
min={50}
|
|
355
|
-
max={200}
|
|
356
|
-
step={5}
|
|
357
|
-
value={loCompensation}
|
|
358
|
-
onChange={(e) => handleMarginChange(Number(e.target.value), 'loComp')}
|
|
359
|
-
className="w-full h-2 bg-zinc-200 dark:bg-zinc-700 rounded-lg appearance-none cursor-pointer slider-thumb"
|
|
360
|
-
/>
|
|
361
|
-
</div>
|
|
362
|
-
|
|
363
|
-
{/* Lender Credits */}
|
|
364
|
-
<div className="space-y-3">
|
|
365
|
-
<div className="flex items-baseline justify-between">
|
|
366
|
-
<label className="text-sm text-zinc-700 dark:text-zinc-300">
|
|
367
|
-
Lender Credits (bps)
|
|
368
|
-
</label>
|
|
369
|
-
<span className="text-base text-zinc-900 dark:text-white font-medium">
|
|
370
|
-
{lenderCredits} bps
|
|
371
|
-
</span>
|
|
372
|
-
</div>
|
|
373
|
-
<input
|
|
374
|
-
type="range"
|
|
375
|
-
min={0}
|
|
376
|
-
max={150}
|
|
377
|
-
step={5}
|
|
378
|
-
value={lenderCredits}
|
|
379
|
-
onChange={(e) => handleMarginChange(Number(e.target.value), 'credits')}
|
|
380
|
-
className="w-full h-2 bg-zinc-200 dark:bg-zinc-700 rounded-lg appearance-none cursor-pointer slider-thumb"
|
|
381
|
-
/>
|
|
382
|
-
</div>
|
|
383
|
-
</div>
|
|
384
|
-
|
|
385
|
-
{/* Gross Profit Display */}
|
|
386
|
-
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
387
|
-
<div className="flex items-baseline justify-between">
|
|
388
|
-
<span className="text-sm text-zinc-700 dark:text-zinc-300">Gross Profit Margin</span>
|
|
389
|
-
<span className="text-base text-zinc-900 dark:text-white font-medium">
|
|
390
|
-
{grossProfitBps} bps
|
|
391
|
-
</span>
|
|
392
|
-
</div>
|
|
393
|
-
</div>
|
|
394
|
-
|
|
395
|
-
{/* Operating Expenses - Full Width */}
|
|
396
|
-
<div className="space-y-3">
|
|
397
|
-
<div className="flex items-baseline justify-between">
|
|
398
|
-
<label className="text-sm text-zinc-700 dark:text-zinc-300">
|
|
399
|
-
Monthly Operating Expenses
|
|
400
|
-
</label>
|
|
401
|
-
<span className="text-base text-zinc-900 dark:text-white font-medium">
|
|
402
|
-
{formatCurrency(monthlyOpEx)}/mo
|
|
403
|
-
</span>
|
|
404
|
-
</div>
|
|
405
|
-
<input
|
|
406
|
-
type="range"
|
|
407
|
-
min={0}
|
|
408
|
-
max={100000}
|
|
409
|
-
step={1000}
|
|
410
|
-
value={monthlyOpEx}
|
|
411
|
-
onChange={(e) => handleOpExChange(Number(e.target.value))}
|
|
412
|
-
className="w-full h-2 bg-zinc-200 dark:bg-zinc-700 rounded-lg appearance-none cursor-pointer slider-thumb"
|
|
413
|
-
/>
|
|
414
|
-
<div className="flex justify-between text-xs text-zinc-500">
|
|
415
|
-
<span>$0</span>
|
|
416
|
-
<span>$100k/mo</span>
|
|
417
|
-
</div>
|
|
418
|
-
<p className="text-xs text-zinc-500">
|
|
419
|
-
Includes rent, staff, marketing, technology, and overhead
|
|
420
|
-
</p>
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
423
|
-
|
|
424
|
-
{/* Section 2: P&L Comparison */}
|
|
425
|
-
<div className="space-y-4">
|
|
426
|
-
<h3 className="text-lg text-zinc-900 dark:text-white">
|
|
427
|
-
Monthly Branch P&L Comparison
|
|
428
|
-
</h3>
|
|
429
|
-
|
|
430
|
-
<div className="overflow-x-auto">
|
|
431
|
-
<table className="w-full text-sm">
|
|
432
|
-
<thead>
|
|
433
|
-
<tr className="border-b border-zinc-200 dark:border-zinc-700">
|
|
434
|
-
<th className="text-left py-3 text-zinc-600 dark:text-zinc-400 font-medium">Metric</th>
|
|
435
|
-
<th className="text-right py-3 text-zinc-600 dark:text-zinc-400 font-medium">Current</th>
|
|
436
|
-
<th className="text-right py-3 text-[#8B5CF6] font-medium">With AFN</th>
|
|
437
|
-
</tr>
|
|
438
|
-
</thead>
|
|
439
|
-
<tbody className="divide-y divide-zinc-100 dark:divide-zinc-800">
|
|
440
|
-
<tr>
|
|
441
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">Monthly Funded Volume</td>
|
|
442
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatVolume(monthlyVolume)}</td>
|
|
443
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatVolume(afnMonthlyVolume)}</td>
|
|
444
|
-
</tr>
|
|
445
|
-
<tr>
|
|
446
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">Branch Margin (bps)</td>
|
|
447
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{branchMargin}</td>
|
|
448
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{branchMargin}</td>
|
|
449
|
-
</tr>
|
|
450
|
-
<tr>
|
|
451
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">Revenue</td>
|
|
452
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(currentRevenue)}</td>
|
|
453
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(afnRevenue)}</td>
|
|
454
|
-
</tr>
|
|
455
|
-
<tr>
|
|
456
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">LO Compensation</td>
|
|
457
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(currentLOComp)}</td>
|
|
458
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(afnLOComp)}</td>
|
|
459
|
-
</tr>
|
|
460
|
-
<tr>
|
|
461
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">Lender Credits</td>
|
|
462
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(currentLenderCredits)}</td>
|
|
463
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(afnLenderCredits)}</td>
|
|
464
|
-
</tr>
|
|
465
|
-
<tr className="font-medium">
|
|
466
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">Gross Profit</td>
|
|
467
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(currentGrossProfit)}</td>
|
|
468
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(afnGrossProfit)}</td>
|
|
469
|
-
</tr>
|
|
470
|
-
<tr>
|
|
471
|
-
<td className="py-3 text-zinc-700 dark:text-zinc-300">Operating Expenses</td>
|
|
472
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(currentOpEx)}</td>
|
|
473
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(afnOpEx)}</td>
|
|
474
|
-
</tr>
|
|
475
|
-
<tr className="font-bold border-t-2 border-zinc-300 dark:border-zinc-600">
|
|
476
|
-
<td className="py-3 text-zinc-900 dark:text-white">Branch Profit</td>
|
|
477
|
-
<td className="py-3 text-right text-zinc-900 dark:text-white">{formatCurrency(currentBranchProfit)}</td>
|
|
478
|
-
<td className="py-3 text-right text-[#8B5CF6]">{formatCurrency(afnBranchProfit)}</td>
|
|
479
|
-
</tr>
|
|
480
|
-
</tbody>
|
|
481
|
-
</table>
|
|
482
|
-
</div>
|
|
483
|
-
</div>
|
|
484
|
-
|
|
485
|
-
{/* Section 3: AFN Assumptions */}
|
|
486
|
-
<div className="space-y-4">
|
|
487
|
-
<h3 className="text-lg text-zinc-900 dark:text-white">
|
|
488
|
-
AFN Scenario Assumptions
|
|
489
|
-
</h3>
|
|
490
|
-
|
|
491
|
-
{/* Volume Increase */}
|
|
492
|
-
<div className="p-4 bg-[#8B5CF6]/5 dark:bg-[#8B5CF6]/10 rounded-lg border border-[#8B5CF6]/20">
|
|
493
|
-
<div className="flex items-start justify-between mb-2">
|
|
494
|
-
<span className="text-sm text-zinc-700 dark:text-zinc-300">Monthly Funded Volume Increase</span>
|
|
495
|
-
<span className="text-base text-[#8B5CF6] font-medium">+12% to +22%</span>
|
|
496
|
-
</div>
|
|
497
|
-
<p className="text-xs text-zinc-600 dark:text-zinc-400">
|
|
498
|
-
Driven by faster turn times, higher pull-through, and AI-assisted workflows.
|
|
499
|
-
</p>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
{/* Operating Expense Reduction Toggle */}
|
|
503
|
-
<div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg border border-zinc-200 dark:border-zinc-700">
|
|
504
|
-
<div className="flex items-start justify-between mb-2">
|
|
505
|
-
<div className="flex-1">
|
|
506
|
-
<div className="flex items-center gap-3 mb-2">
|
|
507
|
-
<span className="text-sm text-zinc-700 dark:text-zinc-300">Operating Expense Reduction</span>
|
|
508
|
-
<button
|
|
509
|
-
onClick={() => {
|
|
510
|
-
setOpexReductionEnabled(!opexReductionEnabled);
|
|
511
|
-
setHasInteracted(true);
|
|
512
|
-
trackCalculatorAdjustment();
|
|
513
|
-
}}
|
|
514
|
-
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
|
|
515
|
-
opexReductionEnabled
|
|
516
|
-
? 'bg-[#8B5CF6]'
|
|
517
|
-
: 'bg-zinc-300 dark:bg-zinc-600'
|
|
518
|
-
}`}
|
|
519
|
-
>
|
|
520
|
-
<span
|
|
521
|
-
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
|
522
|
-
opexReductionEnabled ? 'translate-x-6' : 'translate-x-1'
|
|
523
|
-
}`}
|
|
524
|
-
/>
|
|
525
|
-
</button>
|
|
526
|
-
<span className="text-base text-[#8B5CF6] font-medium">25%</span>
|
|
527
|
-
</div>
|
|
528
|
-
<p className="text-xs text-zinc-600 dark:text-zinc-400">
|
|
529
|
-
Technology leverage and centralized support reduce per-loan operating costs.
|
|
530
|
-
</p>
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
|
-
</div>
|
|
534
|
-
|
|
535
|
-
{/* Unchanged Assumptions */}
|
|
536
|
-
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
537
|
-
<p className="text-xs text-zinc-700 dark:text-zinc-300">
|
|
538
|
-
Branch margin, LO compensation, and lender credits are unchanged in this scenario.
|
|
539
|
-
</p>
|
|
540
|
-
</div>
|
|
541
|
-
</div>
|
|
542
|
-
|
|
543
|
-
{/* Section 4: Highlighted Outcome */}
|
|
544
|
-
<div className="p-6 bg-gradient-to-br from-[#8B5CF6]/10 to-[#8B5CF6]/5 dark:from-[#8B5CF6]/20 dark:to-[#8B5CF6]/10 rounded-xl border-2 border-[#8B5CF6]/30 shadow-[0_0_30px_rgba(139,92,246,0.15)]">
|
|
545
|
-
<div className="space-y-4">
|
|
546
|
-
<div>
|
|
547
|
-
<p className="text-sm text-zinc-600 dark:text-zinc-400 mb-2">
|
|
548
|
-
Modeled Monthly Branch Profit at AFN
|
|
549
|
-
</p>
|
|
550
|
-
<p className="text-5xl text-[#8B5CF6] font-bold drop-shadow-[0_0_20px_rgba(139,92,246,0.3)]">
|
|
551
|
-
{formatCurrency(afnBranchProfit)}
|
|
552
|
-
</p>
|
|
553
|
-
<p className="text-xs text-zinc-500 dark:text-zinc-500 mt-2">
|
|
554
|
-
Illustrative scenario based on your inputs.
|
|
555
|
-
</p>
|
|
556
|
-
</div>
|
|
557
|
-
<div className="pt-4 border-t border-[#8B5CF6]/20">
|
|
558
|
-
<p className="text-sm text-zinc-600 dark:text-zinc-400 mb-1">
|
|
559
|
-
Incremental Monthly Branch Profit
|
|
560
|
-
</p>
|
|
561
|
-
<p className="text-2xl text-green-600 dark:text-green-400 font-bold">
|
|
562
|
-
+ {formatCurrency(profitDelta)}
|
|
563
|
-
</p>
|
|
564
|
-
</div>
|
|
565
|
-
</div>
|
|
566
|
-
</div>
|
|
567
|
-
|
|
568
|
-
{/* Section 5: Disclaimers */}
|
|
569
|
-
<div className="text-[10px] text-zinc-400 dark:text-zinc-600 leading-relaxed space-y-1">
|
|
570
|
-
<p>This model is illustrative only and does not represent a guarantee of results.</p>
|
|
571
|
-
<p>Actual margins, expenses, and profitability vary by branch, market, and compensation structure.</p>
|
|
572
|
-
</div>
|
|
573
|
-
</>
|
|
574
|
-
)}
|
|
575
|
-
</div>
|
|
576
|
-
</div>
|
|
577
|
-
|
|
578
|
-
{/* Sticky Footer CTA */}
|
|
579
|
-
{!submitted && !showForm && (
|
|
580
|
-
<div className="p-6 border-t border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900">
|
|
581
|
-
<div className="space-y-3">
|
|
582
|
-
<p className="text-xs text-center text-zinc-600 dark:text-zinc-400">
|
|
583
|
-
We'll help you build a realistic pro forma P&L using AFN rates, support structure, and your branch goals.
|
|
584
|
-
</p>
|
|
585
|
-
<div className="flex gap-3">
|
|
586
|
-
<button
|
|
587
|
-
onClick={handleClose}
|
|
588
|
-
className="flex-1 px-6 py-3 border border-zinc-300 dark:border-zinc-700 text-zinc-700 dark:text-zinc-300 rounded-lg hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-colors"
|
|
589
|
-
>
|
|
590
|
-
Close
|
|
591
|
-
</button>
|
|
592
|
-
<button
|
|
593
|
-
onClick={handlePrimaryCTA}
|
|
594
|
-
className="flex-1 px-6 py-3 bg-gradient-to-r from-[#8B5CF6] to-[#7C3AED] hover:from-[#7C3AED] hover:to-[#8B5CF6] text-white rounded-lg transition-all duration-300 shadow-lg"
|
|
595
|
-
>
|
|
596
|
-
Sanity-Check This With AFN
|
|
597
|
-
</button>
|
|
598
|
-
</div>
|
|
599
|
-
</div>
|
|
600
|
-
</div>
|
|
601
|
-
)}
|
|
602
|
-
</motion.div>
|
|
603
|
-
)}
|
|
604
|
-
</AnimatePresence>
|
|
605
|
-
|
|
606
|
-
{/* Follow-Up Modal */}
|
|
607
|
-
<CalculatorFollowUp
|
|
608
|
-
isOpen={showFollowUp}
|
|
609
|
-
onClose={() => setShowFollowUp(false)}
|
|
610
|
-
isBranchManager={true}
|
|
611
|
-
clickedPrimaryCTA={clickedPrimaryCTA}
|
|
612
|
-
/>
|
|
613
|
-
</>
|
|
614
|
-
);
|
|
615
|
-
}
|