/** * 채널 링크 — 목록 · 직접 등록 · 확정. * * ★ 수집 흐름(useCollectFlow)에서 떼어낸 이유: 이건 **단계 전이가 없는** 일이다. * 등록하고 확정하는 것뿐이고, 수집이 돌고 있든 아니든 똑같이 동작한다. * 한 훅에 섞여 있으면 "지금 어느 단계라 이 버튼이 되는가"를 매번 따져야 한다. * * ★ 확정된 링크만 크롤링 대상이 된다 — 이 훅이 수집 게이트의 앞단이다. */ import {useCallback, useState} from 'react'; import {LinkChannel, SourceType} from '@o2o/shared'; import type {LinkChannel as LinkChannelCode, LinkData} from '@/api'; import {confirmLink, createLink, getListLinksQueryKey, useListLinks} from '@/api'; import {notify, notifyApiError} from '@/lib/notify'; import {queryClient} from '@/lib/query-client'; export function useChannelLinks(placeId: string | null) { const [confirmingId, setConfirmingId] = useState(null); const [isAdding, setIsAdding] = useState(false); /** 서버가 네이버 플레이스를 찾지 못했는지 여부. 수집 결과가 알려주고, 직접 등록하면 내린다. */ const [naverPlaceMissing, setNaverPlaceMissing] = useState(false); const enabled = Boolean(placeId); const linksQuery = useListLinks(placeId ?? '', undefined, {query: {enabled}}); const links: LinkData[] = linksQuery.data?.links ?? []; const confirmedCount = linksQuery.data?.confirmed ?? 0; /** * 사장님이 직접 붙여넣은 채널 URL 을 등록하고 **동시에 확정**한다. * * ★ 발견된 URL 과 달리 확인 절차를 한 번 더 두지 않는다. 사장님이 자기 가게 주소를 * 직접 가져온 것이므로, 그 붙여넣기 자체가 확인이다. 여기서 또 [내 채널 맞아요]를 * 요구하면 같은 판단을 두 번 시키는 셈이다. */ const addLink = useCallback( async (url: string, channel: LinkChannelCode, title: string) => { if (!placeId) return; setIsAdding(true); try { const created = await createLink(placeId, { channel, url, title, discovered_by: SourceType.OWNER, }); const linkId = created.link?.link_id; if (linkId) await confirmLink(placeId, linkId); // 사장님이 직접 주소를 넣었으면 "자동으로 못 찾았습니다" 안내는 할 일을 다 했다 — 내린다. if (channel === LinkChannel.NAVER_PLACE) setNaverPlaceMissing(false); await queryClient .invalidateQueries({queryKey: getListLinksQueryKey(placeId)}) .catch(() => undefined); notify.success('채널을 등록했습니다', `${title} · 바로 수집 대상이 됩니다.`); } catch (error) { notifyApiError(error, '채널을 등록하지 못했습니다.'); } finally { setIsAdding(false); } }, [placeId], ); /** 사람이 "내 채널 맞다"고 고른 URL 을 확정한다. ★ 확정된 것만 크롤링 대상이 된다. */ const confirm = useCallback( async (linkId: string) => { if (!placeId) return; setConfirmingId(linkId); try { await confirmLink(placeId, linkId); await queryClient .invalidateQueries({queryKey: getListLinksQueryKey(placeId)}) .catch(() => undefined); } catch (error) { notifyApiError(error, '채널을 확정하지 못했습니다.'); } finally { setConfirmingId(null); } }, [placeId], ); return { links, confirmedCount, confirmingId, isAdding, naverPlaceMissing, setNaverPlaceMissing, isLinksLoading: enabled && linksQuery.isPending, addLink, confirm, }; }