Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/integration/NaverConnectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface INaverConnectModalProps {
orgId: number;
mode?: TNaverConnectMode;
initialCustomerId?: string;
/** 최초 connect success 직후 sync 모달 오픈 (reconnect 제외) */
onConnectSuccess?: () => void;
}

type TNaverConnectFormValues = z.infer<typeof naverConnectSchema>;
Expand All @@ -36,6 +38,7 @@ export default function NaverConnectModal({
orgId,
mode = "connect",
initialCustomerId,
onConnectSuccess,
}: INaverConnectModalProps) {
const {
register,
Expand Down Expand Up @@ -82,6 +85,9 @@ export default function NaverConnectModal({
);
reset();
onClose();
if (mode === "connect") {
onConnectSuccess?.();
}
},
userOnError: (error) => {
toast.error(
Expand Down
14 changes: 13 additions & 1 deletion src/hooks/integration/useIntegrationOAuthReturn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,23 @@ const PROVIDER_LABEL: Record<TOAuthReturnProvider, string> = {
GOOGLE: "Google",
};

interface IUseIntegrationOAuthReturnOptions {
/** Google OAuth success 직후 초기 sync */
onGoogleConnectSuccess?: (requestOrgId: number) => void;
}

/** OAuth 연동 완료 후 `/integrations?status=&provider=` 쿼리 처리 (Google 전용) */
export function useIntegrationOAuthReturn(orgId: number | null) {
export function useIntegrationOAuthReturn(
orgId: number | null,
options?: IUseIntegrationOAuthReturnOptions,
) {
const [searchParams, setSearchParams] = useSearchParams();
const queryClient = useQueryClient();
const processedRef = useRef(false);

const onGoogleConnectSuccessRef = useRef(options?.onGoogleConnectSuccess);
onGoogleConnectSuccessRef.current = options?.onGoogleConnectSuccess;

useLayoutEffect(() => {
if (orgId == null) return;

Expand All @@ -45,6 +56,7 @@ export function useIntegrationOAuthReturn(orgId: number | null) {

if (status === "success") {
toast.success(`${label} 광고 계정을 연동했습니다.`);
onGoogleConnectSuccessRef.current?.(orgId);
} else {
toast.error(detail ?? `${label} 연동에 실패했습니다.`);
}
Expand Down
105 changes: 105 additions & 0 deletions src/hooks/integration/usePlatformSyncMutations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";

import type {
IGoogleSyncData,
IMetaSyncData,
INaverSyncData,
} from "@/types/integration/platformSync";

import type { TNaverSyncFormValues } from "@/utils/integration/naverSyncSchema";
import { getPlatformSyncToast } from "@/utils/integration/platformSync";

import { useCoreMutation } from "@/hooks/customQuery";

import { syncGoogleAdData } from "@/api/integration/google";
import { syncMetaAdData } from "@/api/integration/meta";
import { syncNaverAdData } from "@/api/integration/naver";
import { QUERY_KEYS } from "@/lib/queryKeys";

interface IUsePlatformSyncMutationsOptions {
/** sync success/error 공통 — syncingProvider 초기화 */
onSyncSettled?: () => void;
/** Naver sync success — sync 모달 닫기 */
onNaverSyncSuccess?: () => void;
}

export function usePlatformSyncMutations(
options?: IUsePlatformSyncMutationsOptions,
) {
const queryClient = useQueryClient();
const { onSyncSettled, onNaverSyncSuccess } = options ?? {};

const invalidateConnections = async (requestOrgId: number) => {
await queryClient.invalidateQueries({
queryKey: QUERY_KEYS.platform.connections(requestOrgId),
});
};

const metaSyncMutation = useCoreMutation<IMetaSyncData, number>(
(requestOrgId) => syncMetaAdData(requestOrgId),
{
userOnSuccess: async (data, requestOrgId) => {
await invalidateConnections(requestOrgId);
const { type, message } = getPlatformSyncToast("META", data);
if (type === "success") toast.success(message);
else toast.warning(message);
onSyncSettled?.();
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "Meta 동기화에 실패했습니다.");
onSyncSettled?.();
},
},
);

const googleSyncMutation = useCoreMutation<IGoogleSyncData, number>(
() => syncGoogleAdData(),
{
userOnSuccess: async (data, requestOrgId) => {
await invalidateConnections(requestOrgId);
const { type, message } = getPlatformSyncToast("GOOGLE", data);
if (type === "success") toast.success(message);
else toast.warning(message);
onSyncSettled?.();
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "Google 동기화에 실패했습니다.");
onSyncSettled?.();
},
},
);

const naverSyncMutation = useCoreMutation<
INaverSyncData,
{ requestOrgId: number; body: TNaverSyncFormValues }
>(({ requestOrgId, body }) => syncNaverAdData(requestOrgId, body), {
userOnSuccess: async (data, { requestOrgId }) => {
await invalidateConnections(requestOrgId);
const { type, message } = getPlatformSyncToast("NAVER", data);
if (type === "success") toast.success(message);
else toast.warning(message);
onSyncSettled?.();
onNaverSyncSuccess?.();
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "네이버 동기화에 실패했습니다.");
onSyncSettled?.();
},
});

const isSyncPending =
metaSyncMutation.isPending ||
googleSyncMutation.isPending ||
naverSyncMutation.isPending;

return {
syncMeta: (requestOrgId: number) => metaSyncMutation.mutate(requestOrgId),
syncGoogle: (requestOrgId: number) =>
googleSyncMutation.mutate(requestOrgId),
syncNaver: (requestOrgId: number, body: TNaverSyncFormValues) =>
naverSyncMutation.mutate({ requestOrgId, body }),
isSyncPending,
isNaverSyncPending: naverSyncMutation.isPending,
};
}
93 changes: 19 additions & 74 deletions src/pages/integration/PlatformIntegrationsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,14 @@ import type {
IPlatformConnectionItem,
TIntegrationProvider,
} from "@/types/integration/platformConnection";
import type {
IGoogleSyncData,
IMetaSyncData,
INaverSyncData,
} from "@/types/integration/platformSync";

import type { TNaverSyncFormValues } from "@/utils/integration/naverSyncSchema";
import { getPlatformSyncToast } from "@/utils/integration/platformSync";
import { startPlatformConnect } from "@/utils/integration/startPlatformConnect";

import { useCoreMutation } from "@/hooks/customQuery";
import { useIntegrationOAuthReturn } from "@/hooks/integration/useIntegrationOAuthReturn";
import { usePlatformConnections } from "@/hooks/integration/usePlatformConnections";
import { usePlatformSyncMutations } from "@/hooks/integration/usePlatformSyncMutations";

import AreaErrorFallback from "@/components/common/error/AreaErrorFallback";
import { ErrorBoundary } from "@/components/common/error/ErrorBoundary";
Expand All @@ -33,9 +28,6 @@ import {
KakaoUpcomingCard,
} from "@/components/integration/UpcomingPlatformCard";

import { syncGoogleAdData } from "@/api/integration/google";
import { syncMetaAdData } from "@/api/integration/meta";
import { syncNaverAdData } from "@/api/integration/naver";
import {
disconnectPlatformAccount,
reconnectPlatformAccount,
Expand Down Expand Up @@ -111,70 +103,22 @@ export default function PlatformIntegrationsPage() {
},
);

const invalidateConnections = async (requestOrgId: number) => {
await queryClient.invalidateQueries({
queryKey: QUERY_KEYS.platform.connections(requestOrgId),
const { syncMeta, syncGoogle, syncNaver, isSyncPending, isNaverSyncPending } =
usePlatformSyncMutations({
onSyncSettled: () => setSyncingProvider(null),
onNaverSyncSuccess: () => setIsNaverSyncModalOpen(false),
});
};

const metaSyncMutation = useCoreMutation<IMetaSyncData, number>(
(requestOrgId) => syncMetaAdData(requestOrgId),
{
userOnSuccess: async (data, requestOrgId) => {
await invalidateConnections(requestOrgId);
const { type, message } = getPlatformSyncToast("META", data);
if (type === "success") toast.success(message);
else toast.warning(message);
setSyncingProvider(null);
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "Meta 동기화에 실패했습니다.");
setSyncingProvider(null);
},
},
);

const googleSyncMutation = useCoreMutation<IGoogleSyncData, number>(
() => syncGoogleAdData(),
{
userOnSuccess: async (data, requestOrgId) => {
await invalidateConnections(requestOrgId);
const { type, message } = getPlatformSyncToast("GOOGLE", data);
if (type === "success") toast.success(message);
else toast.warning(message);
setSyncingProvider(null);
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "Google 동기화에 실패했습니다.");
setSyncingProvider(null);
},
},
);

const naverSyncMutation = useCoreMutation<
INaverSyncData,
{ requestOrgId: number; body: TNaverSyncFormValues }
>(({ requestOrgId, body }) => syncNaverAdData(requestOrgId, body), {
userOnSuccess: async (data, { requestOrgId }) => {
await invalidateConnections(requestOrgId);
const { type, message } = getPlatformSyncToast("NAVER", data);
if (type === "success") toast.success(message);
else toast.warning(message);
setSyncingProvider(null);
setIsNaverSyncModalOpen(false);
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "네이버 동기화에 실패했습니다.");
setSyncingProvider(null);
useIntegrationOAuthReturn(orgId, {
onGoogleConnectSuccess: (requestOrgId) => {
setSyncingProvider("GOOGLE");
syncGoogle(requestOrgId);
},
});
Comment thread
YermIm marked this conversation as resolved.

const isSyncPending =
metaSyncMutation.isPending ||
googleSyncMutation.isPending ||
naverSyncMutation.isPending;

useIntegrationOAuthReturn(orgId);
const handleNaverConnectSuccess = () => {
setIsNaverSyncModalOpen(true);
};

const startNewConnect = async (provider: TIntegrationProvider) => {
if (orgId == null) {
Expand Down Expand Up @@ -276,20 +220,20 @@ export default function PlatformIntegrationsPage() {
setSyncingProvider(provider);

if (provider === "META") {
metaSyncMutation.mutate(orgId);
syncMeta(orgId);
return;
}

if (provider === "GOOGLE") {
googleSyncMutation.mutate(orgId);
syncGoogle(orgId);
}
};

const handleNaverSyncSubmit = (values: TNaverSyncFormValues) => {
if (orgId == null || naverSyncMutation.isPending) return;
if (orgId == null || isNaverSyncPending) return;

setSyncingProvider("NAVER");
naverSyncMutation.mutate({ requestOrgId: orgId, body: values });
syncNaver(orgId, values);
};

return (
Expand Down Expand Up @@ -360,17 +304,18 @@ export default function PlatformIntegrationsPage() {
orgId={orgId}
mode={naverModalMode}
initialCustomerId={naverCustomerId}
onConnectSuccess={handleNaverConnectSuccess}
/>
) : null}
{orgId != null ? (
<NaverSyncModal
isOpen={isNaverSyncModalOpen}
onClose={() => {
if (naverSyncMutation.isPending) return;
if (isNaverSyncPending) return;
setIsNaverSyncModalOpen(false);
}}
onSubmit={handleNaverSyncSubmit}
isLoading={naverSyncMutation.isPending}
isLoading={isNaverSyncPending}
/>
) : null}
<PlatformDisconnectModal
Expand Down
Loading