Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions apps/web/utils/actions/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "@/utils/actions/onboarding.validation";
import { actionClientUser } from "@/utils/actions/safe-action";
import prisma from "@/utils/prisma";
import { updateContactCompanySize } from "@inboxzero/loops";

export const completedOnboardingAction = actionClientUser
.metadata({ name: "completedOnboarding" })
Expand All @@ -22,7 +23,7 @@ export const saveOnboardingAnswersAction = actionClientUser
.action(
async ({
parsedInput: { surveyId, questions, answers },
ctx: { userId },
ctx: { userId, logger },
}) => {
// Helper function to extract survey answers from the response format
function extractSurveyAnswers(questions: any[], answers: any) {
Expand Down Expand Up @@ -105,10 +106,9 @@ export const saveOnboardingAnswersAction = actionClientUser
return result;
}

// Extract individual survey answers for easier querying
const extractedAnswers = extractSurveyAnswers(questions, answers);

await prisma.user.update({
const userPromise = prisma.user.update({
where: { id: userId },
data: {
onboardingAnswers: { surveyId, questions, answers },
Expand All @@ -120,6 +120,20 @@ export const saveOnboardingAnswersAction = actionClientUser
surveyImprovements: extractedAnswers.surveyImprovements,
},
});

await Promise.all([
userPromise,
async () => {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async function is being passed to Promise.all without being invoked, so the update logic never runs. Pass a promise to Promise.all by immediately invoking the async arrow or by creating a promise and including that instead.

Prompt for AI agents
Address the following comment on apps/web/utils/actions/onboarding.ts at line 126:

<comment>The async function is being passed to Promise.all without being invoked, so the update logic never runs. Pass a promise to Promise.all by immediately invoking the async arrow or by creating a promise and including that instead.</comment>

<file context>
@@ -120,6 +120,20 @@ export const saveOnboardingAnswersAction = actionClientUser
+
+      await Promise.all([
+        userPromise,
+        async () =&gt; {
+          if (extractedAnswers.surveyCompanySize) {
+            updateContactCompanySize(
</file context>
Fix with Cubic

if (extractedAnswers.surveyCompanySize) {
updateContactCompanySize(
userId,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateContactCompanySize expects an email address, but userId is passed here. Use the user’s email (e.g., from the updated user record) to ensure the correct contact is updated.

Prompt for AI agents
Address the following comment on apps/web/utils/actions/onboarding.ts at line 129:

<comment>updateContactCompanySize expects an email address, but userId is passed here. Use the user’s email (e.g., from the updated user record) to ensure the correct contact is updated.</comment>

<file context>
@@ -120,6 +120,20 @@ export const saveOnboardingAnswersAction = actionClientUser
+        async () =&gt; {
+          if (extractedAnswers.surveyCompanySize) {
+            updateContactCompanySize(
+              userId,
+              extractedAnswers.surveyCompanySize,
+            ).catch((error) =&gt; {
</file context>
Fix with Cubic

extractedAnswers.surveyCompanySize,
).catch((error) => {
logger.error("Error updating company size", { error });
});
}
},
]);
Comment on lines +124 to +136
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Company size update never runs (async fn not invoked) and wrong identifier used.

  • The async function is passed to Promise.all without invocation, so it never executes.
  • updateContactCompanySize expects an email, but userId is passed.
  • Also log when success === false, not only on thrown errors.

Apply these changes:

-      await Promise.all([
-        userPromise,
-        async () => {
-          if (extractedAnswers.surveyCompanySize) {
-            updateContactCompanySize(
-              userId,
-              extractedAnswers.surveyCompanySize,
-            ).catch((error) => {
-              logger.error("Error updating company size", { error });
-            });
-          }
-        },
-      ]);
+      await Promise.all([
+        userPromise,
+        (async () => {
+          const size = extractedAnswers.surveyCompanySize;
+          if (typeof size !== "number" || !Number.isFinite(size)) return;
+          const { email } = await userPromise; // from select below
+          if (!email) return;
+          try {
+            const resp = await updateContactCompanySize(email, size);
+            if (!resp.success) {
+              logger.error("Loops updateContactCompanySize returned success=false", { userId, email, companySize: size });
+            }
+          } catch (error) {
+            logger.error("Error updating company size", { error, userId, email, companySize: size });
+          }
+        })(),
+      ]);

Additionally, if any UI depends on these fields, consider revalidatePath here.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await Promise.all([
userPromise,
async () => {
if (extractedAnswers.surveyCompanySize) {
updateContactCompanySize(
userId,
extractedAnswers.surveyCompanySize,
).catch((error) => {
logger.error("Error updating company size", { error });
});
}
},
]);
await Promise.all([
userPromise,
(async () => {
const size = extractedAnswers.surveyCompanySize;
if (typeof size !== "number" || !Number.isFinite(size)) return;
const { email } = await userPromise; // from select below
if (!email) return;
try {
const resp = await updateContactCompanySize(email, size);
if (!resp.success) {
logger.error("Loops updateContactCompanySize returned success=false", { userId, email, companySize: size });
}
} catch (error) {
logger.error("Error updating company size", { error, userId, email, companySize: size });
}
})(),
]);
🤖 Prompt for AI Agents
In apps/web/utils/actions/onboarding.ts around lines 124 to 136, the inline
async function is being passed to Promise.all without invocation and thus never
runs, updateContactCompanySize is called with userId but expects an email, and
failures where the API returns success === false are not logged; fix by
replacing the array entry with an invoked async function (e.g., immediately
executed async arrow), call updateContactCompanySize with the user's email (not
userId), await its result and check the returned success flag and log an error
when success === false as well as catching thrown errors, and if any UI depends
on the updated fields call revalidatePath for the affected route after a
successful update.

},
);

Expand Down
21 changes: 21 additions & 0 deletions packages/loops/src/loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ export async function cancelledPremium(
return resp;
}

async function updateContactProperty(
email: string,
properties: Record<string, string | number | boolean>,
): Promise<{ success: boolean }> {
const loops = getLoopsClient();
if (!loops) return { success: false };

const resp = await loops.updateContact({
email,
properties,
});
return resp;
}

export async function updateContactCompanySize(
email: string,
companySize: number,
) {
return updateContactProperty(email, { companySize });
}

function getRandomInt(max: number) {
return Math.ceil(Math.random() * max);
}
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.9.42
v2.9.43
Loading