Skip to content

Commit 7f39029

Browse files
committed
fix (onboarding): complete-profile fixes
1 parent bea400c commit 7f39029

File tree

2 files changed

+159
-145
lines changed

2 files changed

+159
-145
lines changed

src/client/app/complete-profile/page.js

Lines changed: 153 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const CompleteProfilePage = () => {
4343
const debounceTimeoutRef = useRef(null)
4444

4545
const verifyWhatsappNumber = async (number) => {
46-
if (!/^\+[1-9]\d{1,14}$/.test(number.trim())) {
46+
if (!/^\+[1-9]\d{6,14}$/.test(number.trim())) {
4747
setWhatsappStatus("invalid")
4848
setWhatsappError(
4949
"Please use E.164 format with country code (e.g., +14155552671)."
@@ -53,11 +53,14 @@ const CompleteProfilePage = () => {
5353
setWhatsappStatus("checking")
5454
setWhatsappError("")
5555
try {
56-
const response = await fetch("/api/settings/whatsapp-notifications/verify", {
57-
method: "POST",
58-
headers: { "Content-Type": "application/json" },
59-
body: JSON.stringify({ phone_number: number })
60-
})
56+
const response = await fetch(
57+
"/api/settings/whatsapp-notifications/verify",
58+
{
59+
method: "POST",
60+
headers: { "Content-Type": "application/json" },
61+
body: JSON.stringify({ phone_number: number })
62+
}
63+
)
6164
const result = await response.json()
6265
if (!response.ok) {
6366
throw new Error(result.detail || "Verification request failed.")
@@ -67,7 +70,9 @@ const CompleteProfilePage = () => {
6770
setWhatsappError("")
6871
} else {
6972
setWhatsappStatus("invalid")
70-
setWhatsappError("This number does not appear to be on WhatsApp.")
73+
setWhatsappError(
74+
"This number does not appear to be on WhatsApp."
75+
)
7176
}
7277
} catch (error) {
7378
setWhatsappStatus("invalid")
@@ -130,151 +135,157 @@ const CompleteProfilePage = () => {
130135
const currentQuestion = questions[currentQuestionIndex]
131136

132137
return (
133-
<div className="relative flex flex-col items-center justify-center min-h-screen w-full p-4 sm:p-8 text-white overflow-hidden">
138+
<div className="relative h-screen w-full overflow-hidden text-white">
134139
<div className="absolute inset-0 z-[-1]">
135140
<InteractiveNetworkBackground />
136141
</div>
137-
<motion.div
138-
key="complete-profile"
139-
initial={{ opacity: 0, y: 20 }}
140-
animate={{ opacity: 1, y: 0 }}
141-
className="relative z-10 w-full max-w-2xl text-center"
142-
>
143-
<IconSparkles
144-
size={60}
145-
className="mx-auto text-brand-orange mb-4"
146-
/>
147-
<h1 className="text-3xl sm:text-4xl font-bold mb-2">
148-
Just a quick update...
149-
</h1>
150-
<p className="text-neutral-300 mb-8">
151-
We've added some new features and need a couple more details
152-
to personalize your experience.
153-
</p>
142+
{/* Scrollable container for the content */}
143+
<div className="relative z-10 flex h-full w-full flex-col items-center justify-start overflow-y-auto px-4 py-16 sm:p-8 md:justify-center">
144+
<motion.div
145+
key="complete-profile"
146+
initial={{ opacity: 0, y: 20 }}
147+
animate={{ opacity: 1, y: 0 }}
148+
className="w-full max-w-2xl text-center"
149+
>
150+
<IconSparkles
151+
size={60}
152+
className="mx-auto text-brand-orange mb-4"
153+
/>
154+
<h1 className="text-3xl sm:text-4xl font-bold mb-2">
155+
Just a quick update...
156+
</h1>
157+
<p className="text-neutral-300 mb-8">
158+
We've added some new features and need a couple more
159+
details to personalize your experience.
160+
</p>
154161

155-
<div className="bg-neutral-900/50 border border-neutral-700/50 rounded-2xl p-6 sm:p-8 text-left space-y-6">
156-
{currentQuestion.id === "needs-pa" ? (
157-
<div className="text-neutral-200 space-y-4">
158-
<p>
159-
Are you someone who finds themselves spending
160-
too much time on administrative work? For
161-
example:
162-
</p>
163-
<ul className="list-disc list-inside pl-4 space-y-2 text-neutral-300">
164-
<li>Juggling multiple priorities</li>
165-
<li>
166-
Managing a small team or leading projects
167-
</li>
168-
<li>
169-
Scheduling meetings and organizing calendars
170-
</li>
171-
<li>Responding to routine emails</li>
172-
</ul>
173-
<p className="font-semibold pt-2">
174-
Do you ever feel the need for a personal
175-
assistant (human or AI) to handle these
176-
repetitive tasks?
162+
<div className="bg-neutral-900/50 border border-neutral-700/50 rounded-2xl p-6 sm:p-8 text-left space-y-6">
163+
{currentQuestion.id === "needs-pa" ? (
164+
<div className="text-neutral-200 space-y-4">
165+
<p>
166+
Are you someone who finds themselves
167+
spending too much time on administrative
168+
work? For example:
169+
</p>
170+
<ul className="list-disc list-inside pl-4 space-y-2 text-neutral-300">
171+
<li>Juggling multiple priorities</li>
172+
<li>
173+
Managing a small team or leading
174+
projects
175+
</li>
176+
<li>
177+
Scheduling meetings and organizing
178+
calendars
179+
</li>
180+
<li>Responding to routine emails</li>
181+
</ul>
182+
<p className="font-semibold pt-2">
183+
Do you ever feel the need for a personal
184+
assistant (human or AI) to handle these
185+
repetitive tasks?
186+
</p>
187+
</div>
188+
) : (
189+
<p className="whitespace-pre-wrap text-neutral-200">
190+
{currentQuestion.question}
177191
</p>
178-
</div>
179-
) : (
180-
<p className="whitespace-pre-wrap text-neutral-200">
181-
{currentQuestion.question}
182-
</p>
183-
)}
184-
185-
{currentQuestion.type === "yes-no" && (
186-
<div className="flex gap-4 pt-2">
187-
<button
188-
onClick={() => {
189-
handleAnswer(currentQuestion.id, "yes")
190-
setTimeout(handleNext, 100)
191-
}}
192-
className="px-6 py-2 rounded-lg font-semibold bg-neutral-700 hover:bg-neutral-600"
193-
>
194-
Yes
195-
</button>
196-
<button
197-
onClick={() => {
198-
handleAnswer(currentQuestion.id, "no")
199-
setTimeout(handleNext, 100)
200-
}}
201-
className="px-6 py-2 rounded-lg font-semibold bg-neutral-700 hover:bg-neutral-600"
202-
>
203-
No
204-
</button>
205-
</div>
206-
)}
192+
)}
207193

208-
{currentQuestion.type === "text-input" && (
209-
<div className="relative pt-2">
210-
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
211-
{currentQuestion.icon}
194+
{currentQuestion.type === "yes-no" && (
195+
<div className="flex gap-4 pt-2">
196+
<button
197+
onClick={() => {
198+
handleAnswer(currentQuestion.id, "yes")
199+
setTimeout(handleNext, 100)
200+
}}
201+
className="px-6 py-2 rounded-lg font-semibold bg-neutral-700 hover:bg-neutral-600"
202+
>
203+
Yes
204+
</button>
205+
<button
206+
onClick={() => {
207+
handleAnswer(currentQuestion.id, "no")
208+
setTimeout(handleNext, 100)
209+
}}
210+
className="px-6 py-2 rounded-lg font-semibold bg-neutral-700 hover:bg-neutral-600"
211+
>
212+
No
213+
</button>
212214
</div>
213-
<input
214-
type="text"
215-
value={answers[currentQuestion.id] || ""}
216-
onChange={(e) =>
217-
handleAnswer(
218-
currentQuestion.id,
219-
e.target.value
220-
)
221-
}
222-
placeholder={currentQuestion.placeholder}
223-
className="w-full pl-10 pr-10 py-3 bg-neutral-800 border border-neutral-700 rounded-lg focus:ring-2 focus:ring-brand-orange"
224-
autoFocus
225-
/>
226-
{currentQuestion.id ===
227-
"whatsapp_notifications_number" && (
228-
<div className="absolute right-3 top-1/2 -translate-y-1/2">
229-
{whatsappStatus === "checking" && (
230-
<IconLoader
231-
size={18}
232-
className="animate-spin text-neutral-400"
233-
/>
234-
)}
235-
{whatsappStatus === "valid" && (
236-
<IconCheck
237-
size={18}
238-
className="text-green-500"
239-
/>
240-
)}
241-
{whatsappStatus === "invalid" && (
242-
<IconX
243-
size={18}
244-
className="text-red-500"
245-
/>
246-
)}
247-
</div>
248-
)}
249-
</div>
250-
)}
251-
{currentQuestion.id === "whatsapp_notifications_number" &&
252-
whatsappStatus === "invalid" &&
253-
whatsappError && (
254-
<p className="text-red-500 text-sm mt-2">
255-
{whatsappError}
256-
</p>
257215
)}
258-
</div>
259216

260-
<div className="mt-8">
261-
<button
262-
onClick={handleSubmit}
263-
disabled={
264-
isSubmitting ||
265-
currentQuestionIndex !== questions.length - 1 ||
266-
whatsappStatus !== "valid"
267-
}
268-
className="w-full max-w-xs py-3 px-6 rounded-lg bg-brand-orange text-brand-black font-semibold text-lg transition-all hover:bg-brand-orange/90 disabled:opacity-50 disabled:cursor-not-allowed"
269-
>
270-
{isSubmitting ? (
271-
<IconLoader className="animate-spin mx-auto" />
272-
) : (
273-
"Continue to App"
217+
{currentQuestion.type === "text-input" && (
218+
<div className="relative pt-2">
219+
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
220+
{currentQuestion.icon}
221+
</div>
222+
<input
223+
type="text"
224+
value={answers[currentQuestion.id] || ""}
225+
onChange={(e) =>
226+
handleAnswer(
227+
currentQuestion.id,
228+
e.target.value
229+
)
230+
}
231+
placeholder={currentQuestion.placeholder}
232+
className="w-full pl-10 pr-10 py-3 bg-neutral-800 border border-neutral-700 rounded-lg focus:ring-2 focus:ring-brand-orange"
233+
autoFocus
234+
/>
235+
{currentQuestion.id ===
236+
"whatsapp_notifications_number" && (
237+
<div className="absolute right-3 top-1/2 -translate-y-1/2">
238+
{whatsappStatus === "checking" && (
239+
<IconLoader
240+
size={18}
241+
className="animate-spin text-neutral-400"
242+
/>
243+
)}
244+
{whatsappStatus === "valid" && (
245+
<IconCheck
246+
size={18}
247+
className="text-green-500"
248+
/>
249+
)}
250+
{whatsappStatus === "invalid" && (
251+
<IconX
252+
size={18}
253+
className="text-red-500"
254+
/>
255+
)}
256+
</div>
257+
)}
258+
</div>
274259
)}
275-
</button>
276-
</div>
277-
</motion.div>
260+
{currentQuestion.id ===
261+
"whatsapp_notifications_number" &&
262+
whatsappStatus === "invalid" &&
263+
whatsappError && (
264+
<p className="text-red-500 text-sm mt-2">
265+
{whatsappError}
266+
</p>
267+
)}
268+
</div>
269+
270+
<div className="mt-8">
271+
<button
272+
onClick={handleSubmit}
273+
disabled={
274+
isSubmitting ||
275+
currentQuestionIndex !== questions.length - 1 ||
276+
whatsappStatus !== "valid"
277+
}
278+
className="w-full max-w-xs py-3 px-6 rounded-lg bg-brand-orange text-brand-black font-semibold text-lg transition-all hover:bg-brand-orange/90 disabled:opacity-50 disabled:cursor-not-allowed"
279+
>
280+
{isSubmitting ? (
281+
<IconLoader className="animate-spin mx-auto" />
282+
) : (
283+
"Continue to App"
284+
)}
285+
</button>
286+
</div>
287+
</motion.div>
288+
</div>
278289
</div>
279290
)
280291
}

src/server/main/settings/routes.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,21 @@ async def complete_profile(
227227
raise HTTPException(status_code=404, detail="User profile not found.")
228228

229229
existing_onboarding_data = user_profile.get("userData", {}).get("onboardingAnswers", {})
230+
if not isinstance(existing_onboarding_data, dict):
231+
existing_onboarding_data = {}
230232

231233
# 3. Consolidate old and new data
232234
consolidated_data = {
233235
**existing_onboarding_data,
234236
"needs-pa": request.needs_pa,
235-
"whatsapp_notifications_number": whatsapp_number
237+
"whatsapp_notifications_number": whatsapp_number,
236238
}
237239

238240
# 4. Update MongoDB with new answers and notification prefs
241+
# We must update the entire `onboardingAnswers` object because it's encrypted as a whole.
242+
# Using dot notation to update sub-fields will fail on encrypted string values.
239243
update_payload = {
240-
"userData.onboardingAnswers.needs-pa": request.needs_pa,
241-
"userData.onboardingAnswers.whatsapp_notifications_number": whatsapp_number,
244+
"userData.onboardingAnswers": consolidated_data,
242245
"userData.notificationPreferences.whatsapp": {
243246
"number": whatsapp_number,
244247
"chatId": chat_id,

0 commit comments

Comments
 (0)