Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 11 additions & 1 deletion src/routes/_main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
import BottomTab from "./_main/components/BottomTab";
import { useState } from "react";
import { LayoutContext } from "./_main/layout-context";
import Logo from "../assets/logo/RealMatchLogo_ex.svg";
import { useAuthStore } from "../stores/auth-store";

export const Route = createFileRoute("/_main")({
beforeLoad: () => {
// 처음 진입 시 로그인 페이지로
const { me } = useAuthStore.getState();
if (!me) {
throw redirect({
to: "/login",
});
}
},
component: MainLayout,
});

Expand Down
179 changes: 123 additions & 56 deletions src/routes/_main/_business/calendar/calendar-content.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,138 @@
import { useState } from "react";
import Tabs from "../../../../components/common/Tabs";


import FilterBottomSheet from "../components/FilterBottomSheet";
import WeeklyCalendar from "../components/WeeklyCalendar";
import MonthlyCalendar from "../components/MonthlyCalendar";
import CampaignCard from "../components/CampaignCard";
import SectionTitle from "../components/SectionTitle";
import MatchingCard from "../components/MatchingCard";
import MatchingTabSection from "../components/MatchingTabSection";
import dropdownIcon from "../../../../assets/arrow-down.svg";
import EmptyState from "../components/EmptyState";

