Skip to content
2 changes: 1 addition & 1 deletion app/routes/_main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function MainLayout() {

useEffect(() => {
mainRef.current?.scrollTo(0, 0);
}, [location.pathname]);
}, [location.pathname, location.search]);

useEffect(() => {
const accessToken = tokenStorage.getAccessToken();
Expand Down
4 changes: 2 additions & 2 deletions app/routes/brand-detail/components/BrandHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export default function BrandHero({
<img src={goBackIcon} alt="뒤로가기" className="h-6 w-6" />
</button>

<div className="absolute -bottom-8 left-5 z-10">
<div className="grid h-16 w-16 place-items-center overflow-hidden rounded-2xl border border-[#E6E6F3] bg-white shadow-[0_8px_18px_rgba(0,0,0,0.12)]">
<div className="absolute -bottom-8 left-3 z-10">
<div className="grid h-18 w-18 place-items-center overflow-hidden rounded-[20px] border border-core-2 bg-white">
{logoImageUrl ? (
<img
src={logoImageUrl}
Expand Down
4 changes: 2 additions & 2 deletions app/routes/brand-detail/components/BrandInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function BrandInfo({
description,
}: Props) {
return (
<div className="pt-10">
<div className="pt-11.5">
<div className="flex items-center justify-between">
<div className="text-title text-text-black">{name}</div>

Expand All @@ -25,7 +25,7 @@ export default function BrandInfo({
</div>
</div>

<div className=" text-title3 text-text-gray3">
<div className="my-1 text-title3 text-text-gray3">
{hashtags
.map((tag) => (tag.startsWith("#") ? tag : `#${tag}`))
.join(" ")}
Expand Down
44 changes: 14 additions & 30 deletions app/routes/brand-detail/components/OngoingCampaignSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,44 @@ import type { BrandOngoingCampaign } from "../types";

type Props = {
campaigns: BrandOngoingCampaign[];
brandLogoUrl?: string;
onMore?: () => void;
onCampaignClick?: (c: BrandOngoingCampaign) => void;
onLikeToggle?: (id: string) => void;
};

export default function OngoingCampaignSection({
campaigns,
brandLogoUrl,
onMore,
onCampaignClick,
onLikeToggle,
}: Props) {
const isEmpty = campaigns.length === 0;

return (
<section className="py-9">
<section className="pt-9 pb-9 px-2">
<div className="flex items-center justify-between">
<div className="text-title1 text-text-black">진행 중인 캠페인</div>
<div className="text-title1 text-text-black">진행 중인 다른 캠페인</div>

{onMore ? (
{!isEmpty && onMore ? (
<button
type="button"
onClick={onMore}
className="mt-0.5 grid h-6 w-6 place-items-center text-[var(--color-text-gray3)]"
className="grid h-8 w-8 place-items-center rounded-full text-text-gray2"
aria-label="more"
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 18L15 12L9 6"
stroke="#9B9BA1"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<span className="text-[18px] leading-none">›</span>
</button>
) : (
<div className="h-6 w-6" />
)}
) : null}
</div>

{isEmpty ? (
<div className="mt-2 inline-flex w-full flex-col items-start gap-2 bg-white">
<div className="flex h-[126px] w-full flex-col items-start justify-center">
<div className="flex w-full flex-col items-start gap-[14px] pb-[60px] pt-[60px]">
<div className="flex w-full flex-col items-center justify-center gap-[10px]">
<div className=" text-center text-callout1 text-text-gray2">
진행 중인 캠페인이 없어요
</div>
<div className="flex h-[126px] w-full flex-col justify-center items-start">
<div className="flex w-full flex-col items-start gap-[14px] pt-[50px] pb-[60px]">
<div className="flex w-full flex-col items-center justify-center gap-[10px]">
<div className="w-[113px] text-center text-[12px] font-medium leading-[16px] text-text-gray2">
진행 중인 다른 캠페인이 없어요
</div>
</div>
</div>
Expand All @@ -80,7 +64,7 @@ export default function OngoingCampaignSection({
className="text-left"
>
<CampaignCard
item={toCampaignItem(c)}
item={toCampaignItem(c, brandLogoUrl)}
onClick={() => onCampaignClick?.(c)}
onLikeToggle={onLikeToggle}
/>
Expand Down
4 changes: 2 additions & 2 deletions app/routes/brand-detail/components/toCampaignItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { CampaignItem } from "../../home/types";
import type { BrandOngoingCampaign } from "../types";

export function toCampaignItem(campaign: BrandOngoingCampaign): CampaignItem {
export function toCampaignItem(campaign: BrandOngoingCampaign, brandLogoUrl?: string): CampaignItem {

const ddayLabel = campaign.dday === 0 ? "D-DAY" : `D-${campaign.dday}`;

const result: CampaignItem = {
id: String(campaign.campaignId),
brandName: campaign.brandName || "",
logoUrl: campaign.imageUrl || undefined,
logoUrl: brandLogoUrl || campaign.imageUrl || undefined,
progressText: String(campaign.recruitQuota || 0),
ddayLabel,
descText: campaign.title || "",
Expand Down
127 changes: 57 additions & 70 deletions app/routes/campaign-detail/campaign-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ type OngoingCampaign = NonNullable<BrandDetailData["ongoingCampaigns"]>[number];
const fmtMoney = (n?: number) =>
Number.isFinite(n) ? `${Number(n).toLocaleString()}원` : "-";

const formatDateOnly = (iso?: string) => {
if (!iso) return "-";
return iso.split("T")[0];
};

const joinTagNames = (items?: { name: string }[]) =>
(items ?? []).map((x) => x.name).filter(Boolean);

Expand Down Expand Up @@ -90,6 +85,10 @@ export default function CampaignDetailContent({
const [searchParams] = useSearchParams();
const setProposalData = useCampaignProposalStore((s) => s.setProposalData);

useEffect(() => {
window.scrollTo(0, 0);
}, [campaignId]);

const heroUrl = brandData.brandImages?.[0] ?? brandData.heroImageUrl;

const [isCampaignLiked, setIsCampaignLiked] = useState(false);
Expand Down Expand Up @@ -127,16 +126,8 @@ export default function CampaignDetailContent({
}

setCampaign(res.data.result);
setIsCampaignLiked(res.data.result.like ?? false);
setCampaignError(null);

const liked = (() => {
const r: unknown = res.data.result;
if (!r || typeof r !== "object") return false;
const rec = r as Record<string, unknown>;
return rec["isLiked"] === true;
})();

setIsCampaignLiked(liked);
} catch {
if (!alive) return;
setCampaignError("캠페인 정보를 불러오지 못했어요.");
Expand All @@ -155,14 +146,14 @@ export default function CampaignDetailContent({
{ label: "모집인원", value: `${campaign.quota}명` },
{ label: "우대사항", value: campaign.preferredSkills || "-" },
{ label: "제품협찬", value: campaign.product || "-" },
{ label: "원고료", value: fmtMoney(campaign.rewardAmount) },
{ label: "원고료", value: `${fmtMoney(campaign.rewardAmount)} (VAT포함)` },
{ label: "일정", value: campaign.schedule || "-" },
{
label: "모집기간",
value: `${formatDateOnly(campaign.recruitStartDate)} ~ ${formatDateOnly(
campaign.recruitEndDate,
)}`,
},
// {
// label: "모집기간",
// value: `${formatDateOnly(campaign.recruitStartDate)} ~ ${formatDateOnly(
// campaign.recruitEndDate,
// )}`,
// },
];
}, [campaign]);

Expand All @@ -174,10 +165,10 @@ export default function CampaignDetailContent({
{ label: "설명", value: campaign.description || "-" },
{ label: "개수 및 길이", value: campaign.videoSpec || "-" },
{ label: "콘텐츠 형식", chips: joinTagNames(tags?.formats) },
{ label: "카테고리", chips: joinTagNames(tags?.categories) },
{ label: "톤", chips: joinTagNames(tags?.tones) },
{ label: "참여도", chips: joinTagNames(tags?.involvements) },
{ label: "사용 범위", chips: joinTagNames(tags?.usageRanges) },
{ label: "콘텐츠 종류", chips: joinTagNames(tags?.categories) },
{ label: "컨톤체 톤", chips: joinTagNames(tags?.tones) },
{ label: "콘텐츠 활용범위", chips: joinTagNames(tags?.usageRanges) },
{ label: "콘텐츠 관려도", chips: joinTagNames(tags?.involvements) },
];
}, [campaign]);

Expand Down Expand Up @@ -395,29 +386,29 @@ export default function CampaignDetailContent({
const campaignImage = campaign.imageUrl ?? heroUrl;

return (
<div className="w-full bg-bg-w">
<div className="w-full overflow-x-hidden bg-bg-w">
<div className="w-full bg-bg-w">
<BrandHero
heroImageUrl={heroUrl}
logoImageUrl={brandData.logoImageUrl}
logoText={brandData.logoText ?? ""}
/>

<div className="px-5">
<div className="px-3">
<BrandInfo
name={brandData.name}
matchRate={brandData.matchRate}
hashtags={(brandData.hashtags ?? []).slice(0, 2)}
description={brandData.description}
/>

<div className="my-3.5 flex items-center gap-2 text-title1 text-core-1 font-[16px]">
<div className="my-3 flex items-center gap-2 text-title1 text-core-1 font-[16px]">
<MetaItem
icon={
<img
src={informationIconUrl}
alt=""
className="block h-4 w-4 select-none"
className="block h-4 w- select-none"
draggable={false}
/>
}
Expand All @@ -439,10 +430,10 @@ export default function CampaignDetailContent({
onToggleHeart={handleToggleHeart}
/>

<div className="my-4 h-px w-full bg-core-2" />
<div className="my-3 h-px w-full bg-core-2" />

<section>
<div className="my-6 overflow-hidden bg-bluegray-1">
<div className="my-6 mx-2 overflow-hidden bg-bluegray-1">
<img
src={campaignImage}
alt="campaign"
Expand All @@ -456,9 +447,9 @@ export default function CampaignDetailContent({
</div>
</section>

<section className="pt-6 pb-6.5">
<div className="text-title1 text-text-black">상세 설명</div>
<div className="mt-3 space-y-2.5">
<section className="py-4 mx-4">
<div className="text-title1 text-text-black my-3">상세 설명</div>
<div className="mt-2.5 space-y-3">
{detailRows.map((row) => (
<DetailRow
key={row.label}
Expand All @@ -469,45 +460,24 @@ export default function CampaignDetailContent({
</div>
</section>

<div className="mx-auto w-3/4 border-t border-core-2" />
<div className="my-2 mx-auto w-3/4 border-t border-core-2" />

<section className="py-6">
<section className="py-4 mx-4">
<div className="text-title1 text-text-black">콘텐츠</div>

<div className="mt-3 space-y-2.5 text-callout1 text-text-gray3">
<div className="mt-2.5 space-y-3">
{contentRows.map((row) => (
<div key={row.label} className="flex">
<div className="w-21 shrink-0 text-title3 text-text-gray3">
{row.label}
</div>

<div className="flex-1 gap-[14px]">
{"value" in row && row.value ? (
<div className="whitespace-pre-line text-title3 text-text-gray1">
{row.value}
</div>
) : (
<div className="flex flex-wrap gap-1.5">
{(row.chips ?? []).map((c) => (
<span
key={c}
className="inline-flex items-center rounded-full border border-core-2 bg-bg-w px-2 py-1 text-callout1 text-text-gray1"
>
{c}
</span>
))}
{(!row.chips || row.chips.length === 0) && (
<span>-</span>
)}
</div>
)}
</div>
</div>
<DetailRow
key={row.label}
label={row.label}
value={row.value}
chips={row.chips}
/>
))}
</div>
</section>

<div className="mt-6 mb-9">
<div className="mt-6 mb-9 mx-2">
<button
type="button"
onClick={handleApply}
Expand All @@ -527,6 +497,7 @@ export default function CampaignDetailContent({

<OngoingCampaignSection
campaigns={ongoingCampaigns}
brandLogoUrl={brandData.logoImageUrl}
onMore={() => {}}
onCampaignClick={goOngoingCampaignDetail}
onLikeToggle={handleOngoingLikeToggle}
Expand All @@ -537,20 +508,36 @@ export default function CampaignDetailContent({
);
}

function DetailRow({ label, value }: { label: string; value: string }) {
function DetailRow({ label, value, chips }: { label: string; value?: string; chips?: string[] }) {
return (
<div className="flex gap-4">
<div className="w-21 shrink-0 text-title3 text-text-gray3">{label}</div>
<div className="whitespace-pre-line text-title3 text-text-gray1">
{value}
<div className="w-23 shrink-0 text-title3 text-text-gray3">{label}</div>
<div className="flex-1">
{value ? (
<div className="whitespace-pre-line text-title3 text-text-gray1">
{value}
</div>
) : (
<div className="flex flex-wrap gap-1.5">
{(chips ?? []).map((c) => (
<span
key={c}
className="inline-flex items-center rounded-full border border-core-2 bg-bg-w px-2 py-1 text-callout1 text-text-gray1"
>
{c}
</span>
))}
{(!chips || chips.length === 0) && <span className="text-title3 text-text-gray1">-</span>}
</div>
)}
</div>
</div>
);
}

function MetaItem({ icon, text }: { icon?: React.ReactNode; text: string }) {
return (
<span className="flex items-center gap-2">
<span className="flex items-center gap-1">
{icon ? (
<span className="flex h-5 w-5 items-center justify-center">{icon}</span>
) : null}
Expand Down
Loading
Loading