diff --git a/app/routes/business/calendar/calendar-content.tsx b/app/routes/business/calendar/calendar-content.tsx index 048657da..06e49fdb 100644 --- a/app/routes/business/calendar/calendar-content.tsx +++ b/app/routes/business/calendar/calendar-content.tsx @@ -203,6 +203,7 @@ export default function CalendarContent() { 받은 캠페인 보기 + navigate(`/business/proposal?type=received-campaign&proposalId=${id}`); } else { - // 기본값 또는 SENT - navigate(`/business/proposal?type=sent&proposalId=${id}`); + // 보낸 제안 (SENT) -> 보낸 캠페인 보기 + navigate(`/business/proposal?type=sent-campaign&proposalId=${id}`); } }; diff --git a/app/routes/business/proposal/api/proposal.ts b/app/routes/business/proposal/api/proposal.ts index 84419c7a..6cc0cc94 100644 --- a/app/routes/business/proposal/api/proposal.ts +++ b/app/routes/business/proposal/api/proposal.ts @@ -1,4 +1,4 @@ -import { AxiosError } from "axios"; +import axios, { AxiosError } from "axios"; import { axiosInstance } from "../../../../api/axios"; import type { BrandDetail } from "../../../../data/brand"; @@ -158,15 +158,25 @@ export const cancelCampaignApply = async ( campaignApplyId: string | number ): Promise> => { try { + + const id = Number(campaignApplyId); + const response = await axiosInstance.patch>( - `/v1/campaigns/apply/${campaignApplyId}/cancel` + `/v1/campaigns/apply/${id}/cancel` ); return response.data; } catch (error) { - console.error("지원 취소 실패:", error); - if (error instanceof AxiosError) { - const errorMessage = error.response?.data?.message || "지원 취소에 실패했습니다."; - throw new Error(errorMessage); + if (axios.isAxiosError(error)) { + // 서버에서 내려주는 구체적인 에러 객체 확인 + const serverData = error.response?.data; + console.error("서버 에러 상세:", serverData); + + // 403 에러일 경우 구체적인 메시지 처리 + if (error.response?.status === 403) { + throw new Error(serverData?.message || "지원 취소 권한이 없습니다. 본인이 지원한 내역인지 확인해주세요."); + } + + throw new Error(serverData?.message || "지원 취소 중 오류가 발생했습니다."); } throw error; } diff --git a/app/routes/business/proposal/application-content.tsx b/app/routes/business/proposal/application-content.tsx index 6187bcbf..515defcf 100644 --- a/app/routes/business/proposal/application-content.tsx +++ b/app/routes/business/proposal/application-content.tsx @@ -62,7 +62,7 @@ export default function ApplicationContent() { } const profileResult = await getProfileCard(); - setProfileCard(profileResult); + setProfileCard(profileResult); } catch (error) { console.error("최종 데이터 로드 실패:", error); } finally { @@ -83,7 +83,7 @@ export default function ApplicationContent() { } try { - const response = await cancelCampaignApply(applicationId); + const response = await cancelCampaignApply(data.campaignApplyId); if (response.isSuccess) { setModalStep("COMPLETE"); } @@ -151,7 +151,11 @@ export default function ApplicationContent() { {/* 제안 프로필 */}

제안 프로필

-
+
navigate("/mypage/profileCard")} + className="w-full p-4 bg-bluegray-2 rounded-2xl flex justify-between items-center border border-core-70 cursor-pointer active:bg-bluegray-3 transition-colors" + >
profile @@ -203,7 +207,7 @@ export default function ApplicationContent() { {modalStep === "CONFIRM" ? ( <>

- 제안을 취소하시겠습니까? + 지원을 취소하시겠습니까?

+
+
+ + {/* 2. 캠페인 상세 정보 섹션 */} +
+ {/* 캠페인 내용 (아코디언) */} + setIsContentOpen((prev) => !prev)}> + toggle + + } + > +
+
+

설명

+
+ {proposal.description} +
+
+ + {isContentOpen && ( +
+
+ +
+ + + + +
+ )} +
+
+ + {/* 협찬품 / 원고료 */} +
+ +
+ 네이처 스크럽 바 1개 + arrow +
+
+ + +
+ {proposal.rewardAmount.toLocaleString()} + +
+
+
+ + {/* 제작 기간 */} + +
+
+ {formatDate(proposal?.startDate || "")} +
+ ~ +
+ {formatDate(proposal?.endDate || "")} +
+
+
+ + {/* 기타 협의 사항 */} + } + > +
+
+
+ +
+ ); +} + +function ContentItem({ label, value }: { label: string; value: string }) { + return ( +
+

{label}

+
+ {value} + arrow +
+
+ ); +} \ No newline at end of file diff --git a/app/routes/business/proposal/route.tsx b/app/routes/business/proposal/route.tsx index 9ba7927e..05b7676b 100644 --- a/app/routes/business/proposal/route.tsx +++ b/app/routes/business/proposal/route.tsx @@ -2,6 +2,8 @@ import { useSearchParams } from "react-router"; import ProposalContent from "./sent-proposal-content"; import ReceivedProposalContent from "./received-proposal-content"; import ApplicationContent from "./application-content"; +import SentCampaignContent from "./sent-campaign-content"; +import ReceivedCampaignContent from "./received-campaign-content"; export default function Proposal() { const [searchParams] = useSearchParams(); @@ -15,5 +17,13 @@ export default function Proposal() { return ; } + if (type === "sent-campaign") { + return ; + } + + if (type === "received-campaign") { + return ; + } + return ; } diff --git a/app/routes/business/proposal/sent-campaign-content.tsx b/app/routes/business/proposal/sent-campaign-content.tsx new file mode 100644 index 00000000..31e633d6 --- /dev/null +++ b/app/routes/business/proposal/sent-campaign-content.tsx @@ -0,0 +1,193 @@ +import { useState, useEffect } from "react"; +import { useSearchParams, useNavigate } from "react-router-dom"; +import { getProposalDetail, type ProposalDetail } from "./api/proposal"; +import { getBrandSummary, type BrandSummary } from "./api/brand"; +import Header from "../../../components/layout/Header"; +import CampaignBrandCard from "../components/CampaignBrandCard"; +import LoadingSpinner from "../../../components/common/LoadingSpinner"; +import CampaignInfoGroup from "../components/CampaignInfoGroup"; + +import dropdownIcon from "../../../assets/arrow-down.svg"; +import dropupIcon from "../../../assets/arrow-up.svg"; +import arrowRightIcon from "../../../assets/icon/arrow-right.svg"; +import chatIcon from "../../../assets/chat-icon.svg"; // 채팅 아이콘 추가 필요 + +export default function SentCampaignContent() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [isContentOpen, setIsContentOpen] = useState(false); + + const [data, setData] = useState(null); + const [brand, setBrand] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + const proposalId = searchParams.get("proposalId"); + + useEffect(() => { + if (!proposalId) { + setIsLoading(false); + return; + } + + const fetchData = async () => { + try { + setIsLoading(true); + const proposalResult = await getProposalDetail(proposalId); + setData(proposalResult); + + if (proposalResult.brandId) { + const brandResult = await getBrandSummary(proposalResult.brandId); + setBrand(brandResult); + } + } catch (error) { + console.error("캠페인 상세 조회 실패:", error); + } finally { + setIsLoading(false); + } + }; + fetchData(); + }, [proposalId]); + + if (isLoading) return ; + if (!data) return
데이터를 찾을 수 없습니다.
; + + const getTagNames = (tags: { name: string }[]) => tags.map(t => t.name).join(", "); + + return ( +
+
+ +
+ {/* 상단 브랜드 정보 및 채팅 섹션 */} +
+ + +
+
+ 신규 캠페인 +

{data.title}

+
+ {/* 채팅하기 버튼 */} + +
+
+ + {/* 캠페인 상세 정보 영역 */} +
+ {/* 캠페인명 (수정 아이콘 포함 UI) */} + } + > +
+ {data.title} +
+
+ + {/* 캠페인 내용 (아코디언) */} + setIsContentOpen(prev => !prev)}> + toggle + + } + > +
+
+

