Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 1x 1x 9x 9x 9x 9x 9x 8x 1x 7x 1x 6x 6x 6x 6x 6x 6x 2x 4x 4x 9x 3x 3x 2x 4x 4x 4x 4x 4x 4x | // Onboarding Completion Routes
import { Hono } from "hono";
import type { Dal } from "../../../dal";
import { BadRequestError, NotFoundError } from "../../../lib/errors";
import { handleError, success } from "../../../lib/response";
import { requireUser } from "../../../lib/utils";
import { requireProAccess } from "../../../middleware";
import type { Services } from "../../../services";
import { flattenProForOnboarding } from "./utils";
import { logger } from "../../../lib/logger";
type Env = {
Bindings: CloudflareBindings;
Variables: {
user: { id: string; name: string; email: string } | null;
session: unknown;
dal: Dal;
services: Services;
proId: string;
proRole: string;
};
};
const completionRouter = new Hono<Env>();
// Complete onboarding
completionRouter.post("/complete", requireProAccess, async (c) => {
try {
const dal = c.get("dal");
const user = requireUser(c.get("user"));
const proId = c.get("proId");
// Get current pro to check required fields
const pro = await dal.pros.findById(proId);
if (!pro) {
throw new NotFoundError("Pro not found");
}
// Idempotency: if already completed, return early without re-sending emails
if (pro.onboardingStatus === "completed") {
return success(c, {
completed: true,
pro: flattenProForOnboarding(pro as Record<string, unknown>),
message: "Onboarding already completed",
});
}
// Validate required fields are set
const errors: string[] = [];
if (!pro.businessName) errors.push("Business name is required");
if (!pro.slug) errors.push("URL slug is required");
if (!pro.cityId) errors.push("City is required");
if (!pro.businessTypeId) errors.push("Business type is required");
if (errors.length > 0) {
throw new BadRequestError(
`Cannot complete onboarding: ${errors.join(", ")}`,
);
}
// Sync whatsapp fields: ensure both columns have the number if
// either was set during onboarding (safeguard for step 3 data)
const whatsappSync: Record<string, unknown> = {};
const effectiveWhatsapp =
pro.whatsapp || pro.whatsappBusinessNumber || null;
if (effectiveWhatsapp) {
if (!pro.whatsapp) whatsappSync.whatsapp = effectiveWhatsapp;
if (!pro.whatsappBusinessNumber)
whatsappSync.whatsappBusinessNumber = effectiveWhatsapp;
}
// Mark onboarding as complete
const updatedPro = await dal.pros.update(proId, {
...whatsappSync,
onboardingStatus: "completed",
onboardingStep: 7,
onboardingCompletedAt: new Date(),
userUpdated: user.id,
dateUpdated: new Date(),
});
c.executionCtx.waitUntil(
(async () => {
try {
const { CommunicationGateway } = await import(
"../../../lib/communication/gateway"
);
const { OnboardingCompleteHandler } = await import(
"../../../lib/communication/handlers/onboarding-complete.handler"
);
const gateway = new CommunicationGateway(dal, c.env);
const handler = new OnboardingCompleteHandler(gateway);
await handler.handle({
email: user.email,
userName: user.name,
});
} catch (err) {
logger.error("[ONBOARDING] Failed to send welcome email:", err);
}
})(),
);
return success(c, {
completed: true,
pro: flattenProForOnboarding(updatedPro as Record<string, unknown>),
});
} catch (err) {
return handleError(c, err);
}
});
export default completionRouter;
|