Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
8147c5a
feat: ads 플랫폼 예산 타입 및 projectBudget 유틸 추가
YermIm Aug 3, 2026
8d84519
refactor: 광고 플랫폼 판별·그룹화 유틸 분리
YermIm Aug 3, 2026
27f87a8
feat: 캠페인 상세 플랫폼별 예산·광고 모아보기 UI
YermIm Aug 3, 2026
91db2a2
feat: ads PlatformBudgetItem light 예산 UI 적용
YermIm Aug 3, 2026
14c2490
fix: 광고 플랫폼 그룹화 개선 및 캠페인 상세 플랫폼 섹션 구분 강화
YermIm Aug 3, 2026
c6bd783
fix: ads AdListTable hidePlatformColumn 추가 및 테이블·예산 레이아웃 정리
YermIm Aug 3, 2026
0d200eb
fix: 캠페인 상세 플랫폼별 광고 테이블 전체 선택 범위 수정
YermIm Aug 3, 2026
919d74b
style: 캠페인 상세 플랫폼 섹션·예산·embedded 광고 테이블 UI 정리
YermIm Aug 3, 2026
fa00beb
fix: AdListTableSkeleton 스크롤 동작을 실제 테이블과 동일하게 수정
YermIm Aug 3, 2026
b3f66f8
feat: 멤버 조회 response에 소유자Id(userId) 타입 추가
jjjsun Aug 4, 2026
56d85ca
feat: 조직 양도 type과 API훅 추가
jjjsun Aug 4, 2026
db7e7fd
feat: 조직 소유자 양도 모달 구현
jjjsun Aug 4, 2026
95b4b8c
chore: 멤버선택컴포넌트 placeholder 수정
jjjsun Aug 4, 2026
eb48f30
feat: 소유자 뱃지 추가
jjjsun Aug 4, 2026
cb04440
Merge pull request #389 from WhereYouAd/feature/#384
YermIm Aug 4, 2026
cc2ce78
feat: 조직 소유권 양도 구현 완료
jjjsun Aug 4, 2026
dbc757c
fix: 공유 조회 중 카드 펼침 시 POST fallback 누락 버그 수정
Seojegyeong Aug 4, 2026
611d92b
feat: 공유 리포트 조회 시 팀 공유 분석 생성일 표시
Seojegyeong Aug 4, 2026
4ed0b83
fix: import 경로 수정
jjjsun Aug 4, 2026
de03332
fix: 양도 대상 조건 문구 수정
jjjsun Aug 4, 2026
48a9a74
feat: 조직 AI 리포트 목록 조회 API 및 쿼리키 추가
Seojegyeong Aug 4, 2026
1ab6428
fix: 전체 멤버 필요할 떄 cursor 끝까지 순회
jjjsun Aug 4, 2026
b0e1ed3
fix: 공유 리포트 조회 중 카드 펼침 시 불필요한 POST 방지
Seojegyeong Aug 4, 2026
2b29290
Merge pull request #390 from WhereYouAd/feature/#381
jjjsun Aug 4, 2026
a75cf48
Merge pull request #391 from WhereYouAd/feature/#377
Seojegyeong Aug 4, 2026
f43c663
feat: 플랫폼별 캠페인 예산 수정 타입 및 API 추가
YermIm Aug 4, 2026
f97413c
feat: 캠페인 예산 수정 검증 및 mutation 가드 유틸 추가
YermIm Aug 4, 2026
e97bab5
feat: 캠페인 상세 플랫폼별 예산 단일 표시 및 수정 버튼 레이아웃
YermIm Aug 4, 2026
acb316c
style: 게이지 카드 UI 수정 및 버튼 크기 조정
YermIm Aug 4, 2026
ee0e329
feat: 플랫폼별 캠페인 예산 수정 mutation 훅 추가
YermIm Aug 4, 2026
e7af2a9
feat: 플랫폼 예산 수정 모달 및 폼 유틸 추가
YermIm Aug 4, 2026
968b232
feat: 캠페인 상세 예산 수정 연동 및 금액 입력 UX 개선
YermIm Aug 4, 2026
c9addb0
refactor: ads 훅useCoreQuery·useCoreMutation 패턴 적용
YermIm Aug 4, 2026
08bd331
fix: 멤버초대 상태 안내에 스크린 리더 사용자를 위한 라이브 리전 추가
jjjsun Aug 5, 2026
4bd7c07
fix: 초대 토큰 특수문자 경로 깨지지 않도록 안전하게 인코딩, 함수명 오타 수정
jjjsun Aug 5, 2026
b59ba23
fix: 스크린 리더가 매초마다 읽을 수 있도록 수정
jjjsun Aug 5, 2026
c1c7995
style: 타임라인 관련 반응형 구현
jjjsun Aug 5, 2026
ce280ad
Merge pull request #393 from WhereYouAd/bugfix/#388
jjjsun Aug 5, 2026
489a104
style: 워크스페이스 관련 반응형 구현
jjjsun Aug 5, 2026
1c0b5a4
style: 설정/알림/로그아웃/회원탈퇴 관련 반응형 구현
jjjsun Aug 5, 2026
4811ed5
style: 타임라인 관련 모바일 반응형 적용
jjjsun Aug 5, 2026
d074ab8
fix: Naver 예산 일일 기준으로 표시/수정 통일
YermIm Aug 5, 2026
24efe1c
fix: 예산 수정 disabled 사유를 버튼 아래 안내 문구로 표시
YermIm Aug 5, 2026
687cd55
fix: 예산 수정 모달 제목 스크린리더 중복 낭독 제거
YermIm Aug 5, 2026
6a7163d
style: 모바일 반응형 자연스럽게 수정
jjjsun Aug 5, 2026
7a54fd5
fix: 일괄 status 변경 부분 성공 시에도 목록 캐시 갱신
YermIm Aug 5, 2026
53d7c79
fix: 예산 수정 모달 닫힘 시 포커스 복원 및 exit 애니메이션 유지
YermIm Aug 5, 2026
e5f8021
refactor: Meta/Google 예산 수정 요청 타입을 상호 배타 union으로 정리
YermIm Aug 5, 2026
3c1d9dc
fix: Meta/Google 예산 수정 시 금액 undefined 요청 방지
YermIm Aug 5, 2026
162ea47
fix: 활성 예산 판정 로직을 게이지·모달·payload에서 공통화
YermIm Aug 5, 2026
9b869a4
style: 코드래빗 수정사항 반영
jjjsun Aug 5, 2026
51273b2
Merge pull request #395 from WhereYouAd/feature/#394
jjjsun Aug 5, 2026
81af035
feat: 랜딩 mock데이터 구현
jjjsun Aug 5, 2026
b042a03
feat: 랜딩 타임라인 실제 타임라인과 맞춰서 UI 구현
jjjsun Aug 5, 2026
1fe837e
fix: 타임라인 바 onEdit/onDelete 둘다 안넘기면 케밥 메뉴 미표시 되도록 수정, mock데이터 수정
jjjsun Aug 5, 2026
ae37de6
Merge pull request #392 from WhereYouAd/feature/#385
YermIm Aug 5, 2026
1a930ed
fix: 알림 Slack/Discord ADMIN에게만 보이도록 처리
jjjsun Aug 5, 2026
23ba698
Merge pull request #399 from WhereYouAd/feature/#396
jjjsun Aug 5, 2026
c7a0306
Merge branch 'develop' into bugfix/#397
jjjsun Aug 5, 2026
16f6859
feat: 플랫폼 수동 동기화 API 타입 추가
YermIm Aug 5, 2026
1a13d16
feat: 플랫폼별 수동 동기화 API 함수 추가
YermIm Aug 5, 2026
3eae0eb
feat: 플랫폼 동기화 toast·Naver 날짜 유틸 및 validation 추가
YermIm Aug 5, 2026
d78607d
feat: Naver 수동 동기화 날짜 선택 모달 추가
YermIm Aug 5, 2026
1c4df58
feat: 플랫폼 연동 수동 동기화 UI 및 모달 연결
YermIm Aug 5, 2026
982b527
style: 네이버 동기화 모달 문구 크기 조정
YermIm Aug 5, 2026
38dfdcc
fix: Google 동기화 성공 시 요청 조직 connections invalidate
YermIm Aug 5, 2026
047223a
fix: Naver sync 날짜 스키마에 iso.date 형식 검증 추가
YermIm Aug 5, 2026
ffe0d1d
fix: Naver sync 기본 날짜를 로컬 yyyy-MM-dd로 변경
YermIm Aug 5, 2026
d427918
Merge pull request #400 from WhereYouAd/bugfix/#397
jjjsun Aug 6, 2026
1e37d1e
Merge pull request #403 from WhereYouAd/feature/#398
YermIm Aug 6, 2026
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
53 changes: 53 additions & 0 deletions src/api/ads/budget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type {
IMetaGoogleBudgetUpdateData,
INaverBudgetUpdateData,
INaverBudgetUpdateRequest,
TMetaGoogleBudgetUpdateRequest,
} from "@/types/ads/budget";
import type { ICommonResponse } from "@/types/common/common";