설명

+
+ {data.description} +
+
+ + {isContentOpen && ( +
+
+ +
+ + + + +
+ )} +
+
+ + {/* 협찬품 / 원고료 */} +
+ +
+ {data.productId} + arrow +
+
+ + +
+ {data.rewardAmount.toLocaleString()} +
+
+
+ + {/* 제작 기간 */} + +
+
+ {(data?.startDate || "").replace(/-/g, '. ')} + calendar +
+ ~ +
+ {(data?.endDate || "").replace(/-/g, '. ')} + calendar +
+
+
+ + {/* 기타 협의 사항 (추가된 부분) */} + } + > +
+ {/* 데이터에 기타 협의 사항 필드가 있다면 출력 */} +

+ {data.description ? "협의된 내용이 없습니다." : ""} +

+
+
+
+
+
+ ); +} + +function ContentItem({ label, value }: { label: string; value: string }) { + return ( +
+

{label}

+
+ {value} + arrow +
+
+ ); +} \ No newline at end of file diff --git a/app/routes/notification/notification-content.tsx b/app/routes/notification/notification-content.tsx index 0e8acdbc..cb5aee23 100644 --- a/app/routes/notification/notification-content.tsx +++ b/app/routes/notification/notification-content.tsx @@ -155,7 +155,7 @@ export default function NotificationContent() {
) : (
- +

받은 알림이 없어요