import {useState} from 'react'; import {ExternalLink, Link2, Loader2, MapPin, Search} from 'lucide-react'; import type {LinkChannel as LinkChannelCode} from '@/api'; import {LinkChannel} from '@o2o/shared'; import {Button} from '@/components/ui/button'; import {Input} from '@/components/ui/input'; /** * 채널 URL 직접 등록 — **수집의 1순위 경로다.** 폴백이 아니다. * * ★ 왜 자동 발견보다 이게 먼저인가 (실측 2026-08-27) * · Perplexity 는 네이버 플레이스를 **한 건도** 못 찾았다(도플로·버터브루·힐튼 가든 인 * 서울 강남 전부 야놀자만 물어옴). 도메인 필터를 넓혔더니 이번엔 네이버 도움말 페이지 * (pages.map.naver.com/useful-tips)를 이 가게 채널로 등록했다. 그래서 기본 경로에서 뺐다. * · 상호로 place id 를 자동 해석하는 경로도 4곳 중 3곳만 성공한다('롯데호텔 서울' 실패). * · 반면 **네이버 플레이스 URL 만 있으면 100% 된다** — 어댑터가 fact 5~7건 + 사진 10장을 * 안정적으로 가져온다(힐튼·스테이머뭄·롯데월드에서 검증). * 그리고 사장님은 자기 가게 주소를 이미 알고 있다. 추측하게 두는 것보다 물어보는 것이 * 빠르고 정확하고 공짜다. * * 그래서 이 컴포넌트는 "네이버 지도 주소"를 전제로 말한다 — 다른 채널도 받지만, * 사장님이 따라 할 동작 하나는 네이버 지도에서 주소를 복사하는 것이다. * * 붙여넣은 URL 은 그 자체로 사장님이 확인한 값이므로 등록과 동시에 확정한다. */ /** 호스트로 채널을 판정한다. 서버도 같은 규칙을 쓰지만, 화면이 먼저 라벨을 보여줘야 한다. */ function guessChannel(url: string): {channel: LinkChannelCode; label: string} | null { const u = url.toLowerCase(); if (u.includes('naver.com') || u.includes('naver.me')) { // 블로그·카페는 채널이 아니라 후기다 — 크롤링 대상이 아니므로 받지 않는다. if (u.includes('blog.naver') || u.includes('cafe.naver')) return null; return {channel: LinkChannel.NAVER_PLACE, label: '네이버 플레이스'}; } if (u.includes('yanolja.com')) return {channel: LinkChannel.YANOLJA, label: '야놀자'}; if (u.includes('goodchoice.kr') || u.includes('yeogi.com')) { return {channel: LinkChannel.GOODCHOICE, label: '여기어때'}; } if (u.includes('instagram.com')) return {channel: LinkChannel.INSTAGRAM, label: '인스타그램'}; return {channel: LinkChannel.OFFICIAL_SITE, label: '공식 홈페이지'}; } /** 주소를 어디서 복사해 오는지 — 사장님이 그대로 따라 할 수 있게 동작 단위로 쪼갠다. */ const HOW_TO = [ '네이버 지도(또는 네이버 검색)에서 내 가게를 찾습니다', '가게 이름 옆 [공유] 를 누릅니다', '[URL 복사] 를 눌러 주소를 복사합니다', '아래 칸에 붙여넣고 [이 주소로 가져오기] 를 누릅니다', ]; export function ChannelUrlInput({ onAdd, isBusy, searchQuery, }: { onAdd: (url: string, channel: LinkChannelCode, title: string) => Promise; isBusy?: boolean; /** 이 상호로 네이버 지도를 바로 연다 — 사장님이 새 탭에서 직접 검색어를 치지 않게. */ searchQuery?: string; }) { const [url, setUrl] = useState(''); const [error, setError] = useState(null); const guess = url.trim() ? guessChannel(url.trim()) : null; const isNaver = guess?.channel === LinkChannel.NAVER_PLACE; const submit = async () => { const value = url.trim(); if (!value) return; if (!/^https?:\/\//i.test(value)) { setError('https:// 로 시작하는 주소를 붙여넣어 주세요.'); return; } if (!guess) { setError('블로그·카페 글은 채널이 아니라 후기라 수집 대상이 아닙니다.'); return; } setError(null); await onAdd(value, guess.channel, guess.label); setUrl(''); }; return (

네이버 지도에서 내 가게 주소를 복사해 붙여넣어 주세요

가장 확실한 방법입니다. 주소 하나만 있으면 영업시간·전화·편의시설과 사진 까지 그대로 가져옵니다.

{/* ★ 여기에 바로가기가 없으면 사장님은 새 탭을 열고 상호를 다시 타이핑해야 한다. "찾아 보세요" 라고만 하고 갈 곳을 안 주면 그 자리에서 멈춘다. */} {searchQuery?.trim() && ( 네이버 지도에서 '{searchQuery.trim()}' 열기 )}
    {HOW_TO.map((step, index) => (
  1. {index + 1} {step}
  2. ))}
{ setUrl(e.target.value); setError(null); }} onKeyDown={(e) => { if (e.key === 'Enter') void submit(); }} placeholder="https://map.naver.com/p/entry/place/1273971279" disabled={isBusy} /> {/* ★ '추가' 가 아니다. 이 버튼은 등록에서 끝나지 않고 **그 주소에서 정보를 가져온다.** '추가' 라고 쓰면 사장님은 목록에 한 줄 늘기를 기다리다가 아무 변화가 없어 실패로 읽는다. */}

{error ?? (isNaver ? '네이버 플레이스 주소입니다 — 이 경로가 가장 많이, 가장 정확하게 가져옵니다.' : guess ? `${guess.label} 주소로 등록됩니다. 네이버 지도 주소가 있으면 그쪽이 더 많이 가져옵니다.` : 'naver.me 단축 주소도 됩니다. 야놀자·여기어때·공식 홈페이지 주소도 받습니다.')}

붙여넣은 주소는 사장님이 확인한 것으로 보고 바로 수집 대상이 됩니다. 수집된 값은 그래도 전부 확인 대기로 들어갑니다.

); }