/** * 문의 폼 전송. * * 받는 쪽은 Google Apps Script 웹앱이다(scripts/apps-script/lead-intake.gs). * 그 스크립트 하나가 구글시트에 행을 추가하고 알림 메일까지 보낸다. * 이 사이트는 서버가 없는 SPA 라 중간 서버를 두지 않았다. * * Apps Script 웹앱은 CORS 프리플라이트에 응답하지 않는다. 그래서 단순 요청(simple request) * 조건을 지켜 Content-Type 을 text/plain 으로 보낸다. 스크립트가 본문을 JSON 으로 파싱한다. * 헤더를 application/json 으로 바꾸면 프리플라이트가 발생해 브라우저에서 차단된다. */ /** Vercel 환경변수 VITE_LEAD_ENDPOINT (Apps Script 배포 URL). 없으면 전송하지 않는다. */ const ENDPOINT = import.meta.env.VITE_LEAD_ENDPOINT as string | undefined; /** 선택. 스크립트 쪽 SHARED_TOKEN 과 같은 값을 넣으면 외부의 무작위 호출을 걸러낸다. */ const TOKEN = import.meta.env.VITE_LEAD_TOKEN as string | undefined; export interface LeadInput { name: string; company: string; phone: string; email: string; message: string; /** 어느 화면에서 열었는지 (footer · navbar) */ source: string; /** * 허니팟. 사람에게는 보이지 않는 칸이라 항상 빈 문자열이어야 한다. * 값이 차 있으면 스크립트가 봇으로 보고 저장하지 않는다. * 이름은 Apps Script 의 HONEYPOT_FIELD 와 같아야 한다. */ website: string; } export type LeadResult = { ok: true } | { ok: false; reason: 'unconfigured' | 'failed' }; export async function submitLead(input: LeadInput): Promise { if (!ENDPOINT) { // 엔드포인트가 없는데 성공했다고 말하지 않는다. 화면은 메일 폴백을 보여준다. console.warn('[submitLead] VITE_LEAD_ENDPOINT 가 설정되지 않았습니다.'); return { ok: false, reason: 'unconfigured' }; } try { const res = await fetch(ENDPOINT, { method: 'POST', // 프리플라이트를 피하기 위한 simple request. 위 주석 참고. headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify({ ...input, token: TOKEN ?? '', submittedAt: new Date().toISOString() }), }); if (!res.ok) return { ok: false, reason: 'failed' }; const data = await res.json().catch(() => null); // 스크립트는 { ok: true } 를 돌려준다. 형식이 다르면 실패로 본다. return data && data.ok ? { ok: true } : { ok: false, reason: 'failed' }; } catch (e) { console.error('[submitLead] 전송 실패', e); return { ok: false, reason: 'failed' }; } }