export default function CalendarContent() {
const [mainTab, setMainTab] = useState<'collaboration' | 'matching'>('collaboration');
const [activeTab, setActiveTab] = useState<'thisMonth' | 'today'>('thisMonth');

return (
<div className="flex flex-col w-full min-h-screen bg-bluegray-1">
const [mainTab, setMainTab] = useState<"collaboration" | "matching">("collaboration");
const [activeTab, setActiveTab] = useState<"thisMonth" | "today">("thisMonth");
const [matchingSubTab, setMatchingSubTab] = useState<"sent" | "received">("sent");
const [isFilterOpen, setIsFilterOpen] = useState(false);
const [activeFilter, setActiveFilter] = useState("전체");

const hasData = true;

{/* 1. 상단 협업/매칭 현황 탭 */}
<Tabs
tabs={[
{ label: '협업 현황', value: 'collaboration' },
{ label: '매칭 현황', value: 'matching' }
]}
activeTab={mainTab}
onTabChange={(val: string) => setMainTab(val as 'collaboration' | 'matching')}
/>
return (
<div className="flex flex-col w-full min-h-screen bg-bluegray-1">
{/* 탭 네비게이션 */}
<div className="flex w-full bg-bg-w border-b border-text-gray5">
<button
onClick={() => setMainTab("collaboration")}
className={`flex-1 py-4 text-[16px] font-bold relative transition-colors ${mainTab === "collaboration" ? "text-core-1" : "text-text-gray3"
}`}
>
협업 현황
{mainTab === "collaboration" && (
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[120px] h-[2px] bg-core-1" />
)}
</button>
<button
onClick={() => setMainTab("matching")}
className={`flex-1 py-4 text-[16px] font-bold relative transition-colors ${mainTab === "matching" ? "text-core-1" : "text-text-gray3"
}`}
>
매칭 현황
{mainTab === "matching" && (
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[120px] h-[2px] bg-core-1" />
)}
</button>
</div>

<main className="flex flex-col gap-6 px-4 py-6">
{/* 진행 중인 협업 섹션 */}
<section className="flex flex-col gap-3">
<SectionTitle title="진행 중인 협업" />
<p className="text-title1 text-text-black font-bold">이번주 일정</p>
<WeeklyCalendar />
</section>
<main className="flex flex-col flex-1">
{mainTab === "collaboration" ? (
/* [A] 협업 현황 */
<div className="flex flex-col gap-6 px-4 py-6">
<section className="flex flex-col gap-3">
<SectionTitle title="진행 중인 협업" />
<p className="text-title1 font-bold text-text-black">이번주 일정</p>
<WeeklyCalendar />
</section>
<section className="flex flex-col gap-3">
<p className="text-title1 font-bold text-text-black">이번달 일정</p>
<MonthlyCalendar />
</section>
<section className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<button
onClick={() => setActiveTab("thisMonth")}
className={`text-[14px] font-semibold ${activeTab === "thisMonth" ? "text-core-1" : "text-text-gray3"}`}
>
이번달
</button>
<span className="text-[14px] text-text-gray3">|</span>
<button
onClick={() => setActiveTab("today")}
className={`text-[14px] font-semibold ${activeTab === "today" ? "text-core-1" : "text-text-gray3"}`}
>
오늘
</button>
</div>
<div className="flex flex-col gap-4">
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="12.21" endDate="12.24" />
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="12.23" endDate="12.26" />
</div>
</section>
</div>
) : (
/* [B] 매칭 현황 */
<div className="flex flex-col flex-1">
<MatchingTabSection subTab={matchingSubTab} setSubTab={setMatchingSubTab} />

{/* 이번달 일정 섹션 */}
<section className="flex flex-col gap-3">
<p className="text-title1 text-text-black font-bold">이번달 일정</p>
<MonthlyCalendar />
</section>
{hasData ? (
<div className="flex flex-col gap-4 px-4 flex-1">
<div className="flex items-center justify-between mb-1">
<h2 className="text-title1 font-bold text-text-black">매칭 현황</h2>
<button
onClick={() => setIsFilterOpen(true)}
className="flex items-center gap-1 px-3 py-1 border border-text-gray4 rounded-full bg-white active:bg-bluegray-2 transition-colors"
>
<span className="text-callout1 text-text-gray2">{activeFilter}</span>
<img src={dropdownIcon} alt="open filter" />
</button>
</div>
<div className="flex flex-col gap-4">
{matchingSubTab === "sent" ? (
<>
<MatchingCard brand="라운드랩" status="매칭" date="12.23.25" actionLabel="제안 보기" />
<MatchingCard brand="비플레인" status="검토 중" date="12.23.25" actionLabel="제안 보기" />
<MatchingCard brand="땡큐파머" status="검토 중" date="12.23.25" actionLabel="제안 보기" />
<MatchingCard brand="이즈트리" status="거절" date="12.23.25" actionLabel="거절 사유 보기" />
</>
) : (
<>
<MatchingCard brand="라운드랩" status="매칭" date="12.23.25" actionLabel="제안 보기" />
<MatchingCard brand="비플레인" status="검토 중" date="12.23.25" actionLabel="제안 보기" />
<MatchingCard brand="그레이스유" status="검토 중" date="12.23.25" actionLabel="제안 보기" />
<MatchingCard brand="이즈트리" status="거절" date="12.23.25" actionLabel="거절 사유 보기" />
</>
)}
</div>

{/* 협업 리스트 섹션 */}
<section className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<button
onClick={() => setActiveTab('thisMonth')}
className="text-[14px] font-semibold"
style={{ color: activeTab === 'thisMonth' ? '#6666E5' : '#9B9BA1' }}
>
이번달
</button>
<span className="text-[14px] text-[#9B9BA1]">|</span>
<button
onClick={() => setActiveTab('today')}
className="text-[14px] font-semibold"
style={{ color: activeTab === 'today' ? '#6666E5' : '#9B9BA1' }}
>
오늘
</button>
</div>
</div>
) : (
<div className="flex-1 flex flex-col items-center justify-center">
<EmptyState
message={`${(matchingSubTab as string) === "sent" ? "보낸" : "받은"} 제안이 없어요`}
/>
</div>
)}

<div className="flex flex-col gap-4">
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="12.21" endDate="12.24" />
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="12.23" endDate="12.26" />
<CampaignCard brand="비플레인" title="비플레인 클렌징 및 세럼 리뷰" startDate="01.10" endDate="01.15" />
</div>
</section>
</main>
</div>
);
<FilterBottomSheet
isOpen={isFilterOpen}
onClose={() => setIsFilterOpen(false)}
onApply={(filter) => setActiveFilter(filter)}
currentFilter={activeFilter}
/>
</div>
)}
</main>
</div>
);
}
2 changes: 1 addition & 1 deletion src/routes/_main/_business/components/MatchingCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import chatIcon from "../../../../assets/chat-icon2.svg";
import searchIcon from "../../../../assets/search.svg";
import searchIcon from "../../../../assets/icon/search.svg";

interface MatchingCardProps {
brand: string;
Expand Down
142 changes: 142 additions & 0 deletions src/routes/_main/_business/proposal/received-proposal-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useState } from "react";

import Header from "../../../../components/layout/Header";
import CampaignBrandCard from "../components/CampaignBrandCard";
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 profileIcon from "../../../../assets/logo/mini-logo.svg";

