From 45d78ca484534b811b575f238926cf08a14d0c56 Mon Sep 17 00:00:00 2001 From: yoonyoungyang Date: Thu, 12 Feb 2026 20:59:34 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=ED=81=AC=EB=A6=AC=EC=97=90=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routes/home/components/CreatorProfileCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/home/components/CreatorProfileCard.tsx b/app/routes/home/components/CreatorProfileCard.tsx index 41e5ca2..3a99f4b 100644 --- a/app/routes/home/components/CreatorProfileCard.tsx +++ b/app/routes/home/components/CreatorProfileCard.tsx @@ -28,7 +28,7 @@ export default function CreatorProfileCard({ return (
-

크레이터 님의 프로필

+

크리에이터 님의 프로필

From 2406f2dd101b4905652ed9fe4a60ee20d85a12ea Mon Sep 17 00:00:00 2001 From: yoonyoungyang Date: Thu, 12 Feb 2026 21:22:52 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EC=9D=B4=EC=83=81=ED=95=9C=20=EA=B8=B0?= =?UTF-8?q?=ED=98=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20>=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/OngoingCampaignSection.tsx | 22 +- .../detail/sponsorable-detail-content.tsx | 10 +- .../campaign-detail/campaign-detail.tsx | 19 +- app/routes/home/home-after-match.tsx | 287 +++++++++--------- 4 files changed, 189 insertions(+), 149 deletions(-) diff --git a/app/routes/brand-detail/components/OngoingCampaignSection.tsx b/app/routes/brand-detail/components/OngoingCampaignSection.tsx index e5bcaf7..cf763f6 100644 --- a/app/routes/brand-detail/components/OngoingCampaignSection.tsx +++ b/app/routes/brand-detail/components/OngoingCampaignSection.tsx @@ -28,26 +28,34 @@ export default function OngoingCampaignSection({ ) : null}
{isEmpty ? ( -
-
-
-
+
+
+
+
진행 중인 다른 캠페인이 없어요
) : ( -
+
{campaigns.map((c) => (
- navigate(-1)} /> + navigate(-1)} + />
diff --git a/app/routes/campaign-detail/campaign-detail.tsx b/app/routes/campaign-detail/campaign-detail.tsx index 888c3ee..1cf5756 100644 --- a/app/routes/campaign-detail/campaign-detail.tsx +++ b/app/routes/campaign-detail/campaign-detail.tsx @@ -146,7 +146,10 @@ export default function CampaignDetailContent({ { label: "모집인원", value: `${campaign.quota}명` }, { label: "우대사항", value: campaign.preferredSkills || "-" }, { label: "제품협찬", value: campaign.product || "-" }, - { label: "원고료", value: `${fmtMoney(campaign.rewardAmount)} (VAT포함)` }, + { + label: "원고료", + value: `${fmtMoney(campaign.rewardAmount)} (VAT포함)`, + }, { label: "일정", value: campaign.schedule || "-" }, // { // label: "모집기간", @@ -509,7 +512,15 @@ export default function CampaignDetailContent({ ); } -function DetailRow({ label, value, chips }: { label: string; value?: string; chips?: string[] }) { +function DetailRow({ + label, + value, + chips, +}: { + label: string; + value?: string; + chips?: string[]; +}) { return (
{label}
@@ -528,7 +539,9 @@ function DetailRow({ label, value, chips }: { label: string; value?: string; chi {c} ))} - {(!chips || chips.length === 0) && -} + {(!chips || chips.length === 0) && ( + - + )}
)}
diff --git a/app/routes/home/home-after-match.tsx b/app/routes/home/home-after-match.tsx index 8b30c28..ecfef27 100644 --- a/app/routes/home/home-after-match.tsx +++ b/app/routes/home/home-after-match.tsx @@ -14,7 +14,9 @@ import BrandCard from "./components/BrandCard"; import CampaignCard from "./components/CampaignCard"; import CreatorProfileCard from "./components/CreatorProfileCard"; -const TraitModal = lazy(() => import("../mypage/components/profileCard/TraitModal")); +const TraitModal = lazy( + () => import("../mypage/components/profileCard/TraitModal"), +); import { useMatchResultStore } from "../../stores/matching-result"; import { useCampaignProposalStore } from "../../stores/campaign-proposal"; @@ -76,7 +78,9 @@ const getBrandIdFromCampaign = (c: MatchingCampaign): number | null => { export default function HomeAfterMatchPage() { const navigate = useNavigate(); const [category, setCategory] = useState("beauty"); - const [selectedTraitType, setSelectedTraitType] = useState<"beauty" | "fashion" | "content" | null>(null); + const [selectedTraitType, setSelectedTraitType] = useState< + "beauty" | "fashion" | "content" | null + >(null); const [brands, setBrands] = useState([]); const [campaigns, setCampaigns] = useState([]); @@ -346,7 +350,10 @@ export default function HomeAfterMatchPage() { ...trait, topSummary: [ { label: "피부타입", value: names(beauty?.skinType).join(", ") }, - { label: "피부 밝기", value: names(beauty?.skinBrightness).join(", ") }, + { + label: "피부 밝기", + value: names(beauty?.skinBrightness).join(", "), + }, { label: "메이크업 스타일", value: names(beauty?.makeupStyle).join(", "), @@ -372,7 +379,10 @@ export default function HomeAfterMatchPage() { { label: "키/몸무게", value: names(fashion?.height).join(", ") }, { label: "체형", value: names(fashion?.bodyShape).join(", ") }, { label: "상의 사이즈", value: names(fashion?.topSize).join(", ") }, - { label: "하의 사이즈", value: names(fashion?.bottomSize).join(", ") }, + { + label: "하의 사이즈", + value: names(fashion?.bottomSize).join(", "), + }, ], sections: [ { @@ -407,7 +417,10 @@ export default function HomeAfterMatchPage() { label: "평균 영상 길이", value: names(content?.avgVideoLength).join(", "), }, - { label: "평균 조회수", value: names(content?.avgViews).join(", ") }, + { + label: "평균 조회수", + value: names(content?.avgViews).join(", "), + }, ], sections: [ { @@ -439,141 +452,141 @@ export default function HomeAfterMatchPage() { return traitsData.find((t) => t.id === selectedTraitType) || null; }, [selectedTraitType, traitsData]); - return ( -
-
- - - - -
- - -
- {brands.map((brand, i) => ( - +
+ + + + +
+ + +
+ {brands.map((brand, i) => ( + `#${t}`) + .join(" "), + badgeText: "모집중", + domain: category, + isLiked: brand.isLiked, + }} + onClick={() => + navigate(`/brand?brandId=${brand.id}&domain=${category}`) + } + onLikeToggle={handleBrandLikeToggle} + /> + ))} +
+
+
+ + +
+ {campaigns.map((campaign, i) => { + const safeCampaignId = getCampaignId(campaign); + if (!safeCampaignId) return null; + + return ( + `#${t}`) - .join(" "), - badgeText: "모집중", - domain: category, - isLiked: brand.isLiked, + id: String(safeCampaignId), + brandName: campaign.brandName, + matchRate: campaign.matchRate || 0, + descText: campaign.name || campaign.title || "", + rewardText: `원고료 ${campaign.reward?.toLocaleString()}원`, + ddayLabel: + campaign.dDay === 0 + ? "D-DAY" + : campaign.dDay + ? `D-${campaign.dDay}` + : "", + progressText: String(campaign.applicants), + isLiked: campaign.isLiked, + logoUrl: campaign.logoUrl, }} - onClick={() => - navigate(`/brand?brandId=${brand.id}&domain=${category}`) - } - onLikeToggle={handleBrandLikeToggle} + onClick={() => goCampaignDetail(campaign)} + onLikeToggle={handleCampaignLikeToggle} /> - ))} -
-
-
- +
+ {profileModel && ( +
+ navigate("/mypage/profileCard")} + onTraitClick={handleTraitClick} /> - -
- {campaigns.map((campaign, i) => { - const safeCampaignId = getCampaignId(campaign); - if (!safeCampaignId) return null; - - return ( - goCampaignDetail(campaign)} - onLikeToggle={handleCampaignLikeToggle} - /> - ); - })} -
-
- {profileModel && ( -
- navigate("/mypage/profileCard")} - onTraitClick={handleTraitClick} - /> -
- )} - -
- - -
- {popularCampaigns.map((campaign, i) => { - const safeCampaignId = getCampaignId(campaign); - if (!safeCampaignId) return null; - - return ( - goCampaignDetail(campaign)} - onLikeToggle={handleCampaignLikeToggle} - /> - ); - })} -
-
-
- - {selectedTrait && ( - - setSelectedTraitType(null)} - /> - +
)} + +
+ + +
+ {popularCampaigns.map((campaign, i) => { + const safeCampaignId = getCampaignId(campaign); + if (!safeCampaignId) return null; + + return ( + goCampaignDetail(campaign)} + onLikeToggle={handleCampaignLikeToggle} + /> + ); + })} +
+
- ); - } + + {selectedTrait && ( + + setSelectedTraitType(null)} + /> + + )} +
+ ); +} From e05ded62a1ef16a9a298dcc39ab26795363ee7d1 Mon Sep 17 00:00:00 2001 From: yoonyoungyang Date: Thu, 12 Feb 2026 21:35:31 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EB=B0=98=EC=9D=91=ED=98=95=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../brand-detail/brand-detail-content.tsx | 198 +++++++++--------- .../components/ProductMiniCard.tsx | 4 +- .../components/SponsorableProductSection.tsx | 10 +- .../sponsorable/sponsorable-content.tsx | 9 +- 4 files changed, 116 insertions(+), 105 deletions(-) diff --git a/app/routes/brand-detail/brand-detail-content.tsx b/app/routes/brand-detail/brand-detail-content.tsx index 78788a6..5d8ac15 100644 --- a/app/routes/brand-detail/brand-detail-content.tsx +++ b/app/routes/brand-detail/brand-detail-content.tsx @@ -235,7 +235,8 @@ export default function BrandDetailContent({ data }: Props) { (p) => { const fallback = (p.productImageUrls ?? []).find(Boolean) ?? - (p.thumbnailImageUrl ?? ""); + p.thumbnailImageUrl ?? + ""; return { productId: p.productId, @@ -343,12 +344,11 @@ export default function BrandDetailContent({ data }: Props) { ? domainParam : "beauty"; - const brandIdNum = - validBrandId - ? brandId - : Number.isFinite(Number(data.id)) && Number(data.id) > 0 - ? Number(data.id) - : null; + const brandIdNum = validBrandId + ? brandId + : Number.isFinite(Number(data.id)) && Number(data.id) > 0 + ? Number(data.id) + : null; if (!brandIdNum) return; @@ -489,14 +489,19 @@ export default function BrandDetailContent({ data }: Props) { const showTitle = showSectionTitle; return ( -
+
{showTitle ? (
{sec.title}
) : null} -
+
{(sec.groups ?? []).map((g, gi) => (
-
캠페인 내역
- - {histories.length === 0 ? ( -
-
-
-
-
- 진행한 캠페인이 없어요 -
-
-
-
-
- ) : ( - <> -
- {pageItems.map((h) => ( - - ))} -
- -
- {page > GROUP_SIZE && ( - - )} - - - -
- {displayPages.map((p) => { - const disabledPage = p > totalPages && !hasNext; - const active = p === page; - - return ( - - ); - })} -
- - - - -
- - )} -
+
캠페인 내역
+ + {histories.length === 0 ? ( +
+
+
+
+
+ 진행한 캠페인이 없어요 +
+
+
+
+
+ ) : ( + <> +
+ {pageItems.map((h) => ( + + ))} +
+ +
+ {page > GROUP_SIZE && ( + + )} + + + +
+ {displayPages.map((p) => { + const disabledPage = p > totalPages && !hasNext; + const active = p === page; + + return ( + + ); + })} +
+ + + +
+ + )} +
diff --git a/app/routes/brand-detail/components/ProductMiniCard.tsx b/app/routes/brand-detail/components/ProductMiniCard.tsx index 091565b..288d53b 100644 --- a/app/routes/brand-detail/components/ProductMiniCard.tsx +++ b/app/routes/brand-detail/components/ProductMiniCard.tsx @@ -6,13 +6,13 @@ export type ProductMiniCardItem = { type Props = { item: ProductMiniCardItem; - onClick?: () => void; + onClick?: () => void; }; export default function ProductMiniCard({ item, onClick }: Props) { return (
diff --git a/app/routes/brand-detail/components/SponsorableProductSection.tsx b/app/routes/brand-detail/components/SponsorableProductSection.tsx index 2502e1b..9b2db77 100644 --- a/app/routes/brand-detail/components/SponsorableProductSection.tsx +++ b/app/routes/brand-detail/components/SponsorableProductSection.tsx @@ -41,10 +41,10 @@ export default function SponsorableProductSection({ {isEmpty ? (
-
-
-
-
+
+
+
+
협찬 가능한 제품이 없어요
@@ -53,7 +53,7 @@ export default function SponsorableProductSection({
) : (
-
+
{products.map((p, idx) => (
- navigate(-1)} /> + navigate(-1)} + />
From 5eab440d4a44ba0f4a3cea94172346433b51292d Mon Sep 17 00:00:00 2001 From: yoonyoungyang Date: Thu, 12 Feb 2026 21:44:51 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=EC=BA=A0=ED=8E=98=EC=9D=B8=20=EB=B0=B0?= =?UTF-8?q?=EB=84=88=20=EC=A0=81=EC=9A=A9=20=EB=B0=8F=20=EB=B0=98=EC=9D=91?= =?UTF-8?q?=ED=98=95=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/OngoingCampaignSection.tsx | 2 +- .../detail/sponsorable-detail-content.tsx | 108 +++++++++++++++--- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/app/routes/brand-detail/components/OngoingCampaignSection.tsx b/app/routes/brand-detail/components/OngoingCampaignSection.tsx index cf763f6..70856b4 100644 --- a/app/routes/brand-detail/components/OngoingCampaignSection.tsx +++ b/app/routes/brand-detail/components/OngoingCampaignSection.tsx @@ -20,7 +20,7 @@ export default function OngoingCampaignSection({ const isEmpty = campaigns.length === 0; return ( -
+
진행 중인 다른 캠페인
diff --git a/app/routes/brand-detail/sponsorable/detail/sponsorable-detail-content.tsx b/app/routes/brand-detail/sponsorable/detail/sponsorable-detail-content.tsx index 001d65a..056ab11 100644 --- a/app/routes/brand-detail/sponsorable/detail/sponsorable-detail-content.tsx +++ b/app/routes/brand-detail/sponsorable/detail/sponsorable-detail-content.tsx @@ -1,4 +1,11 @@ -import { useContext, useEffect, useMemo, useState } from "react"; +import { + useContext, + useEffect, + useMemo, + useRef, + useState, + useCallback, +} from "react"; import { useLocation, useNavigate, useSearchParams } from "react-router-dom"; import NavigationHeader from "../../../../components/common/NavigateHeader"; import { LayoutContext } from "../../../layout-context"; @@ -6,6 +13,8 @@ import Button from "../../../../components/common/Button"; import { fetchSponsorProductDetail } from "../../api/api"; import LoadingSpinner from "../../../../components/common/LoadingSpinner"; +const INTERVAL = 3000; + type SponsorAvailableItem = { itemId: number; availableType: string; @@ -40,6 +49,11 @@ type NavState = { productName?: string; }; +interface BannerItem { + src: string; + alt: string; +} + function Pill({ children }: { children: string }) { return ( @@ -155,6 +169,43 @@ export default function SponsorableDetailContent() { const [loading, setLoading] = useState(false); const [errorText, setErrorText] = useState(null); + const [current, setCurrent] = useState(0); + const timerRef = useRef | null>(null); + + const banners: BannerItem[] = useMemo(() => { + const urls = (data?.productImageUrls ?? []) + .map((v) => (v ?? "").toString().trim()) + .filter(Boolean); + + return urls.map((src, idx) => ({ + src, + alt: `${data?.productName ?? "제품"} 배너 ${idx + 1}`, + })); + }, [data?.productImageUrls, data?.productName]); + + const start = useCallback(() => { + if (timerRef.current) return; + if (banners.length <= 1) return; + + timerRef.current = setInterval(() => { + setCurrent((prev) => (prev + 1) % banners.length); + }, INTERVAL); + }, [banners.length]); + + const stop = useCallback(() => { + if (timerRef.current) { + clearInterval(timerRef.current); + timerRef.current = null; + } + }, []); + + useEffect(() => { + setCurrent(0); + stop(); + start(); + return stop; + }, [banners.length, start, stop]); + useEffect(() => { if (!layout) return; layout.setHideHeader(true); @@ -203,8 +254,6 @@ export default function SponsorableDetailContent() { }; }, [brandId, productId, canFetch]); - const heroUrl = state.heroImageUrl || data?.productImageUrls?.[0] || ""; - const itemsText = useMemo(() => { if (!data) return ""; const items = data.sponsorInfo?.items ?? []; @@ -248,20 +297,47 @@ export default function SponsorableDetailContent() { {!loading && !errorText && data && (
- {heroUrl ? ( + {banners.length ? (
- {data.productName} -
- - - +
+ {banners.map((banner, i) => ( +
+ {banner.alt} +
+ ))}
+ + {banners.length > 1 && ( +
+ {banners.map((_, i) => ( +
+ )}
) : (
@@ -284,6 +360,7 @@ export default function SponsorableDetailContent() { ) : null}
+
@@ -321,7 +398,6 @@ export default function SponsorableDetailContent() {
- {/* ✅ 버튼 영역: 컨텐츠와 분리 (피그마: px-6 pt-14 pb-24) */} {showButton && (