/** * "URL 입력으로 시작하기" 진단 신청 모달. * * - 병원 URL(필수) + 회신 연락처(필수) + 상호(선택) → Supabase discovery_leads insert * - RLS는 anon insert만 허용 (조회는 service_role 전용) * - 저장 실패 시 성공한 척하지 않고 mailto 폴백을 보여준다 (정직성 규약) */ import { useEffect, useState } from 'react'; import { AnimatePresence, motion } from 'motion/react'; import { ArrowRight } from 'lucide-react'; import { supabase } from '../../lib/supabase'; import { DISCOVERY_CONTACT_EMAIL } from './discoveryContact'; interface DiscoveryLeadModalProps { open: boolean; onClose: () => void; } type SubmitState = 'idle' | 'submitting' | 'done' | 'error'; /** 스킴이 없으면 https://를 붙이고, 호스트에 점이 있어야 유효로 본다 */ const normalizeUrl = (raw: string): string | null => { const trimmed = raw.trim(); if (!trimmed) return null; const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`; try { const u = new URL(withScheme); if (!u.hostname.includes('.')) return null; return u.toString(); } catch { return null; } }; export default function DiscoveryLeadModal({ open, onClose }: DiscoveryLeadModalProps) { const [url, setUrl] = useState(''); const [clinicName, setClinicName] = useState(''); const [contact, setContact] = useState(''); const [state, setState] = useState('idle'); const [fieldError, setFieldError] = useState(null); useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [open, onClose]); // @types/react 미설치 프로젝트 방침이라 React.FormEvent 대신 구조적 타입 사용 const handleSubmit = async (e: { preventDefault: () => void }) => { e.preventDefault(); const normalized = normalizeUrl(url); if (!normalized) { setFieldError('URL 형식을 확인해 주세요. 예: viewclinic.com'); return; } if (!contact.trim()) { setFieldError('진단 결과를 받을 이메일 또는 전화번호를 입력해 주세요.'); return; } setFieldError(null); setState('submitting'); // discovery_leads 는 anon 조회 정책이 없어 insert 반환값을 읽지 않는다. const { error } = await supabase.from('discovery_leads').insert({ url: normalized, clinic_name: clinicName.trim() || null, contact: contact.trim(), source: 'discovery_landing', }); // 서포터즈 자동 빌드 큐(supporter_builds, status=queued). 워커(workers/supporters-build/poll.mjs)가 집어 간다. // clinic_id 는 홈페이지 호스트에서 만든다 (www.k-wonjin.co.kr → k-wonjin). 큐 삽입이 실패해도 신청 자체는 접수된 것으로 본다. if (!error) { const host = (() => { try { return new URL(normalized).host; } catch { return normalized; } })(); const clinicId = host.replace(/^www\./, '').split('.')[0].toLowerCase().replace(/[^a-z0-9-]/g, '') || 'clinic'; await supabase.from('supporter_builds').insert({ clinic_id: clinicId, url: normalized, status: 'queued', clinic_name: clinicName.trim() || null }); } setState(error ? 'error' : 'done'); }; const fallbackMailto = () => { const body = `병원/업체 URL: ${url}\n상호: ${clinicName || '-'}\n회신 연락처: ${contact}`; return `mailto:${DISCOVERY_CONTACT_EMAIL}?subject=${encodeURIComponent( '[INFINITH AI Discovery] 무료 진단 신청', )}&body=${encodeURIComponent(body)}`; }; return ( {open && ( e.stopPropagation()} role="dialog" aria-modal="true" aria-label="무료 AEO/GEO 진단 신청" > {/* 닫기 (커스텀 filled SVG — 라인 아이콘 금지 규칙) */} {state === 'done' ? (

신청이 접수되었습니다

입력하신 URL을 실측 진단한 뒤, 남겨주신 연락처로 리포트를 안내드립니다.
문의: {DISCOVERY_CONTACT_EMAIL}

) : (

URL 입력으로 시작하기

병원 홈페이지 URL만 입력하면 AI 검색 인용 가능성을 실측 진단합니다. 진단은 무료입니다.

setUrl(e.target.value)} placeholder="viewclinic.com" autoFocus className="w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-[linear-gradient(to_right,#fff3eb,#e4cfff,#f5f9ff)] text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40" /> setClinicName(e.target.value)} placeholder="뷰성형외과" className="on-light w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-white text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40" /> setContact(e.target.value)} placeholder="marketing@clinic.com" className="on-light w-full h-12 px-4 mb-4 rounded-xl border border-slate-200 bg-white text-[15px] text-primary-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/40" /> {fieldError && (

{fieldError}

)} {state === 'error' && (

저장에 실패했습니다. 아래 이메일로 직접 신청해 주세요.
{DISCOVERY_CONTACT_EMAIL}로 신청 메일 보내기

)}

입력 정보는 진단 회신 용도로만 사용합니다.

)}
)}
); }