export default function ReceivedProposalContent() {
const [isContentOpen, setIsContentOpen] = useState(false);

return (
<div className="flex flex-col w-full min-h-screen bg-bg-w font-pretendard">
<Header title="제안 보기" />

<main className="flex flex-col pb-24">
{/* 1. 상단 섹션: 브랜드 카드 */}
<div className="px-4 py-6">
<CampaignBrandCard
showChatSection={false}
statusText="검토 중"
/>
<div className="mt-6">
<h2 className="text-title1 text-text-black">브랜드 제안 캠페인</h2>
</div>
</div>

{/* 2. 상세 정보 섹션 */}
<div className="bg-bluegray-1 px-4 py-8 flex flex-col gap-6">
{/* 캠페인명 */}
<CampaignInfoGroup label="캠페인명">
<div className="w-full p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1 text-text-gray1">
비플레인 선크림 리뷰 콘텐츠
</div>
</CampaignInfoGroup>

{/* 캠페인 내용 */}
<CampaignInfoGroup
label="캠페인 내용"
right={
<button onClick={() => setIsContentOpen(prev => !prev)}>
<img
src={isContentOpen ? dropupIcon : dropdownIcon}
alt="toggle"
className="w-5 h-5"
/>
</button>
}
>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<p className="text-caption2 text-text-gray3">설명</p>
<div className="w-full p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1 leading-relaxed text-text-gray1">
안녕하세요 비플레인 입니다! <br />
크리에이터님과 이미지와 비플레인이 추구하는 가치가 잘 맞닿아 있다고 생각되어 협찬을 제안드립니다.
</div>
</div>

{/* 아코디언 상세 내용 */}
{isContentOpen && (
<div className="grid grid-cols-2 gap-4 animate-slide-up">
<div className="col-span-2">
<ContentItem label="형식" value="인스타그램 릴스" />
</div>
<ContentItem label="종류" value="겟레디윗미, 스토리" />
<ContentItem label="톤" value="수다적인, 일상적인" />
<ContentItem label="관여도" value="가이드만 제공" />
<ContentItem label="활용 범위" value="크리에이터 1차 활용" />
</div>
)}
</div>
</CampaignInfoGroup>

{/* 협찬품 / 원고료 */}
<div className="grid grid-cols-2 gap-4">
<CampaignInfoGroup label="협찬품">
<div className="w-full p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1 flex justify-between items-center text-text-gray1">
<span className="truncate">글로우 크림 1개</span>
<img src={arrowRightIcon} alt="arrow" className="w-4 h-4 opacity-30" />
</div>
</CampaignInfoGroup>

<CampaignInfoGroup label="원고료">
<div className="w-full p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1 flex justify-between items-center text-text-gray1">
200,000 <span className="text-text-black ml-1">원</span>
</div>
</CampaignInfoGroup>
</div>

{/* 제작 기간 */}
<CampaignInfoGroup label="제작 기간">
<div className="flex items-center gap-2 text-text-gray1">
<div className="flex-1 p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1">
2025년 1월 20일
</div>
<span className="text-text-gray3">~</span>
<div className="flex-1 p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1">
2025년 1월 30일
</div>
</div>
</CampaignInfoGroup>
</div>

{/* 3. 하단 액션 버튼 (거절하기 / 제안 수락하기) */}
<div className="px-2 py-5 flex gap-3 bg-bg-w">
<button className="flex-1 w-[102px] py-[10px] px-[16px] bg-bg-w border border-core-3 rounded-xl text-core-1 text-title3 hover:bg-bluegray-1 transition-colors">
거절하기
</button>
<button
className="flex-[2.5] py-4 bg-core-1 rounded-xl text-bg-w text-title3 flex items-center justify-center gap-2 hover:bg-core-2 transition-colors shadow-sm"
>
{/* 미니 로고 아이콘 */}
<div className="flex items-center justify-center w-6 h-6">
<img
src={profileIcon}
alt="mini-logo"
className="w-full h-full object-contain invert brightness-0"
/>
</div>
<span>제안 수락하기</span>
</button>
</div>
</main>
</div>
);
}

// 기존 ContentItem 재사용
function ContentItem({ label, value }: { label: string; value: string }) {
return (
<div className="flex flex-col gap-1">
<p className="text-caption2 text-text-gray3">{label}</p>
<div className="p-4 bg-bg-w border border-text-gray5 rounded-xl text-body1 flex justify-between items-center text-text-gray1">
<span className="truncate">{value}</span>
<img src={arrowRightIcon} alt="arrow" className="w-4 h-4 opacity-30" />
</div>
</div>
);
}
Loading