import { axiosInstance } from "@/lib/axiosInstance";

/**
* Meta 캠페인 예산 수정
* PATCH /api/meta/campaigns/{adCampaignId}/budget
* — daily / lifetime 중 하나만 수정 가능, 기존 유형과 동일해야 함
*/
export async function updateMetaCampaignBudget(
adCampaignId: number,
body: TMetaGoogleBudgetUpdateRequest,
): Promise<IMetaGoogleBudgetUpdateData> {
const { data } = await axiosInstance.patch<
ICommonResponse<IMetaGoogleBudgetUpdateData>
>(`/api/meta/campaigns/${adCampaignId}/budget`, body);
return data.data;
}

/**
* Naver 캠페인 예산 수정
* PUT /api/naver/{connectionId}/campaigns/{campaignId}/budget
*/
export async function updateNaverCampaignBudget(
connectionId: number,
campaignId: string,
body: INaverBudgetUpdateRequest,
): Promise<INaverBudgetUpdateData> {
const { data } = await axiosInstance.put<
ICommonResponse<INaverBudgetUpdateData>
>(`/api/naver/${connectionId}/campaigns/${campaignId}/budget`, body);
return data.data;
}

/**
* Google 캠페인 예산 수정
* PATCH /api/google/campaigns/{adCampaignId}/budget
*/
export async function updateGoogleCampaignBudget(
adCampaignId: number,
body: TMetaGoogleBudgetUpdateRequest,
): Promise<IMetaGoogleBudgetUpdateData> {
const { data } = await axiosInstance.patch<
ICommonResponse<IMetaGoogleBudgetUpdateData>
>(`/api/google/campaigns/${adCampaignId}/budget`, body);
return data.data;
}
13 changes: 13 additions & 0 deletions src/api/dashboard/aiAnalysis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ICommonResponse } from "@/types/common/common";
import type {
IAiReportListParams,
IAiReportListResponse,
IAnalysisRequest,
IReportStatusResponse,
} from "@/types/dashboard/aiAnalysis";
Expand Down Expand Up @@ -31,3 +33,14 @@ export const getAiReportByAccessToken = async (
>(`/api/ai/reports/${encodeURIComponent(accessToken)}`);
return data.data;
};

