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 | 2x 15x 2x 13x 13x 1x 12x 12x 2x 10x 4x 1x 3x 1x 2x 8x | // Free Consultation Booking — shared constants + validation.
//
// PHASE 1 (standalone): this module is the single source of truth for the
// booking form's server-side payload validation and the booking-reference
// generator. It is intentionally self-contained — it does NOT touch the
// `inquiries` table, the API worker, or WhatsApp. Those hooks are wired in
// later phases (see the endpoint TODO markers).
//
// Simplified quick-book form: we ask only for a phone number and an optional
// free-text box of details to discuss on the call. The richer project-intent
// fields (type/city/budget/help/time) from the PRD are deferred to a later
// iteration.
//
// Feature spec: docs/product/prd/functional/free-consultation-booking (v2).
import { validateIndianPhoneNumber } from "./validation";
// Max length of the free-text "details to discuss" box.
export const DETAILS_MAX_LENGTH = 1000;
export interface ConsultationBookingPayload {
phone: string;
details?: string;
}
export type ConsultationValidationResult =
| { valid: true; data: ConsultationBookingPayload }
| { valid: false; error: string };
/**
* Server-authoritative validation for a consultation booking submission.
* Only the phone number is required; `details` is optional free text.
* Returns the normalized payload on success, or a single user-facing error
* message on the first failure. Reuses the marketplace phone validator so the
* rules match the existing inquiry flow exactly.
*/
export function validateConsultationBooking(
body: unknown,
): ConsultationValidationResult {
if (typeof body !== "object" || body === null) {
return { valid: false, error: "Invalid request." };
}
const b = body as Record<string, unknown>;
// Phone (Indian mobile) — required.
if (typeof b.phone !== "string" || b.phone.trim().length === 0) {
return { valid: false, error: "Please enter your phone number." };
}
const phoneError = validateIndianPhoneNumber(b.phone);
if (phoneError) {
return { valid: false, error: phoneError };
}
// Details — optional free text.
let details: string | undefined;
if (b.details !== undefined && b.details !== null && b.details !== "") {
if (typeof b.details !== "string") {
return { valid: false, error: "Details must be text." };
}
if (b.details.length > DETAILS_MAX_LENGTH) {
return {
valid: false,
error: `Details must be ${DETAILS_MAX_LENGTH} characters or fewer.`,
};
}
details = b.details.trim();
}
return {
valid: true,
data: {
phone: b.phone.trim(),
details,
},
};
}
|