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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | 41x 41x 238x 30x 41x 41x 92x 2x 1x 1x 39x | import { API_BASE_URL, request } from "../base";
// The lead store (#1085), as Admin → Web leads sees it. Field names match the
// API row (apps/api/src/db/schema/landing-leads.ts).
export type LeadStatus =
| "new"
| "contacted"
| "qualified"
| "matched"
| "closed"
| "junk";
export type LeadEntryType =
| "whatsapp"
| "form"
| "calculator"
| "partner_referral";
export type LandingLead = {
id: number;
name: string;
phone: string | null;
email: string | null;
city: string;
locality: string | null;
notes: string | null;
source: string;
pagePath: string | null;
entryType: LeadEntryType | null;
offer: string | null;
utmSource: string | null;
utmMedium: string | null;
utmCampaign: string | null;
utmContent: string | null;
utmTerm: string | null;
gclid: string | null;
fbclid: string | null;
priceTestArm: string | null;
consentState: string | null;
consentVersion: string | null;
consentAt: string | null;
status: LeadStatus;
stage: string;
qualified: "yes" | "no" | "call_to_qualify" | null;
bhk: string | null;
possessionMonth: string | null;
outcome: string | null;
adminNotes: string | null;
dateCreated: string;
lastUpdated: string | null;
};
export type LeadTouch = {
id: number;
leadId: number;
at: string;
entryType: LeadEntryType | null;
channel: string | null;
pagePath: string | null;
utmSource: string | null;
utmMedium: string | null;
utmCampaign: string | null;
ref: string | null;
notes: string | null;
/** The consent that came with this contact (the DPDP record). */
consentState: string | null;
consentVersion: string | null;
};
export type LeadFilters = {
page?: number;
limit?: number;
status?: LeadStatus;
entryType?: LeadEntryType;
utmSource?: string;
/** IST calendar days, `YYYY-MM-DD`. */
from?: string;
to?: string;
q?: string;
qualified?: "yes" | "no" | "call_to_qualify";
/** Hide closed and junk. */
open?: boolean;
};
function leadQuery(filters: LeadFilters = {}): string {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) {
if (value !== undefined && value !== "" && value !== false) {
params.set(key, String(value));
}
}
const qs = params.toString();
return qs ? `?${qs}` : "";
}
export const adminLandingLeadsApi = {
listLandingLeads(filters?: LeadFilters) {
return request<LandingLead[]>(
`/api/admin/landing-leads${leadQuery(filters)}`,
);
},
getLandingLead(id: number) {
return request<LandingLead & { touches: LeadTouch[] }>(
`/api/admin/landing-leads/${id}`,
);
},
updateLandingLead(
id: number,
patch: { status?: LeadStatus; adminNotes?: string },
) {
return request<LandingLead>(`/api/admin/landing-leads/${id}`, {
method: "PATCH",
body: patch,
});
},
/**
* A plain link target, not a fetch: `request()` rejects non-JSON, and a
* top-level GET carries the same-site session cookie on its own.
*/
landingLeadsExportUrl(filters?: Omit<LeadFilters, "page" | "limit">) {
return `${API_BASE_URL}/api/admin/landing-leads/export.csv${leadQuery(filters)}`;
},
};
|