연락처 통일 - 사이트 안에 이메일이 contact@o2o.kr · o2oteam@o2o.kr 두 갈래로 갈라져 있던 것을 sales@o2o.kr 하나로 통일 (lib/contact.ts 단일 출처) - 전화번호 010.2755.6463 을 처음으로 명시. discoveryContact.ts 는 기존 import 경로를 깨지 않도록 재내보내기만 한다 푸터·문의 폼 - 푸터에 전화·이메일과 문의하기 버튼 추가 (사업자명·주소는 확인된 값이 없어 넣지 않는다) - ContactModal 신규: 성함·회사·전화·이메일·문의내용. 화면 구조는 기존 DiscoveryLeadModal 을 그대로 따라 모달이 두 종류로 보이지 않게 한다 - Navbar 문의하기를 mailto 에서 모달로 교체. 메일 앱이 없는 사용자가 mailto 에서 이탈하던 것을 막는다 수신 경로 - submitLead: Apps Script 웹앱으로 POST. 이 사이트는 서버 없는 SPA 라 중간 서버를 두지 않았다. Apps Script 는 CORS 프리플라이트에 응답하지 않아 Content-Type 을 text/plain 으로 보내 simple request 로 맞춘다 - scripts/apps-script/lead-intake.gs: 구글시트 행 추가와 알림 메일을 한 실행에서 처리. 시트 트리거는 스크립트가 쓴 변경에 늦거나 누락되는 경우가 있어 doPost 안에서 바로 보낸다 - 엔드포인트 미설정·전송 실패 시 접수된 것처럼 보이지 않게 하고 메일 폴백을 띄운다 (기존 DiscoveryLeadModal 과 같은 규칙) 토큰 실값은 저장소에 넣지 않는다. 배포본 Apps Script 와 Vercel 환경변수(VITE_LEAD_ENDPOINT · VITE_LEAD_TOKEN)에만 둔다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
3.9 KiB
TypeScript
85 lines
3.9 KiB
TypeScript
/**
|
|
* Navbar — 전역 상단 네비게이션.
|
|
*
|
|
* PART III 피봇:
|
|
* - "Free Report" CTA 완전 제거 (계약 기반 영업 모델로 전환).
|
|
* - Login 버튼 복원 — 계약 완료된 병원에 별도 발급된 자격 정보로 로그인 (스캐폴딩 단계).
|
|
* - 주 CTA는 `문의하기` — 2026-10-01 부터 mailto 대신 입력 폼(ContactModal)을 연다.
|
|
* 메일 앱이 없는 사용자가 mailto 에서 이탈하던 것을 막는다. 제출은 구글시트로 쌓인다.
|
|
*
|
|
* 라우팅 구성:
|
|
* - 로고 "INFINITH" → `/` (홈)
|
|
* - Product / Use Cases → `/#solution`, `/#use-cases` 앵커
|
|
* - Pricing → `/pricing?from=header` (유입 추적용 쿼리)
|
|
* - Login → `/login` (Secondary pill — 계약 병원 전용)
|
|
* - 문의하기 → ContactModal (Primary pill — 신규 리드)
|
|
*
|
|
* ⚠️ 앵커 링크를 `/#solution` 형태로 쓰는 이유:
|
|
* 단순 `#solution`은 현재 페이지 내부 해시로 해석됩니다.
|
|
* 예컨대 `/pricing`에서 `#solution`을 누르면 /pricing 페이지 내의 #solution을 찾다가 실패합니다.
|
|
* `/#solution`은 React Router가 홈으로 이동시킨 뒤 브라우저가 해시 스크롤을 처리합니다.
|
|
*/
|
|
import { useState } from 'react';
|
|
import { Link, useLocation } from 'react-router';
|
|
import { ArrowRight } from 'lucide-react';
|
|
import ContactModal from './ContactModal';
|
|
import ClinicNav, { clinicIdFromPath } from './ClinicNav';
|
|
|
|
export default function Navbar() {
|
|
const { pathname } = useLocation();
|
|
// 훅은 조기 return 위에 둔다. 아래 ClinicNav 분기가 먼저 반환되면 훅 호출 수가 달라진다.
|
|
const [contactOpen, setContactOpen] = useState(false);
|
|
// 병원 작업 공간(진단·빌드·사진 확인·확인 항목)에서는 절차 메뉴로 바꾼다.
|
|
const clinicId = clinicIdFromPath(pathname);
|
|
if (clinicId) return <ClinicNav clinicId={clinicId} />;
|
|
|
|
return (
|
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/95 border-b border-slate-100 backdrop-blur-md">
|
|
<div className="max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
|
|
{/* 로고 */}
|
|
<Link to="/" className="flex items-center gap-2">
|
|
<span className="font-serif text-3xl font-black tracking-[0.05em] bg-gradient-to-r from-[#4F1DA1] to-[#021341] bg-clip-text text-transparent">
|
|
INFINITH
|
|
</span>
|
|
</Link>
|
|
|
|
{/* 가운데 메뉴 */}
|
|
<div className="hidden md:flex items-center gap-8 text-sm font-medium text-slate-600">
|
|
<a href="/#solution" className="hover:text-primary-900 transition-colors">
|
|
Product
|
|
</a>
|
|
<Link
|
|
to="/pricing?from=header"
|
|
className="hover:text-primary-900 transition-colors"
|
|
>
|
|
Pricing
|
|
</Link>
|
|
<a href="/#use-cases" className="hover:text-primary-900 transition-colors">
|
|
Use Cases
|
|
</a>
|
|
</div>
|
|
|
|
{/* 우측 CTA — Login(Secondary) + 문의하기(Primary) */}
|
|
<div className="flex items-center gap-3">
|
|
<Link
|
|
to="/login"
|
|
className="inline-flex items-center gap-2 px-5 py-2.5 text-sm font-semibold text-[#021341] bg-white border border-slate-200 rounded-full hover:bg-slate-50 hover:border-slate-300 transition-colors"
|
|
>
|
|
Login
|
|
</Link>
|
|
<button
|
|
type="button"
|
|
onClick={() => setContactOpen(true)}
|
|
className="inline-flex items-center gap-2 px-6 py-3 text-sm font-semibold text-white rounded-full transition-all shadow-sm hover:shadow-md bg-gradient-to-r from-[#4F1DA1] to-[#021341] hover:opacity-90"
|
|
>
|
|
문의하기
|
|
<ArrowRight className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<ContactModal open={contactOpen} onClose={() => setContactOpen(false)} source="navbar" />
|
|
</nav>
|
|
);
|
|
}
|