/** 조직 단위 AI 분석 리포트 목록 조회 (최신순, 본문 제외) */
export const getOrgAiReports = async (
orgId: number,
params?: IAiReportListParams,
): Promise<IAiReportListResponse> => {
const { data } = await axiosInstance.get<
ICommonResponse<IAiReportListResponse>
>(`/api/ai/organizations/${orgId}/reports`, { params });
return data.data;
};
8 changes: 8 additions & 0 deletions src/api/integration/google.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ICommonResponse } from "@/types/common/common";
import type { IGoogleSyncData } from "@/types/integration/platformSync";

import { axiosInstance } from "@/lib/axiosInstance";

Expand Down Expand Up @@ -30,3 +31,10 @@ export async function startGoogleOAuthLogin(orgId: number): Promise<void> {

window.location.assign(redirectUrl);
}

export async function syncGoogleAdData(): Promise<IGoogleSyncData> {
const { data } = await axiosInstance.post<ICommonResponse<IGoogleSyncData>>(
"/api/google/ad-infos",
);
return data.data;
}
8 changes: 8 additions & 0 deletions src/api/integration/meta.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ICommonResponse } from "@/types/common/common";
import type { IMetaSyncData } from "@/types/integration/platformSync";

import { axiosInstance } from "@/lib/axiosInstance";

Expand Down Expand Up @@ -30,3 +31,10 @@ export async function startMetaOAuthLogin(orgId: number): Promise<void> {

window.location.assign(authUrl);
}

export async function syncMetaAdData(orgId: number): Promise<IMetaSyncData> {
const { data } = await axiosInstance.post<ICommonResponse<IMetaSyncData>>(
`/api/meta/${orgId}/refresh`,
);
return data.data;
}
15 changes: 15 additions & 0 deletions src/api/integration/naver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import type {
INaverConnectResponseData,
INaverCredentialsRequest,
} from "@/types/integration/naver";
import type {
INaverSyncData,
INaverSyncRequest,
} from "@/types/integration/platformSync";

import { encryptAesCbcBase64 } from "@/utils/integration/encryptAesCbcBase64";

Expand Down Expand Up @@ -38,3 +42,14 @@ export async function updateNaverAccount(
toNaverEncryptedBody(body),
);
}

