`backend` 옆에 `front` 가 있을 이유가 없었다. negosium 의 negodata/front 를 그대로 베꼈고 그게 왜 front 인지는 따져보지 않았다 — 근거 없이 들여온 이름이라 바로잡는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019uYhHQdssRubirPirrdJJC
91 lines
3.6 KiB
TypeScript
91 lines
3.6 KiB
TypeScript
/**
|
|
* 채널 링크 — 목록 · 직접 등록 · 확정.
|
|
*
|
|
* ★ 수집 흐름(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<string | null>(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,
|
|
};
|
|
}
|