/* * 리드 전송 — 데모 요청 모달과 상담 신청 폼이 함께 쓴다. * * 목적지는 같은 오리진의 서버리스 함수(api/lead.ts)다. 랜딩은 ssr:false 정적 빌드지만 * Vercel 의 /api 디렉터리 함수는 별개로 배포되므로 서버 없이도 POST 를 받을 수 있다. * 같은 오리진이라 엔드포인트 환경변수도, CORS 도 필요 없다. * * 성공을 위조하지 않는다. 예전 상담 신청 폼은 1.2초 뒤 성공 화면만 띄우고 아무 데도 * 보내지 않았다 — 화면은 "접수되었습니다"인데 영업이 받을 리드는 없었다. * 여기서는 서버가 리드를 확보했을 때만 ok 를 돌려준다. */ const ENDPOINT = "/api/lead" export type LeadSource = "demo-request" | "contact" export type Lead = { source: LeadSource name: string email: string company?: string phone?: string message?: string /** 봇 함정. 화면에서 감춘 필드라 값이 차 있으면 자동 제출이다. 서버가 조용히 버린다. */ website?: string } export type LeadResult = { ok: true } | { ok: false; reason: "invalid" | "network" | "rejected" } /** 느슨한 검사. 정규식으로 이메일을 엄밀히 검증하려는 시도는 늘 진짜 주소를 막는다. */ export function isEmailLike(value: string) { const v = value.trim() return v.length >= 5 && v.includes("@") && !v.startsWith("@") && !v.endsWith("@") && !/\s/.test(v) } export async function submitLead(lead: Lead): Promise { if (!lead.name.trim() || !isEmailLike(lead.email)) return { ok: false, reason: "invalid" } try { const res = await fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(lead), }) return res.ok ? { ok: true } : { ok: false, reason: "rejected" } } catch { // 로컬 dev(react-router dev)에는 /api 가 없어서 여기로 온다. 확인은 `vercel dev` 로. return { ok: false, reason: "network" } } }