export async function syncNaverAdData(
orgId: number,
body: INaverSyncRequest,
): Promise<INaverSyncData> {
const { data } = await axiosInstance.post<ICommonResponse<INaverSyncData>>(
`/api/naver/${orgId}/sync`,
body,
);
return data.data;
}
73 changes: 54 additions & 19 deletions src/api/workspace/org.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import type { ICommonResponse } from "@/types/common/common";
import {
type TAcceptInvitationResponse,
type TCreateOrgRequest,
type TCreateOrgResponse,
type TDeleteWorkspaceMemberResponse,
type TGetOrgResponse,
type TGetWorkspaceMembersData,
type TInviteMemberRequest,
type TInviteMemberResponse,
type TMyOrgsData,
type TPendingMemberResponse,
type TUpdateMemberRoleRequest,
type TUpdateMemberRoleResponse,
type TUpdateWorkspaceRequest,
type TWorkspace,
type TWorkspaceDetail,
type TWorkspaceMemberCount,
import type {
TAcceptInvitationResponse,
TChangeOwnerRequest,
TChangeOwnerResponse,
TCreateOrgRequest,
TCreateOrgResponse,
TDeleteWorkspaceMemberResponse,
TGetOrgResponse,
TGetWorkspaceMembersData,
TInviteMemberRequest,
TInviteMemberResponse,
TMyOrgsData,
TPendingMemberResponse,
TUpdateMemberRoleRequest,
TUpdateMemberRoleResponse,
TUpdateWorkspaceRequest,
TWorkspace,
TWorkspaceDetail,
TWorkspaceMember,
TWorkspaceMemberCount,
} from "@/types/workspace/workspace";

import { axiosInstance } from "@/lib/axiosInstance";
Expand Down Expand Up @@ -156,11 +159,43 @@ export const saveSelectedWorkspace = async (orgId: number): Promise<void> => {
await axiosInstance.post(`/api/org/${orgId}/workspace`);
};

export const acceptInvitaton = async (
export const acceptInvitation = async (
token: string,
): Promise<TAcceptInvitationResponse> => {
const { data } = await axiosInstance.post<
ICommonResponse<TAcceptInvitationResponse>
>(`/api/org/invitations/${token}`);
>(`/api/org/invitations/${encodeURIComponent(token)}`);
return data.data;
};

export const changeWorkspaceOwner = async (
orgId: number,
body: TChangeOwnerRequest,
): Promise<TChangeOwnerResponse> => {
const { data } = await axiosInstance.patch<
ICommonResponse<TChangeOwnerResponse>
>(`/api/org/${orgId}/changeOwner`, body);

return data.data;
};

// 양도 후보 등 전체 멤버가 필요할 때 cursor 끝까지 돌기
export const getAllWorkspaceMembers = async (
orgId: number,
size = 100,
): Promise<{ creatorId: number; members: TWorkspaceMember[] }> => {
const members: TWorkspaceMember[] = [];
let cursor: string | null = null;
let creatorId = 0;
let hasNext = true;

while (hasNext) {
const page = await getWorkspaceMembers(orgId, cursor, size);
creatorId = page.creatorId;
members.push(...page.members);
hasNext = page.hasNext;
cursor = page.nextCursor;
}
Comment thread
jjjsun marked this conversation as resolved.
Resolved

return { creatorId, members };
};
3 changes: 3 additions & 0 deletions src/assets/icon/common/sync.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 15 additions & 11 deletions src/components/ads/AdDetailContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,33 @@ import { toast } from "sonner";
import type { IAd } from "@/types/ads/campaign";

import { useControlModal } from "@/hooks/ads/useControlModal";
import { useCreateTrackingUrl } from "@/hooks/ads/useCreateTrackingUrl";

import Badge from "../common/badge/Badge";
import Button from "../common/button/Button";
import Modal from "../common/modal/Modal";
import ModalContent from "../common/modal/ModalContent";

import { createTrackingUrl } from "@/api/ads/ads";
import LinkIcon from "@/assets/icon/common/link.svg?react";
import WarnCircleIcon from "@/assets/icon/common/warn-circle.svg?react";

export default function AdDetailContent({
ad,
refetchAds,
}: {
ad: IAd;
refetchAds: () => void;
}) {
const { orgId } = useParams<{
export default function AdDetailContent({ ad }: { ad: IAd }) {
const { orgId, projectId } = useParams<{
orgId: string;
projectId: string;
}>();

const orgIdNum = orgId ? Number(orgId) : null;
const projectIdNum = projectId ? Number(projectId) : null;

const { mutateAsync: mutateCreateTrackingUrl } = useCreateTrackingUrl(
orgIdNum,
projectIdNum,
);
Comment thread
jjjsun marked this conversation as resolved.
Resolved

const trackControl = useControlModal({
successMessage: "트래킹 링크가 발급되었습니다.",
errorMessage: "트래킹 링크 발급에 실패했습니다.",
onSuccess: refetchAds,
});

const isTrackingActive = !!ad.trackingUrl && ad.trackingUrl.length > 0;
Expand Down Expand Up @@ -179,7 +180,10 @@ export default function AdDetailContent({
);
throw new Error("랜딩 URL이 없습니다.");
}
await createTrackingUrl(Number(orgId), ad.id, ad.landingUrl);
await mutateCreateTrackingUrl({
adContentId: ad.id,
landingUrl: ad.landingUrl,
});
})
}
isLoading={trackControl.isLoading}
Expand Down
Loading
Loading