diff --git a/app/assets/ad/ad-realmatch-detail-campagin.png b/app/assets/ad/ad-realmatch-detail-campagin.png new file mode 100644 index 0000000..0e5cdab Binary files /dev/null and b/app/assets/ad/ad-realmatch-detail-campagin.png differ diff --git a/app/assets/ad/ad-realmatch-detail-logo.png b/app/assets/ad/ad-realmatch-detail-logo.png new file mode 100644 index 0000000..40753c6 Binary files /dev/null and b/app/assets/ad/ad-realmatch-detail-logo.png differ diff --git a/app/assets/ad/ad-realmatch-logo.png b/app/assets/ad/ad-realmatch-logo.png new file mode 100644 index 0000000..98dc15c Binary files /dev/null and b/app/assets/ad/ad-realmatch-logo.png differ diff --git a/app/assets/ad/ad-realtmatch-detail-banner.png b/app/assets/ad/ad-realtmatch-detail-banner.png new file mode 100644 index 0000000..dff4f9a Binary files /dev/null and b/app/assets/ad/ad-realtmatch-detail-banner.png differ diff --git a/app/routes/brand-detail/api/api.ts b/app/routes/brand-detail/api/api.ts index f18d41f..c8e4975 100644 --- a/app/routes/brand-detail/api/api.ts +++ b/app/routes/brand-detail/api/api.ts @@ -1,4 +1,7 @@ import { apiClient } from "../../../api/axios"; +//import realmatchDetailLogo from "../../../assets/ad/ad-realmatch-detail-logo.png"; +//import realmatchDetailBanner from "../../../assets/ad/ad-realtmatch-detail-banner.png"; +//import realmatchDetailCampaign from "../../../assets/ad/ad-realmatch-detail-campagin.png"; import type { BrandDomain, BrandDetailData, @@ -176,6 +179,31 @@ export async function fetchSponsorProductList(params: { }): Promise { const { brandId } = params; + /*if (brandId === "0") { + return [ + { + brandId: 0, + brandName: "리얼매치", + productId: 0, + productName: "리얼이 캐릭터 크림", + thumbnailImageUrl: realmatchDetailCampaign, + productImageUrls: [realmatchDetailCampaign], + categories: ["스킨케어"], + sponsorInfo: { + items: [ + { + itemId: 0, + availableType: "FULL", + availableQuantity: 1, + availableSize: 0, + shippingType: "CREATOR_PAY", + }, + ], + }, + }, + ]; + }*/ + const res = await apiClient.get( `/api/v1/brands/${brandId}/sponsor-products`, ); @@ -195,6 +223,28 @@ export async function fetchSponsorProductDetail(params: { }): Promise { const { brandId, productId } = params; + /*if (brandId === "0" && String(productId) === "0") { + return { + brandId: 0, + brandName: "리얼매치", + productId: 0, + productName: "리얼이 캐릭터 크림", + productImageUrls: [realmatchDetailCampaign], + categories: ["스킨케어"], + sponsorInfo: { + items: [ + { + itemId: 0, + availableType: "FULL", + availableQuantity: 1, + availableSize: 0, + shippingType: "CREATOR_PAY", + }, + ], + }, + }; + }*/ + const res = await apiClient.get( `/api/v1/brands/${brandId}/sponsor-products/${productId}`, ); @@ -214,6 +264,38 @@ export async function fetchBrandDetail(params: { }): Promise { const { brandId, domain } = params; + /*if (brandId === "0") { + return { + id: "0", + userId: 0, + brandUserId: undefined, + domain: domain ?? "beauty", + name: "리얼매치", + matchRate: 0, + heroImageUrl: realmatchDetailBanner, + brandImages: [realmatchDetailBanner], + logoText: "리얼매치", + logoImageUrl: realmatchDetailLogo, + homepageUrl: undefined, + simpleIntro: "크리에이터와 브랜드를 정밀 매칭하는 플랫폼", + hashtags: ["정밀매칭", "원스톱협업", "쌍방향제안"], + description: "크리에이터와 브랜드를 정밀 매칭하는 플랫폼", + categories: [], + tagSections: [], + isLiked: false, + ongoingCampaigns: [], + products: [ + { + productId: 0, + productName: "리얼이 캐릭터 크림", + thumbnailImageUrl: realmatchDetailCampaign, + }, + ], + histories: [], + historiesHasNext: false, + }; + }*/ + const detailRes = await apiClient.get( `/api/v1/brands/${brandId}`, ); diff --git a/app/routes/brand-detail/brand-detail-content.tsx b/app/routes/brand-detail/brand-detail-content.tsx index 7b3277c..d9d14b4 100644 --- a/app/routes/brand-detail/brand-detail-content.tsx +++ b/app/routes/brand-detail/brand-detail-content.tsx @@ -11,7 +11,7 @@ import HistoryRow from "./components/HistoryRow"; import SponsorableProductSection from "./components/SponsorableProductSection"; import { tokenStorage } from "../../lib/token"; -import { toggleBrandLike } from "../matching/api/matching"; +import { toggleBrandLike, toggleCampaignLike } from "../matching/api/matching"; import { useCampaignProposalStore } from "../../stores/campaign-proposal"; import { apiClient } from "../../api/axios"; @@ -143,7 +143,7 @@ const getNumberField = ( const rec = obj as Record; for (const k of keys) { const v = rec[k]; - if (typeof v === "number" && Number.isFinite(v) && v > 0) return v; + if (typeof v === "number" && Number.isFinite(v) && v >= 0) return v; } return null; }; @@ -172,12 +172,14 @@ export default function BrandDetailContent({ data }: Props) { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const brandId = Number(searchParams.get("brandId")); - const validBrandId = Number.isFinite(brandId) && brandId > 0; + const validBrandId = Number.isFinite(brandId) && brandId >= 0; const setProposalData = useCampaignProposalStore( (state) => state.setProposalData, ); + const isHardcodedBeauty = brandId === 0 && searchParams.get("domain") === "beauty"; + const baseOngoingCampaigns = useMemo( () => data.ongoingCampaigns ?? [], [data.ongoingCampaigns], @@ -187,19 +189,21 @@ export default function BrandDetailContent({ data }: Props) { Record >({}); + const ongoingCampaigns = useMemo(() => { - if (baseOngoingCampaigns.length === 0) return []; const overrides = ongoingLikeOverrides; - return baseOngoingCampaigns.map((c) => { + const real = baseOngoingCampaigns.map((c) => { const cid = getCampaignIdFromOngoing(c); - if (!cid) return c; + if (cid === null) return c; if (Object.prototype.hasOwnProperty.call(overrides, cid)) { return { ...(c as object), isLiked: overrides[cid] } as OngoingCampaign; } return c; }); + + return real; }, [baseOngoingCampaigns, ongoingLikeOverrides]); const ongoingLikeInFlight = useRef>(new Set()); @@ -208,13 +212,17 @@ export default function BrandDetailContent({ data }: Props) { ProductMiniCardItem[] >([]); - const sponsorProducts = useMemo( - () => (validBrandId ? sponsorProductsRaw : []), - [validBrandId, sponsorProductsRaw], - ); + const sponsorProducts = useMemo(() => { + if (!validBrandId) return []; + // brandId=0일 때는 data.products를 직접 사용 + if (brandId === 0) return data.products ?? []; + return sponsorProductsRaw; + }, [validBrandId, brandId, data.products, sponsorProductsRaw]); useEffect(() => { if (!validBrandId) return; + // brandId=0일 때는 API 호출 건너뛰기 (data.products 사용) + //if (brandId === 0) return; let alive = true; @@ -278,16 +286,53 @@ export default function BrandDetailContent({ data }: Props) { const domain = searchParams.get("domain"); - setProposalData({ - brandId, - campaignId: 0, - domain: domain || "beauty", - brandName: data.name, - products: sponsorProducts.map((p) => ({ - id: String(p.productId), - name: p.productName, - })), - }); + // brandId=0일 때는 광고 캠페인 정보 포함 + if (brandId === 0) { + setProposalData({ + brandId, + campaignId: 0, + domain: domain || "beauty", + brandName: data.name, + campaignTitle: "'리얼이 캐릭터 크림' 론칭 리뷰", + campaignDescription: "'리얼이 캐릭터 크림'\n겟레디윗미 영상에서 자연스럽게 노출", + rewardAmount: 200000, + product: "리얼이 캐릭터 크림 1개", + startDate: "2025-01-05", + endDate: "2025-01-22", + contentTags: { + formats: [{ id: 3, name: "인스타 릴스" }], + categories: [ + { id: 6, name: "리뷰" }, + { id: 7, name: "겟레디윗미" }, + ], + tones: [ + { id: 16, name: "일상적인" }, + { id: 17, name: "수다적인" }, + ], + usageRanges: [ + { id: 24, name: "크리에이터 1차활용" }, + { id: 25, name: "브랜드 2차활용" }, + ], + involvements: [{ id: 20, name: "가이드만 제공" }], + }, + products: sponsorProducts.map((p) => ({ + id: String(p.productId), + name: p.productName, + })), + }); + } else { + // 일반 브랜드는 기존 로직 유지 + setProposalData({ + brandId, + campaignId: 0, + domain: domain || "beauty", + brandName: data.name, + products: sponsorProducts.map((p) => ({ + id: String(p.productId), + name: p.productName, + })), + }); + } navigate("/matching/suggest"); }; @@ -306,7 +351,7 @@ export default function BrandDetailContent({ data }: Props) { const handleSponsorableProductClick = (productId: number) => { if (!validBrandId) return; - if (!Number.isFinite(productId) || productId <= 0) return; + if (!Number.isFinite(productId)) return; navigate( `/products/sponsorable/detail?brandId=${brandId}&productId=${productId}`, @@ -335,8 +380,8 @@ export default function BrandDetailContent({ data }: Props) { }; const goOngoingCampaignDetail = (c: OngoingCampaign) => { - const cid = getCampaignIdFromOngoing(c); - if (!cid) return; + const cid = getCampaignIdFromOngoing(c) ?? (c.campaignId === 0 ? 0 : null); + if (cid === null) return; const domainParam = searchParams.get("domain"); const domain = @@ -346,11 +391,11 @@ export default function BrandDetailContent({ data }: Props) { const brandIdNum = validBrandId ? brandId - : Number.isFinite(Number(data.id)) && Number(data.id) > 0 + : Number.isFinite(Number(data.id)) && Number(data.id) >= 0 ? Number(data.id) : null; - if (!brandIdNum) return; + if (brandIdNum === null) return; navigate( `/campaign?brandId=${brandIdNum}&campaignId=${cid}&domain=${domain}`, @@ -365,7 +410,7 @@ export default function BrandDetailContent({ data }: Props) { } const clickedId = Number(id); - if (!Number.isFinite(clickedId) || clickedId <= 0) return; + if (!Number.isFinite(clickedId) || clickedId < 0) return; const currentItem = ongoingCampaigns.find((c) => { const cid = getCampaignIdFromOngoing(c); @@ -374,7 +419,7 @@ export default function BrandDetailContent({ data }: Props) { if (!currentItem) return; const cid = getCampaignIdFromOngoing(currentItem); - if (!cid) return; + if (cid === null) return; if (ongoingLikeInFlight.current.has(cid)) return; ongoingLikeInFlight.current.add(cid); @@ -385,14 +430,24 @@ export default function BrandDetailContent({ data }: Props) { setOngoingLikeOverrides((m) => ({ ...m, [cid]: next })); - ongoingLikeInFlight.current.delete(cid); + try { + await toggleCampaignLike(cid); + } catch (error) { + console.error("Failed to toggle campaign like:", error); + setOngoingLikeOverrides((m) => ({ ...m, [cid]: prev })); + } finally { + ongoingLikeInFlight.current.delete(cid); + } }; const PAGE_SIZE = 4; const GROUP_SIZE = 4; - const histories = data.histories ?? []; - const hasNext = !!data.historiesHasNext; + const histories = useMemo(() => { + return data.histories ?? []; + }, [data.histories]); + + const hasNext = isHardcodedBeauty ? false : !!data.historiesHasNext; const [page, setPage] = useState(1); @@ -457,8 +512,9 @@ export default function BrandDetailContent({ data }: Props) {
@@ -486,6 +542,7 @@ export default function BrandDetailContent({ data }: Props) {
{(tagSections ?? []).map((sec, idx) => { + const showTitle = showSectionTitle; return ( diff --git a/app/routes/brand-detail/components/BrandInfo.tsx b/app/routes/brand-detail/components/BrandInfo.tsx index 1f173ac..151cbb6 100644 --- a/app/routes/brand-detail/components/BrandInfo.tsx +++ b/app/routes/brand-detail/components/BrandInfo.tsx @@ -3,6 +3,7 @@ type Props = { matchRate: number; hashtags: string[]; description: string; + isAd?: boolean; }; export default function BrandInfo({ @@ -10,6 +11,7 @@ export default function BrandInfo({ matchRate, hashtags, description, + isAd = false, }: Props) { return (
@@ -17,11 +19,16 @@ export default function BrandInfo({
{name}
- 매칭률 - - - {matchRate}% - + {isAd ? ( + 광고 + ) : ( + <> + 매칭률 + + {matchRate}% + + + )}
diff --git a/app/routes/brand-detail/components/OngoingCampaignSection.tsx b/app/routes/brand-detail/components/OngoingCampaignSection.tsx index 70856b4..f155d75 100644 --- a/app/routes/brand-detail/components/OngoingCampaignSection.tsx +++ b/app/routes/brand-detail/components/OngoingCampaignSection.tsx @@ -58,25 +58,12 @@ export default function OngoingCampaignSection({
{campaigns.map((c) => ( -
onCampaignClick?.(c)} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === " ") { - e.preventDefault(); - onCampaignClick?.(c); - } - }} - className="text-left" - > - onCampaignClick?.(c)} - onLikeToggle={onLikeToggle} - /> -
+ onLikeToggle={onLikeToggle} + /> ))}
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 0411b13..9f42ede 100644 --- a/app/routes/brand-detail/sponsorable/detail/sponsorable-detail-content.tsx +++ b/app/routes/brand-detail/sponsorable/detail/sponsorable-detail-content.tsx @@ -163,9 +163,8 @@ export default function SponsorableDetailContent() { const canFetch = Number.isFinite(brandId) && - (brandId ?? 0) > 0 && - Number.isFinite(productId) && - productId > 0; + brandId !== undefined && + Number.isFinite(productId); const [data, setData] = useState(null); const [loading, setLoading] = useState(false); diff --git a/app/routes/brand-detail/sponsorable/sponsorable-content.tsx b/app/routes/brand-detail/sponsorable/sponsorable-content.tsx index 2b7a365..b049ded 100644 --- a/app/routes/brand-detail/sponsorable/sponsorable-content.tsx +++ b/app/routes/brand-detail/sponsorable/sponsorable-content.tsx @@ -5,6 +5,7 @@ import ProductListCard from "../components/ProductListCard"; import { LayoutContext } from "../../layout-context"; import { fetchSponsorProductList } from "../../brand-detail/api/api"; import LoadingSpinner from "../../../components/common/LoadingSpinner"; +import realmatchDetailCampaign from "../../../assets/ad/ad-realmatch-detail-campagin.png"; import type { SponsorProductsListDto } from "../../brand-detail/types"; @@ -20,17 +21,6 @@ type NavState = { brandName?: string; }; -const mapType = (t: string) => { - switch (t) { - case "FULL": - return "본품"; - case "SAMPLE": - return "샘플"; - default: - return t; - } -}; - const buildSubtitle = (dto: SponsorProductsListDto) => { const name = (dto.productName ?? "").toString(); const items = dto.sponsorInfo?.items ?? []; @@ -39,8 +29,6 @@ const buildSubtitle = (dto: SponsorProductsListDto) => { const parts = items .map((it) => { - const type = mapType((it.availableType ?? "").toString().trim()); - const qty = typeof it.availableQuantity === "number" && it.availableQuantity > 0 ? `${it.availableQuantity}개` @@ -51,7 +39,7 @@ const buildSubtitle = (dto: SponsorProductsListDto) => { ? `${it.availableSize}ml` : ""; - const left = [name, type, qty].filter(Boolean).join(" ").trim(); + const left = [name, qty].filter(Boolean).join(" ").trim(); const right = size.trim(); return [left, right].filter(Boolean).join(" / "); @@ -90,7 +78,7 @@ export default function SponsorableContent() { const layout = useContext(LayoutContext); const canFetch = useMemo( - () => Number.isFinite(brandId) && (brandId ?? 0) > 0, + () => Number.isFinite(brandId) && brandId !== undefined, [brandId], ); @@ -102,7 +90,7 @@ export default function SponsorableContent() { }, [layout]); useEffect(() => { - if (!canFetch || !brandId) return; + if (!canFetch || brandId === undefined) return; let cancelled = false; @@ -111,6 +99,21 @@ export default function SponsorableContent() { setLoading(true); setErrorText(null); + // brandId=0일 때 하드코딩된 데이터 사용 + if (brandId === 0) { + if (cancelled) return; + setProducts([ + { + productId: 0, + productName: "리얼이 캐릭터 크림", + imageUrl: realmatchDetailCampaign, + subtitle: "리얼이 캐릭터 크림 1개", + }, + ]); + setLoading(false); + return; + } + const list = await fetchSponsorProductList({ brandId: String(brandId), }); @@ -136,8 +139,8 @@ export default function SponsorableContent() { }, [brandId, canFetch]); const goDetail = (product: UiSponsorProduct) => { - if (!brandId || !Number.isFinite(brandId) || brandId <= 0) return; - if (!Number.isFinite(product.productId) || product.productId <= 0) return; + if (brandId === undefined || !Number.isFinite(brandId)) return; + if (!Number.isFinite(product.productId)) return; navigate( `/products/sponsorable/detail?brandId=${brandId}&productId=${product.productId}`, diff --git a/app/routes/business/campaign/$campaignId.tsx b/app/routes/business/campaign/$campaignId.tsx index 6d76a1d..199d4ec 100644 --- a/app/routes/business/campaign/$campaignId.tsx +++ b/app/routes/business/campaign/$campaignId.tsx @@ -27,27 +27,41 @@ export default function CampaignContent() { const [brand, setBrand] = useState(null); useEffect(() => { - console.log("1. 현재 주소창에서 가져온 ID:", campaignId); if (!campaignId) return; const loadData = async () => { try { - let res; + let res: ProposalDetail | AppliedCampaignDetail; + if (isApplied) { - // 지원한 캠페인 API 호출 + // 지원한 캠페인일 경우 res = await getAppliedCampaignDetail(campaignId); } else { - // 보낸/받은 제안 API 호출 + // 제안받은/보낸 캠페인일 경우 res = await getProposalDetail(campaignId); } + + console.log("실제 받아온 데이터 구조:", res); // 여기서 brandId가 어떤 키로 들어오는지 꼭 확인하세요! setData(res); - if (res.brandId) { - const brandResult = await getBrandSummary(Number(res.brandId)); + // brandId 추출 로직 (우선순위 부여) + let targetId: number | undefined; + + if ('brandId' in res && res.brandId) { + targetId = Number(res.brandId); + } else if ('brand' in res && res.brand && typeof res.brand === 'object' && 'id' in res.brand) { + // 만약 brand: { id: 123 } 구조로 올 경우 + targetId = Number((res.brand as { id: number }).id); + } + + if (targetId) { + const brandResult = await getBrandSummary(targetId); setBrand(brandResult); + } else { + console.warn("현재 데이터에서 brandId를 추출할 수 없습니다."); } } catch (err) { - console.error("데이터 호출 에러:", err); + console.error("데이터 로딩 중 에러:", err); } }; @@ -96,11 +110,11 @@ export default function CampaignContent() { diff --git a/app/routes/business/proposal/application-content.tsx b/app/routes/business/proposal/application-content.tsx index 4456cfb..8846c96 100644 --- a/app/routes/business/proposal/application-content.tsx +++ b/app/routes/business/proposal/application-content.tsx @@ -165,7 +165,7 @@ export default function ApplicationContent() { profile
- {profileCard ? `@${profileCard.snsAccount}` : `@${data.creatorId || "ivveeee"}`} + {profileCard ? `@${profileCard.snsAccount}` : `@${data.creatorId}`} arrow diff --git a/app/routes/campaign-detail/campaign-detail.tsx b/app/routes/campaign-detail/campaign-detail.tsx index fcf75b6..56754f1 100644 --- a/app/routes/campaign-detail/campaign-detail.tsx +++ b/app/routes/campaign-detail/campaign-detail.tsx @@ -10,6 +10,8 @@ import { tokenStorage } from "../../lib/token"; import { apiClient } from "../../api/axios"; import { useCampaignProposalStore } from "../../stores/campaign-proposal"; +import adCampaignImage from "../../assets/ad/ad-realmatch-detail-campagin.png"; + import type { BrandDetailData } from "../brand-detail/types"; import type { CampaignDetail, @@ -53,7 +55,7 @@ const getNumberField = ( const rec = obj as Record; for (const k of keys) { const v = rec[k]; - if (typeof v === "number" && Number.isFinite(v) && v > 0) return v; + if (typeof v === "number" && Number.isFinite(v) && v >= 0) return v; } return null; }; @@ -76,6 +78,49 @@ const getBrandIdFromOngoing = (c: OngoingCampaign): number | null => getNumberField(c, ["brandId", "brand_id"]) ?? getNestedNumberField(c, "brand", ["brandId", "id"]); +const AD_CAMPAIGN: CampaignDetail = { + campaignId: 0, + title: "'리얼이 캐릭터 크림' 론칭 리뷰", + description: + "'리얼이 캐릭터 크림'\n겟레디윗미 영상에서 자연스럽게 노출", + imageUrl: adCampaignImage, + category: "BEAUTY", + preferredSkills: + "인스타 뷰티 분야 크리에이터 우대\n1~5만 마이크로 크리에이터 우대", + schedule: + "모집 : 2025년 1월 5~10일\n피드백 : 2025년 1월 17일\n업로드 : 2025년 1월 20~22일", + videoSpec: "개수: 영상 1개 | 길이: 30초~1분", + product: "리얼이 캐릭터 크림 1개", + rewardAmount: 200000, + startDate: "2025-01-05", + endDate: "2025-01-22", + recruitStartDate: "2025-01-05", + recruitEndDate: "2025-01-10", + dday: 0, + quota: 10, + contentTags: { + viewerGenders: [], + viewerAges: [], + avgVideoLengths: [], + avgVideoViews: [], + formats: [{ id: 3, name: "인스타 릴스" }], + categories: [ + { id: 6, name: "리뷰" }, + { id: 7, name: "겟레디윗미" }, + ], + tones: [ + { id: 16, name: "일상적인" }, + { id: 17, name: "수다적인" }, + ], + usageRanges: [ + { id: 24, name: "크리에이터 1차활용" }, + { id: 25, name: "브랜드 2차활용" }, + ], + involvements: [{ id: 20, name: "가이드만 제공" }], + }, + like: false, +}; + export default function CampaignDetailContent({ brandData, campaignId, @@ -92,7 +137,11 @@ export default function CampaignDetailContent({ const [isCampaignLiked, setIsCampaignLiked] = useState(false); - const [campaign, setCampaign] = useState(null); + const isAdCampaign = campaignId === 0; + + const [campaign, setCampaign] = useState( + isAdCampaign ? AD_CAMPAIGN : null, + ); const [campaignError, setCampaignError] = useState(null); const [ongoingCampaigns, setOngoingCampaigns] = useState( @@ -106,6 +155,12 @@ export default function CampaignDetailContent({ const ongoingLikeInFlight = useRef>(new Set()); useEffect(() => { + if (isAdCampaign) { + setCampaign(AD_CAMPAIGN); + setIsCampaignLiked(false); + return; + } + let alive = true; (async () => { @@ -137,7 +192,7 @@ export default function CampaignDetailContent({ return () => { alive = false; }; - }, [campaignId]); + }, [campaignId, isAdCampaign]); const detailRows = useMemo(() => { if (!campaign) return []; @@ -192,7 +247,7 @@ export default function CampaignDetailContent({ } const campaignIdNum = Number(campaignId); - if (!Number.isFinite(campaignIdNum) || campaignIdNum <= 0) return; + if (!Number.isFinite(campaignIdNum) || campaignIdNum < 0) return; const prev = isCampaignLiked; @@ -217,7 +272,7 @@ export default function CampaignDetailContent({ } const clickedId = Number(id); - if (!Number.isFinite(clickedId) || clickedId <= 0) return; + if (!Number.isFinite(clickedId) || clickedId < 0) return; const currentItem = ongoingCampaigns.find((c) => { const cid = getCampaignIdFromOngoing(c); @@ -280,7 +335,7 @@ export default function CampaignDetailContent({ if (!campaign) return; const brandIdNum = Number(brandData.id); - if (!Number.isFinite(brandIdNum) || brandIdNum <= 0) return; + if (!Number.isFinite(brandIdNum) || brandIdNum < 0) return; const domainParam = searchParams.get("domain"); const domain = @@ -401,8 +456,9 @@ export default function CampaignDetailContent({
diff --git a/app/routes/campaign-detail/components/CampaignActionBar.tsx b/app/routes/campaign-detail/components/CampaignActionBar.tsx index 3371a32..a9b9cbf 100644 --- a/app/routes/campaign-detail/components/CampaignActionBar.tsx +++ b/app/routes/campaign-detail/components/CampaignActionBar.tsx @@ -14,7 +14,7 @@ export default function CampaignActionBar({ onToggleHeart, }: Props) { return ( - +