@afncdelacru/brady-chat 0.1.0

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.
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { motion } from 'motion/react';
5
+
6
+ interface InfoRequestFormProps {
7
+ onSubmit: (data: { firstName: string; lastName: string; email: string; phone?: string }) => void;
8
+ onCancel: () => void;
9
+ }
10
+
11
+ export function InfoRequestForm({ onSubmit, onCancel }: InfoRequestFormProps) {
12
+ const [formData, setFormData] = useState({
13
+ firstName: '',
14
+ lastName: '',
15
+ email: '',
16
+ phone: '',
17
+ });
18
+ const [errors, setErrors] = useState<Record<string, string>>({});
19
+
20
+ const handleSubmit = (e: React.FormEvent) => {
21
+ e.preventDefault();
22
+ const newErrors: Record<string, string> = {};
23
+
24
+ if (!formData.firstName.trim()) {
25
+ newErrors.firstName = 'First name is required';
26
+ }
27
+ if (!formData.lastName.trim()) {
28
+ newErrors.lastName = 'Last name is required';
29
+ }
30
+ if (!formData.email.trim()) {
31
+ newErrors.email = 'Email is required';
32
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
33
+ newErrors.email = 'Please enter a valid email address';
34
+ }
35
+
36
+ if (Object.keys(newErrors).length > 0) {
37
+ setErrors(newErrors);
38
+ return;
39
+ }
40
+
41
+ onSubmit(formData);
42
+ };
43
+
44
+ return (
45
+ <motion.div
46
+ initial={{ opacity: 0, y: 10 }}
47
+ animate={{ opacity: 1, y: 0 }}
48
+ className="bg-gradient-to-br from-white to-zinc-50 dark:from-zinc-800 dark:to-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-2xl p-6 max-h-[360px]"
49
+ >
50
+ <h3 className="text-lg text-zinc-900 dark:text-white mb-4">Send Me More Information</h3>
51
+
52
+ <form onSubmit={handleSubmit} className="space-y-3">
53
+ <div>
54
+ <input
55
+ type="text"
56
+ placeholder="First Name"
57
+ value={formData.firstName}
58
+ onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
59
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
60
+ />
61
+ {errors.firstName && <p className="text-red-500 text-xs mt-1">{errors.firstName}</p>}
62
+ </div>
63
+
64
+ <div>
65
+ <input
66
+ type="text"
67
+ placeholder="Last Name"
68
+ value={formData.lastName}
69
+ onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
70
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
71
+ />
72
+ {errors.lastName && <p className="text-red-500 text-xs mt-1">{errors.lastName}</p>}
73
+ </div>
74
+
75
+ <div>
76
+ <input
77
+ type="email"
78
+ placeholder="Email Address"
79
+ value={formData.email}
80
+ onChange={(e) => setFormData({ ...formData, email: e.target.value })}
81
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
82
+ />
83
+ {errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
84
+ </div>
85
+
86
+ <div>
87
+ <input
88
+ type="tel"
89
+ placeholder="Phone (optional)"
90
+ value={formData.phone}
91
+ onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
92
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
93
+ />
94
+ </div>
95
+
96
+ <div className="flex gap-2 pt-2">
97
+ <button
98
+ type="submit"
99
+ className="flex-1 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white py-2.5 rounded-lg transition-colors"
100
+ >
101
+ Send Information
102
+ </button>
103
+ <button
104
+ type="button"
105
+ onClick={onCancel}
106
+ className="px-4 text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white transition-colors"
107
+ >
108
+ Cancel
109
+ </button>
110
+ </div>
111
+ </form>
112
+ </motion.div>
113
+ );
114
+ }
115
+
@@ -0,0 +1,161 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { motion } from 'motion/react';
5
+ import { Calendar, Clock } from 'lucide-react';
6
+
7
+ interface LeadershipCallFormProps {
8
+ onSubmit: (data: {
9
+ firstName: string;
10
+ lastName: string;
11
+ email: string;
12
+ phone: string;
13
+ preferredDate?: string;
14
+ preferredTime?: string;
15
+ }) => void;
16
+ onCancel: () => void;
17
+ }
18
+
19
+ export function LeadershipCallForm({ onSubmit, onCancel }: LeadershipCallFormProps) {
20
+ const [formData, setFormData] = useState({
21
+ firstName: '',
22
+ lastName: '',
23
+ email: '',
24
+ phone: '',
25
+ preferredDate: '',
26
+ preferredTime: '',
27
+ });
28
+ const [errors, setErrors] = useState<Record<string, string>>({});
29
+
30
+ const handleSubmit = (e: React.FormEvent) => {
31
+ e.preventDefault();
32
+ const newErrors: Record<string, string> = {};
33
+
34
+ if (!formData.firstName.trim()) {
35
+ newErrors.firstName = 'First name is required';
36
+ }
37
+ if (!formData.lastName.trim()) {
38
+ newErrors.lastName = 'Last name is required';
39
+ }
40
+ if (!formData.email.trim()) {
41
+ newErrors.email = 'Email is required';
42
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
43
+ newErrors.email = 'Please enter a valid email address';
44
+ }
45
+ if (!formData.phone.trim()) {
46
+ newErrors.phone = 'Phone number is required';
47
+ }
48
+
49
+ if (Object.keys(newErrors).length > 0) {
50
+ setErrors(newErrors);
51
+ return;
52
+ }
53
+
54
+ onSubmit(formData);
55
+ };
56
+
57
+ const timeSlots = ['Morning (8am - 12pm)', 'Afternoon (12pm - 5pm)', 'Evening (5pm - 8pm)'];
58
+
59
+ return (
60
+ <motion.div
61
+ initial={{ opacity: 0, y: 10 }}
62
+ animate={{ opacity: 1, y: 0 }}
63
+ className="bg-gradient-to-br from-white to-zinc-50 dark:from-zinc-800 dark:to-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-2xl p-6"
64
+ >
65
+ <h3 className="text-lg text-zinc-900 dark:text-white mb-4">Talk to AFN Leadership</h3>
66
+
67
+ <form onSubmit={handleSubmit} className="space-y-3">
68
+ <div>
69
+ <input
70
+ type="text"
71
+ placeholder="First Name"
72
+ value={formData.firstName}
73
+ onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
74
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
75
+ />
76
+ {errors.firstName && <p className="text-red-500 text-xs mt-1">{errors.firstName}</p>}
77
+ </div>
78
+
79
+ <div>
80
+ <input
81
+ type="text"
82
+ placeholder="Last Name"
83
+ value={formData.lastName}
84
+ onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
85
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
86
+ />
87
+ {errors.lastName && <p className="text-red-500 text-xs mt-1">{errors.lastName}</p>}
88
+ </div>
89
+
90
+ <div>
91
+ <input
92
+ type="email"
93
+ placeholder="Email Address"
94
+ value={formData.email}
95
+ onChange={(e) => setFormData({ ...formData, email: e.target.value })}
96
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
97
+ />
98
+ {errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
99
+ </div>
100
+
101
+ <div>
102
+ <input
103
+ type="tel"
104
+ placeholder="Phone Number"
105
+ value={formData.phone}
106
+ onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
107
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
108
+ />
109
+ {errors.phone && <p className="text-red-500 text-xs mt-1">{errors.phone}</p>}
110
+ </div>
111
+
112
+ <div className="pt-2 border-t border-zinc-200 dark:border-zinc-700">
113
+ <p className="text-sm text-zinc-600 dark:text-zinc-400 mb-2">Preferred Call Time (optional)</p>
114
+ <div className="grid grid-cols-2 gap-2">
115
+ <div className="relative">
116
+ <Calendar className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" />
117
+ <input
118
+ type="date"
119
+ value={formData.preferredDate}
120
+ onChange={(e) => setFormData({ ...formData, preferredDate: e.target.value })}
121
+ className="w-full pl-12 pr-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white text-sm focus:border-[#8B5CF6] focus:outline-none transition-colors"
122
+ />
123
+ </div>
124
+ <div className="relative">
125
+ <Clock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" />
126
+ <select
127
+ value={formData.preferredTime}
128
+ onChange={(e) => setFormData({ ...formData, preferredTime: e.target.value })}
129
+ className="w-full pl-10 pr-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white text-sm focus:border-[#8B5CF6] focus:outline-none transition-colors appearance-none"
130
+ >
131
+ <option value="">Select time</option>
132
+ {timeSlots.map((slot) => (
133
+ <option key={slot} value={slot}>
134
+ {slot}
135
+ </option>
136
+ ))}
137
+ </select>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <div className="flex gap-2 pt-2">
143
+ <button
144
+ type="submit"
145
+ className="flex-1 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white py-2.5 rounded-lg transition-colors"
146
+ >
147
+ Request Call
148
+ </button>
149
+ <button
150
+ type="button"
151
+ onClick={onCancel}
152
+ className="px-4 text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white transition-colors"
153
+ >
154
+ Cancel
155
+ </button>
156
+ </div>
157
+ </form>
158
+ </motion.div>
159
+ );
160
+ }
161
+
@@ -0,0 +1,277 @@
1
+ 'use client';
2
+
3
+ import { motion } from 'motion/react';
4
+
5
+ interface ModePromptTreeProps {
6
+ mode: 'earnings' | 'profit';
7
+ step: string;
8
+ hasPersonalizedData: boolean;
9
+ onResponse: (response: string, data?: any) => void;
10
+ }
11
+
12
+ export function ModePromptTree({ mode, step, hasPersonalizedData, onResponse }: ModePromptTreeProps) {
13
+ if (mode === 'earnings') {
14
+ if (step === 'volume') {
15
+ if (hasPersonalizedData) {
16
+ return (
17
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
18
+ <div className="grid grid-cols-1 gap-2">
19
+ <button
20
+ onClick={() => onResponse('yes', { volumeMultiplier: 1.0 })}
21
+ className="px-4 py-3 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white rounded-lg text-sm transition-colors text-left"
22
+ >
23
+ Yes, that's about right
24
+ </button>
25
+ <button
26
+ onClick={() => onResponse('higher', { volumeMultiplier: 1.1 })}
27
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
28
+ >
29
+ It's a bit higher
30
+ </button>
31
+ <button
32
+ onClick={() => onResponse('lower', { volumeMultiplier: 0.9 })}
33
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
34
+ >
35
+ It's a bit lower
36
+ </button>
37
+ </div>
38
+ </motion.div>
39
+ );
40
+ }
41
+
42
+ return (
43
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
44
+ <div className="grid grid-cols-1 gap-2">
45
+ <button
46
+ onClick={() => onResponse('Under $500k', { volume: 400000 })}
47
+ className="px-4 py-3 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white rounded-lg text-sm transition-colors text-left"
48
+ >
49
+ Under $500k
50
+ </button>
51
+ <button
52
+ onClick={() => onResponse('$500k – $1M', { volume: 750000 })}
53
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
54
+ >
55
+ $500k – $1M
56
+ </button>
57
+ <button
58
+ onClick={() => onResponse('$1M – $2M', { volume: 1500000 })}
59
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
60
+ >
61
+ $1M – $2M
62
+ </button>
63
+ <button
64
+ onClick={() => onResponse('$2M+', { volume: 2500000 })}
65
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
66
+ >
67
+ $2M+
68
+ </button>
69
+ </div>
70
+ </motion.div>
71
+ );
72
+ }
73
+
74
+ if (step === 'comp') {
75
+ return (
76
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
77
+ <div className="grid grid-cols-1 gap-2">
78
+ <button
79
+ onClick={() => onResponse('Under 100 bps', { comp: 0.95 })}
80
+ className="px-4 py-3 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white rounded-lg text-sm transition-colors text-left"
81
+ >
82
+ Under 100 bps
83
+ </button>
84
+ <button
85
+ onClick={() => onResponse('Around 125 bps', { comp: 1.25 })}
86
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
87
+ >
88
+ Around 125 bps
89
+ </button>
90
+ <button
91
+ onClick={() => onResponse('150+ bps', { comp: 1.5 })}
92
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
93
+ >
94
+ 150+ bps
95
+ </button>
96
+ <button
97
+ onClick={() => onResponse("I'm not sure", { comp: 1.15 })}
98
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
99
+ >
100
+ I'm not sure
101
+ </button>
102
+ </div>
103
+ </motion.div>
104
+ );
105
+ }
106
+
107
+ if (step === 'complete') {
108
+ return (
109
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
110
+ <div className="grid grid-cols-1 gap-2">
111
+ <button
112
+ onClick={() => onResponse('Talk through this with AFN')}
113
+ className="px-4 py-3 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white rounded-lg text-sm transition-colors text-left"
114
+ >
115
+ Talk through this with AFN
116
+ </button>
117
+ <button
118
+ onClick={() => onResponse('Adjust the numbers')}
119
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
120
+ >
121
+ Adjust the numbers
122
+ </button>
123
+ <button
124
+ onClick={() => onResponse('Keep exploring')}
125
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
126
+ >
127
+ Keep exploring
128
+ </button>
129
+ </div>
130
+ </motion.div>
131
+ );
132
+ }
133
+ }
134
+
135
+ if (mode === 'profit') {
136
+ if (step === 'volume') {
137
+ if (hasPersonalizedData) {
138
+ return (
139
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
140
+ <div className="grid grid-cols-1 gap-2">
141
+ <button
142
+ onClick={() => onResponse('Yes', { volumeMultiplier: 1.0 })}
143
+ className="px-4 py-3 bg-[#4399D1] hover:bg-[#2B7AB8] text-white rounded-lg text-sm transition-colors text-left"
144
+ >
145
+ Yes
146
+ </button>
147
+ <button
148
+ onClick={() => onResponse('Volume is higher', { volumeMultiplier: 1.1 })}
149
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
150
+ >
151
+ Volume is higher
152
+ </button>
153
+ <button
154
+ onClick={() => onResponse('Volume is lower', { volumeMultiplier: 0.9 })}
155
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
156
+ >
157
+ Volume is lower
158
+ </button>
159
+ </div>
160
+ </motion.div>
161
+ );
162
+ }
163
+
164
+ return (
165
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
166
+ <div className="grid grid-cols-1 gap-2">
167
+ <button
168
+ onClick={() => onResponse('Under $100M', { volume: 75000000 })}
169
+ className="px-4 py-3 bg-[#4399D1] hover:bg-[#2B7AB8] text-white rounded-lg text-sm transition-colors text-left"
170
+ >
171
+ Under $100M
172
+ </button>
173
+ <button
174
+ onClick={() => onResponse('$100M – $250M', { volume: 175000000 })}
175
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
176
+ >
177
+ $100M – $250M
178
+ </button>
179
+ <button
180
+ onClick={() => onResponse('$250M – $500M', { volume: 375000000 })}
181
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
182
+ >
183
+ $250M – $500M
184
+ </button>
185
+ <button
186
+ onClick={() => onResponse('$500M+', { volume: 650000000 })}
187
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
188
+ >
189
+ $500M+
190
+ </button>
191
+ </div>
192
+ </motion.div>
193
+ );
194
+ }
195
+
196
+ if (step === 'economics') {
197
+ return (
198
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
199
+ <div className="grid grid-cols-1 gap-2">
200
+ <button
201
+ onClick={() => onResponse('Sounds right', { margin: 3.0, grossProfit: 1.5, opEx: 0.75 })}
202
+ className="px-4 py-3 bg-[#4399D1] hover:bg-[#2B7AB8] text-white rounded-lg text-sm transition-colors text-left"
203
+ >
204
+ Sounds right
205
+ </button>
206
+ <button
207
+ onClick={() =>
208
+ onResponse('Margin is higher', {
209
+ margin: 3.5,
210
+ grossProfit: 1.75,
211
+ opEx: 0.75,
212
+ })
213
+ }
214
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
215
+ >
216
+ Margin is higher
217
+ </button>
218
+ <button
219
+ onClick={() =>
220
+ onResponse('Expenses are higher', {
221
+ margin: 3.0,
222
+ grossProfit: 1.5,
223
+ opEx: 0.95,
224
+ })
225
+ }
226
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
227
+ >
228
+ Expenses are higher
229
+ </button>
230
+ <button
231
+ onClick={() =>
232
+ onResponse('Let me adjust', {
233
+ margin: 3.0,
234
+ grossProfit: 1.5,
235
+ opEx: 0.75,
236
+ })
237
+ }
238
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
239
+ >
240
+ Let me adjust
241
+ </button>
242
+ </div>
243
+ </motion.div>
244
+ );
245
+ }
246
+
247
+ if (step === 'complete') {
248
+ return (
249
+ <motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="space-y-3">
250
+ <div className="grid grid-cols-1 gap-2">
251
+ <button
252
+ onClick={() => onResponse('Sanity-check with AFN')}
253
+ className="px-4 py-3 bg-[#4399D1] hover:bg-[#2B7AB8] text-white rounded-lg text-sm transition-colors text-left"
254
+ >
255
+ Sanity-check with AFN
256
+ </button>
257
+ <button
258
+ onClick={() => onResponse('Adjust assumptions')}
259
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
260
+ >
261
+ Adjust assumptions
262
+ </button>
263
+ <button
264
+ onClick={() => onResponse('Schedule leadership call')}
265
+ className="px-4 py-3 dark:bg-zinc-800 bg-zinc-100 dark:hover:bg-zinc-700 hover:bg-zinc-200 dark:text-white text-zinc-900 rounded-lg text-sm transition-colors text-left"
266
+ >
267
+ Schedule leadership call
268
+ </button>
269
+ </div>
270
+ </motion.div>
271
+ );
272
+ }
273
+ }
274
+
275
+ return null;
276
+ }
277
+
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ import React, { useState } from 'react';
4
+ import { motion } from 'motion/react';
5
+
6
+ interface PersonalizedOverviewFormProps {
7
+ onSubmit: (data: {
8
+ firstName: string;
9
+ lastName: string;
10
+ email: string;
11
+ phone?: string;
12
+ nmlsId?: string;
13
+ }) => void;
14
+ onCancel: () => void;
15
+ }
16
+
17
+ export function PersonalizedOverviewForm({ onSubmit, onCancel }: PersonalizedOverviewFormProps) {
18
+ const [formData, setFormData] = useState({
19
+ firstName: '',
20
+ lastName: '',
21
+ email: '',
22
+ phone: '',
23
+ nmlsId: '',
24
+ });
25
+ const [errors, setErrors] = useState<Record<string, string>>({});
26
+
27
+ const handleSubmit = (e: React.FormEvent) => {
28
+ e.preventDefault();
29
+ const newErrors: Record<string, string> = {};
30
+
31
+ if (!formData.firstName.trim()) {
32
+ newErrors.firstName = 'First name is required';
33
+ }
34
+ if (!formData.lastName.trim()) {
35
+ newErrors.lastName = 'Last name is required';
36
+ }
37
+ if (!formData.email.trim()) {
38
+ newErrors.email = 'Email is required';
39
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
40
+ newErrors.email = 'Please enter a valid email address';
41
+ }
42
+
43
+ if (Object.keys(newErrors).length > 0) {
44
+ setErrors(newErrors);
45
+ return;
46
+ }
47
+
48
+ onSubmit(formData);
49
+ };
50
+
51
+ return (
52
+ <motion.div
53
+ initial={{ opacity: 0, y: 10 }}
54
+ animate={{ opacity: 1, y: 0 }}
55
+ className="bg-gradient-to-br from-white to-zinc-50 dark:from-zinc-800 dark:to-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-2xl p-6"
56
+ >
57
+ <h3 className="text-lg text-zinc-900 dark:text-white mb-4">Request Personalized Overview</h3>
58
+
59
+ <form onSubmit={handleSubmit} className="space-y-3">
60
+ <div>
61
+ <input
62
+ type="text"
63
+ placeholder="First Name"
64
+ value={formData.firstName}
65
+ onChange={(e) => setFormData({ ...formData, firstName: e.target.value })}
66
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
67
+ />
68
+ {errors.firstName && <p className="text-red-500 text-xs mt-1">{errors.firstName}</p>}
69
+ </div>
70
+
71
+ <div>
72
+ <input
73
+ type="text"
74
+ placeholder="Last Name"
75
+ value={formData.lastName}
76
+ onChange={(e) => setFormData({ ...formData, lastName: e.target.value })}
77
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
78
+ />
79
+ {errors.lastName && <p className="text-red-500 text-xs mt-1">{errors.lastName}</p>}
80
+ </div>
81
+
82
+ <div>
83
+ <input
84
+ type="email"
85
+ placeholder="Email Address"
86
+ value={formData.email}
87
+ onChange={(e) => setFormData({ ...formData, email: e.target.value })}
88
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
89
+ />
90
+ {errors.email && <p className="text-red-500 text-xs mt-1">{errors.email}</p>}
91
+ </div>
92
+
93
+ <div>
94
+ <input
95
+ type="tel"
96
+ placeholder="Phone (optional)"
97
+ value={formData.phone}
98
+ onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
99
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
100
+ />
101
+ </div>
102
+
103
+ <div>
104
+ <input
105
+ type="text"
106
+ placeholder="NMLS ID (optional)"
107
+ value={formData.nmlsId}
108
+ onChange={(e) => setFormData({ ...formData, nmlsId: e.target.value })}
109
+ className="w-full px-4 py-2.5 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-lg text-zinc-900 dark:text-white placeholder-zinc-500 focus:border-[#8B5CF6] focus:outline-none transition-colors"
110
+ />
111
+ </div>
112
+
113
+ <div className="flex gap-2 pt-2">
114
+ <button
115
+ type="submit"
116
+ className="flex-1 bg-[#8B5CF6] hover:bg-[#7C3AED] text-white py-2.5 rounded-lg transition-colors"
117
+ >
118
+ Request Overview
119
+ </button>
120
+ <button
121
+ type="button"
122
+ onClick={onCancel}
123
+ className="px-4 text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-white transition-colors"
124
+ >
125
+ Cancel
126
+ </button>
127
+ </div>
128
+ </form>
129
+ </motion.div>
130
+ );
131
+ }
132
+