Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
9 changes: 9 additions & 0 deletions src/api/integration/platformAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,12 @@ export async function disconnectPlatformAccount(
): Promise<void> {
await axiosInstance.delete(`/api/platform/${orgId}/accounts/${accountId}`);
}

export async function reconnectPlatformAccount(
orgId: number,
accountId: number,
): Promise<void> {
await axiosInstance.patch(
`/api/platform/${orgId}/accounts/${accountId}/reconnect`,
);
}
6 changes: 5 additions & 1 deletion src/components/integration/PlatformDisconnectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,13 @@ export default function PlatformDisconnectModal({
연동 계정 ·{" "}
<span className="text-text-title">{externalAccountId}</span>
<br />
<br />
</>
) : null}
연동 해제 시 관련 데이터가 삭제되며 복구할 수 없습니다.
해제 후에도 기존 계정을 다시 연동할 수 있으며, 계정은 매일 새벽 4시에
삭제됩니다.
<br />
다른 계정으로 바꾸려면 계정 삭제가 완료된 후 새로 연동해 주세요.
</p>

<div className="flex gap-4">
Expand Down
68 changes: 61 additions & 7 deletions src/components/integration/PlatformIntegrationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,49 @@ function PlatformConnectionMeta({
status,
syncedAt,
externalAccountId,
platformAccountId,
tokenExpireAt,
}: Pick<
IPlatformConnectionItem,
"status" | "syncedAt" | "externalAccountId" | "tokenExpireAt"
| "status"
| "syncedAt"
| "externalAccountId"
| "platformAccountId"
| "tokenExpireAt"
>) {
const syncedLabel = formatConnectionDateTime(syncedAt);
const expireLabel = formatConnectionDate(tokenExpireAt);
const expireTone = getTokenExpireTone(tokenExpireAt);
const isSoftDisconnected =
status === "disconnected" && platformAccountId != null;

return (
<div className="flex w-full flex-col gap-2">
{status === "disconnected" ? (
{isSoftDisconnected ? (
<>
{externalAccountId ? (
<p className="min-w-0 font-body2 text-text-muted">
<span>연동 계정 · </span>
<span
className="truncate text-text-title"
title={externalAccountId}
>
{externalAccountId}
</span>
</p>
) : null}
<p className="font-body2 text-text-muted">
<span>토큰 만료 예정 · </span>
{expireLabel ? (
<span className={TOKEN_EXPIRE_TEXT[expireTone]}>
{expireLabel}
</span>
) : (
<span className="text-text-muted/60">—</span>
)}
</p>
</>
) : status === "disconnected" ? (
<>
<p className="font-body2 text-text-muted/60">
<span>마지막 동기화 · </span>
Expand Down Expand Up @@ -114,7 +145,12 @@ function PlatformConnectionMeta({
{expireLabel}
</span>
</p>
) : null}
) : (
<p className="font-body2 text-text-muted">
<span>토큰 만료 예정 · </span>
<span className="text-text-muted/60">—</span>
</p>
)}
</>
)}
</div>
Expand All @@ -126,13 +162,18 @@ function PlatformIntegrationCard({
status,
syncedAt,
externalAccountId,
platformAccountId,
tokenExpireAt,
errorMessage,
onConnect,
onReconnect,
onDisconnect,
}: TProps) {
const label = PLATFORM_MAP[provider] ?? provider;
const statusLabel =
status === "disconnected" && platformAccountId != null
? "연동 해제"
: STATUS_LABEL[status];

return (
<div className="flex h-full min-h-70 w-full flex-col gap-5 rounded-3xl bg-surface-100 p-8 shadow-Soft">
Expand All @@ -147,14 +188,15 @@ function PlatformIntegrationCard({
variant={CONNECTION_STATUS_BADGE[status]}
className="h-8 shrink-0 font-body2"
>
{STATUS_LABEL[status]}
{statusLabel}
</Badge>
</div>

<PlatformConnectionMeta
status={status}
syncedAt={syncedAt}
externalAccountId={externalAccountId}
platformAccountId={platformAccountId}
tokenExpireAt={tokenExpireAt}
/>

Expand All @@ -169,8 +211,20 @@ function PlatformIntegrationCard({
<div className="mt-auto flex w-full flex-col gap-4">
{status === "disconnected" ? (
<p className="font-body2 text-text-muted/80">
광고 계정을 연동하면 대시보드와 캠페인에서 데이터를 확인할 수
있습니다.
{platformAccountId != null ? (
<>
기존 계정을 다시 연동하면 대시보드와 캠페인에서 데이터를 확인할
수 있습니다.
<br />
계정이 완전히 삭제되기 전까지는 기존 계정 복구가 가능하며,
현재는 다른 계정 연동이 불가능합니다.
</>
) : (
<>
광고 계정을 연동하면 대시보드와 캠페인에서 데이터를 확인할 수
있습니다.
</>
)}
</p>
) : null}

Expand Down Expand Up @@ -199,7 +253,7 @@ function PlatformIntegrationCard({
className="min-w-0 flex-1"
onClick={onDisconnect}
>
연결 해제
연동 해제
</Button>
</>
) : null}
Expand Down
69 changes: 58 additions & 11 deletions src/pages/integration/PlatformIntegrationsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
KakaoUpcomingCard,
} from "@/components/integration/UpcomingPlatformCard";

import { disconnectPlatformAccount } from "@/api/integration/platformAccounts";
import {
disconnectPlatformAccount,
reconnectPlatformAccount,
} from "@/api/integration/platformAccounts";
import { QUERY_KEYS } from "@/lib/queryKeys";
import useWorkspaceStore from "@/store/useWorkspaceStore";

Expand Down Expand Up @@ -73,26 +76,40 @@ export default function PlatformIntegrationsPage() {
},
);

const reconnectMutation = useCoreMutation<
void,
{ orgId: number; accountId: number }
>(
({ orgId: requestOrgId, accountId }) =>
reconnectPlatformAccount(requestOrgId, accountId),
{
userOnSuccess: async (_, { orgId: requestOrgId }) => {
await queryClient.invalidateQueries({
queryKey: QUERY_KEYS.platform.connections(requestOrgId),
});
toast.success("광고 계정을 다시 연동했습니다.");
},
userOnError: (apiError) => {
toast.error(apiError.message ?? "재연동에 실패했습니다.");
},
},
);
Comment thread
YermIm marked this conversation as resolved.

useIntegrationOAuthReturn(orgId);

const handleConnect = async (provider: TIntegrationProvider) => {
const startNewConnect = async (provider: TIntegrationProvider) => {
if (orgId == null) {
toast.error("워크스페이스를 선택해 주세요.");
return;
}
if (provider === "NAVER") {
const naverItem = platformConnections.find((p) => p.provider === "NAVER");

if (naverItem?.platformAccountId != null) {
setNaverModalMode("reconnect");
setNaverCustomerId(naverItem.externalAccountId);
} else {
setNaverModalMode("connect");
setNaverCustomerId(undefined);
}
if (provider === "NAVER") {
setNaverModalMode("connect");
setNaverCustomerId(undefined);
setIsNaverModalOpen(true);
return;
}

try {
await startPlatformConnect(provider, orgId);
} catch (err) {
Expand All @@ -105,6 +122,36 @@ export default function PlatformIntegrationsPage() {
}
};

const handleConnect = async (provider: TIntegrationProvider) => {
if (orgId == null) {
toast.error("워크스페이스를 선택해 주세요.");
return;
}

const item = platformConnections.find((p) => p.provider === provider);
if (item?.status === "disconnected" && item.platformAccountId != null) {
if (reconnectMutation.isPending) return;
reconnectMutation.mutate({
orgId,
accountId: item.platformAccountId,
});
return;
}

if (provider === "NAVER") {
const naverItem = platformConnections.find((p) => p.provider === "NAVER");

if (naverItem?.platformAccountId != null) {
setNaverModalMode("reconnect");
setNaverCustomerId(naverItem.externalAccountId);
setIsNaverModalOpen(true);
return;
}
}

await startNewConnect(provider);
};

const handleDisconnect = (item: IPlatformConnectionItem) => {
if (orgId == null) {
toast.error("워크스페이스를 선택해 주세요.");
Expand Down
6 changes: 5 additions & 1 deletion src/types/integration/platformConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import type { TProvider } from "@/types/ads/campaign";
export type TIntegrationProvider = TProvider;

/** 목록 API `platformAccounts[].status` */
export type TPlatformAccountApiStatus = "ACTIVE" | "EXPIRED" | "INACTIVE";
export type TPlatformAccountApiStatus =
| "ACTIVE"
| "EXPIRED"
| "INACTIVE"
| "DISCONNECTED";

export type TPlatformAuthType = "OAUTH";

Expand Down
2 changes: 1 addition & 1 deletion src/utils/integration/mapPlatformAccounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function mapApiStatusToUi(
if (account.status === "ACTIVE") {
return "connected";
}
return "disconnected";
return "disconnected"; // DISCONNECTED, INACTIVE 포함
}

function mapAccountToConnection(
Expand Down
Loading