diff --git a/src/App.tsx b/src/App.tsx
index ac771baf..011c3308 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -17,6 +17,7 @@ import { ProfileSettings } from './components/mypage/profile-settings/ProfileSet
import IdeaAnalysis from './components/mypage/idea-analysis/IdeaAnalysis'
import OngoingProject from './components/mypage/ongoing-project/OngoingProject'
import ProfileAnalysis from './components/mypage/profile-analysis/ProfileAnalysis'
+import { MatchingStatus } from './components/mypage/matching-status/MatchingStatus'
import IdeaAnalyzePage from './pages/IdeaAnalyzePage'
import AnalyzeReportPage from './pages/AnalyzeReportPage'
import RecruitingProjectsPage from './pages/RecruitingProjectsPage'
@@ -95,6 +96,10 @@ const router = createBrowserRouter([
path: 'ongoing',
element: ,
},
+ {
+ path: 'matching',
+ element: ,
+ },
],
},
{
diff --git a/src/assets/icons/mypage/chat.svg b/src/assets/icons/mypage/chat.svg
new file mode 100644
index 00000000..8df6b868
--- /dev/null
+++ b/src/assets/icons/mypage/chat.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/components/common/CTAModal.tsx b/src/components/common/CTAModal.tsx
index d81e7a0e..cea6644b 100644
--- a/src/components/common/CTAModal.tsx
+++ b/src/components/common/CTAModal.tsx
@@ -1,10 +1,14 @@
import Button from './Button'
-interface ICTAModal {
+interface CTAModalProps {
message: string
subMessage?: string
isMessageHighlight?: boolean
fixedHeight?: boolean
+ // 단일 버튼 모드
+ buttonMsg?: string
+ onButtonClick?: () => void
+ // 이중 버튼 모드
leftButtonMsg?: string
rightButtonMsg?: string
onLeftClick?: () => void
@@ -16,11 +20,13 @@ const CTAModal = ({
subMessage = '',
isMessageHighlight = false,
fixedHeight = false,
+ buttonMsg,
+ onButtonClick,
leftButtonMsg,
rightButtonMsg,
onLeftClick,
onRightClick,
-}: ICTAModal) => {
+}: CTAModalProps) => {
// {텍스트} 형식 부분만 보라색으로 하이라이팅
const parseMessage = (text: string) => {
const parts = text.split(/(\{[^}]+\})/g)
@@ -37,34 +43,49 @@ const CTAModal = ({
})
}
+ // 단일 버튼 모드인지 확인
+ const isSingleButtonMode = !!buttonMsg && !leftButtonMsg && !rightButtonMsg
+
return (
{/* 타이틀 + 설명글 */}
{parseMessage(message)}
- {subMessage ?
{subMessage}
: ''}
+
+ {subMessage}
+
- {/* 선택용 버튼 2개 */}
-
- {leftButtonMsg && (
-
+ )}
)
diff --git a/src/components/common/ConfirmModal.tsx b/src/components/common/ConfirmModal.tsx
deleted file mode 100644
index bab59a63..00000000
--- a/src/components/common/ConfirmModal.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { useEffect } from 'react'
-import XIcon from '@/assets/icons/common/X.svg?react'
-
-interface ConfirmModalProps {
- isOpen: boolean
- onClose: () => void
- title: string
- description?: string
- cancelText: string
- confirmText: string
- onCancel: () => void
- onConfirm: () => void
-}
-
-const ConfirmModal = ({
- isOpen,
- onClose,
- title,
- description,
- cancelText,
- confirmText,
- onCancel,
- onConfirm,
-}: ConfirmModalProps) => {
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === 'Escape' && isOpen) {
- onClose()
- }
- }
-
- document.addEventListener('keydown', handleKeyDown)
- return () => document.removeEventListener('keydown', handleKeyDown)
- }, [isOpen, onClose])
-
- // 모달이 열리면 스크롤 방지
- useEffect(() => {
- if (isOpen) {
- document.body.style.overflow = 'hidden'
- } else {
- document.body.style.overflow = 'unset'
- }
-
- return () => {
- document.body.style.overflow = 'unset'
- }
- }, [isOpen])
-
- if (!isOpen) return null
-
- return (
-
- {/* 배경 오버레이 */}
-
-
- {/* 모달 컨텐츠 */}
-
- {/* 닫기 버튼 */}
-
-
-
-
- {/* 타이틀 */}
-
{title}
-
- {/* 설명 */}
- {description &&
{description}
}
-
- {/* 버튼 영역 */}
-
-
- {cancelText}
-
-
- {confirmText}
-
-
-
-
- )
-}
-
-export default ConfirmModal
diff --git a/src/components/mypage/SegmentTabButton.tsx b/src/components/mypage/SegmentTabButton.tsx
new file mode 100644
index 00000000..c07baa2a
--- /dev/null
+++ b/src/components/mypage/SegmentTabButton.tsx
@@ -0,0 +1,42 @@
+import { cn } from '@/utils/cn'
+
+interface SegmentTabButtonProps {
+ /** 탭 텍스트 */
+ label: string
+ /** 카운트 (Count 타입일 때 표시) */
+ count?: number
+ /** 활성화 여부 */
+ isActive: boolean
+ /** 클릭 핸들러 */
+ onClick?: () => void
+ /** 추가 클래스명 */
+ className?: string
+}
+
+const SegmentTabButton = ({ label, count, isActive, onClick, className }: SegmentTabButtonProps) => {
+ return (
+
+
+ {label}
+ {count !== undefined && {count}}
+
+
+
+ )
+}
+
+export default SegmentTabButton
diff --git a/src/components/mypage/idea-analysis/IdeaAnalysis.tsx b/src/components/mypage/idea-analysis/IdeaAnalysis.tsx
index 58b69594..7d147b57 100644
--- a/src/components/mypage/idea-analysis/IdeaAnalysis.tsx
+++ b/src/components/mypage/idea-analysis/IdeaAnalysis.tsx
@@ -8,7 +8,7 @@ import Section02TeamComposition from './sections/Section02TeamComposition'
import Section03Improvements from './sections/Section03Improvements'
import Section04Roadmap from './sections/Section04Roadmap'
import type { IdeaAnalysisData } from '@/types/mypage/ideaAnalysis'
-import CTAModal from '../../common/CTAModal'
+import CTAModal from '@/components/common/CTAModal'
import { useNavigate } from 'react-router'
import { useCTAModal } from '@/stores/useCTAModal'
diff --git a/src/components/mypage/matching-status/MatchingNotice.tsx b/src/components/mypage/matching-status/MatchingNotice.tsx
new file mode 100644
index 00000000..e630085a
--- /dev/null
+++ b/src/components/mypage/matching-status/MatchingNotice.tsx
@@ -0,0 +1,64 @@
+import { cn } from '@/utils/cn'
+import type { NoticeItem } from '@/constants/matchingNotice'
+
+interface MatchingNoticeProps {
+ /** 유의사항 항목 목록 */
+ items: NoticeItem[]
+ /** 추가 클래스명 */
+ className?: string
+}
+
+const MatchingNotice = ({ items, className }: MatchingNoticeProps) => {
+ return (
+
+ {items.map((item, index) => (
+
+ {/* 규칙 메인 텍스트 */}
+
+ {item.number}.
+ {/* 텍스트 내부의 "/" 처리 */}
+ {item.text.split(/(\/)/).map((part, partIndex) => {
+ if (part === '/') {
+ return {part}
+ }
+ return {part}
+ })}
+
+
+ {/* 규칙 하위 설명 텍스트 */}
+ {item.subText && (
+
+ {item.subText.split('\n').map((line, lineIndex) => (
+
+ {line.split(/(\*\*.*?\*\*)/).map((segment, segIndex) => {
+ if (segment.startsWith('**') && segment.endsWith('**')) {
+ return (
+
+ {segment.slice(2, -2)}
+
+ )
+ }
+ return {segment}
+ })}
+
+ ))}
+
+ )}
+
+ ))}
+
+ )
+}
+
+export default MatchingNotice
diff --git a/src/components/mypage/matching-status/MatchingStatus.tsx b/src/components/mypage/matching-status/MatchingStatus.tsx
new file mode 100644
index 00000000..5029c0c2
--- /dev/null
+++ b/src/components/mypage/matching-status/MatchingStatus.tsx
@@ -0,0 +1,433 @@
+import { useState } from 'react'
+import { MyPageHeader } from '../MyPageHeader'
+import ProjectCard from './ProjectCard'
+import ProfileCard from './ProfileCard'
+import MatchingTimerCard from './MatchingTimerCard'
+import MatchingNotice from './MatchingNotice'
+import RoleTagChip from '@/components/mission-modal/RoleTagChip'
+import { RECEIVED_REQUEST_NOTICES, SENT_REQUEST_NOTICES } from '@/constants/matchingNotice'
+import CTAModal from '@/components/common/CTAModal'
+import SegmentTabButton from '../SegmentTabButton'
+
+type TabType = 'received' | 'sent'
+
+interface MatchingStatusProps {
+ receivedCount?: number
+ sentCount?: number
+}
+
+// 역할 이름을 roleId로 매핑
+const getRoleIdByName = (roleName: string): number => {
+ const roleMap: Record = {
+ PM: 1,
+ Design: 2,
+ Frontend: 3,
+ Backend: 4,
+ }
+ return roleMap[roleName] || 1
+}
+
+export const MatchingStatus = ({ receivedCount = 6, sentCount = 5 }: MatchingStatusProps) => {
+ const [activeTab, setActiveTab] = useState('received')
+ const [modalType, setModalType] = useState<'reject' | 'rejectSuccess' | 'accept' | 'acceptSuccess' | 'cancel' | 'cancelSuccess' | null>(null)
+
+ // 받은 요청 데이터 (임시)
+ const receivedProjects = [
+ {
+ id: 1,
+ projectName: 'MoneyLog',
+ category: '금융 · 핀테크',
+ description: '하루의 소비를 기록해, 나의 돈 흐름을 이해하는 금융 다이어리',
+ currentMembers: 4,
+ totalMembers: 10,
+ timerText: '09:58:29',
+ },
+ ]
+
+ const receivedTeamMembers = [
+ {
+ part: 'Design',
+ partColor: 'bg-roletag-pink',
+ members: [
+ {
+ id: 1,
+ nickname: '김넥트',
+ part: 'Design',
+ introduction: 'UX.UI 어쩌고 한줄 소개 ~~~~',
+ timerText: '01:27:00',
+ },
+ {
+ id: 2,
+ nickname: '윤다',
+ part: 'Design',
+ introduction: '인터렉티브 디자인 전공으로 인터렉션에 강합니다 !',
+ timerText: '07:14:00',
+ },
+ ],
+ },
+ {
+ part: 'Backend',
+ partColor: 'bg-roletag-blue',
+ members: [
+ {
+ id: 3,
+ nickname: '러핑',
+ part: 'Backend',
+ introduction: '프로필 소개 (첫 문장까지 미리보기됨)',
+ timerText: '04:08:00',
+ },
+ {
+ id: 4,
+ nickname: '리뮤딘',
+ part: 'Backend',
+ introduction: '프로필 소개 (첫 문장까지 미리보기됨)',
+ timerText: '06:32:00',
+ },
+ {
+ id: 5,
+ nickname: '이경',
+ part: 'Backend',
+ introduction: '프로필 소개 (첫 문장까지 미리보기됨)',
+ timerText: '07:54:00',
+ },
+ {
+ id: 6,
+ nickname: '루트',
+ part: 'Backend',
+ introduction: '프로필 소개 (첫 문장까지 미리보기됨)',
+ timerText: '00:00:00',
+ status: 'accepted' as const,
+ },
+ ],
+ },
+ ]
+
+ // 보낸 요청 데이터 (임시)
+ const sentProjects = [
+ {
+ id: 1,
+ projectName: 'NECT 웹사이트',
+ category: 'IT · 웹/모바일 서비스',
+ description: '크리에이터를 위한 사이드 프로젝트 매칭 & 협업 플랫폼',
+ currentMembers: 6,
+ totalMembers: 10,
+ timerText: '12:34:56',
+ },
+ ]
+
+ const sentTeamMembers = [
+ {
+ part: 'Frontend',
+ partColor: 'bg-roletag-green',
+ members: [
+ {
+ id: 1,
+ nickname: '김개발',
+ part: 'Frontend',
+ introduction: 'React/Next.js 개발 경험이 있습니다!',
+ timerText: '05:20:00',
+ },
+ {
+ id: 2,
+ nickname: '박프론트',
+ part: 'Frontend',
+ introduction: '프로필 소개 (첫 문장까지 미리보기됨)',
+ timerText: '08:15:30',
+ },
+ ],
+ },
+ {
+ part: 'PM',
+ partColor: 'bg-roletag-purple',
+ members: [
+ {
+ id: 3,
+ nickname: '최기획',
+ part: 'PM',
+ introduction: '프로젝트 관리 경험이 풍부합니다',
+ timerText: '03:45:00',
+ },
+ ],
+ },
+ ]
+
+ return (
+
+
+
+ {/* 전체 컨테이너 */}
+
+ {/* 탭 영역 */}
+
+ setActiveTab('received')}
+ />
+ setActiveTab('sent')}
+ />
+
+
+ {/* 탭 컨텐츠 */}
+
+ {activeTab === 'received' && (
+ <>
+ {/* 프로젝트 섹션 */}
+
+
+
+ {receivedProjects.map(project => (
+
+
+
setModalType('accept')}
+ onReject={() => setModalType('reject')}
+ />
+
+ ))}
+
+
+
+ {/* 넥트 팀원 섹션 */}
+
+
+
+ {receivedTeamMembers.map((partGroup, partIndex) => (
+
+
+
+ {partGroup.members.map(member => (
+
+
console.log(`${member.nickname}에게 메시지 보내기`)}
+ />
+ setModalType('accept')}
+ onReject={() => setModalType('reject')}
+ />
+
+ ))}
+
+
+ ))}
+
+
+
+ {/* 유의사항 섹션 */}
+
+ >
+ )}
+ {activeTab === 'sent' && (
+ <>
+ {/* 프로젝트 섹션 */}
+
+
+
+ {sentProjects.map(project => (
+
+
+
setModalType('cancel')}
+ />
+
+ ))}
+
+
+
+ {/* 넥트 팀원 섹션 */}
+
+
+
+ {sentTeamMembers.map((partGroup, partIndex) => (
+
+
+
+ {partGroup.members.map(member => (
+
+
console.log(`${member.nickname}에게 메시지 보내기`)}
+ />
+ setModalType('cancel')}
+ />
+
+ ))}
+
+
+ ))}
+
+
+
+ {/* 유의사항 섹션 */}
+
+ >
+ )}
+
+
+
+ {/* 매칭 거절 확인 모달 */}
+ {modalType === 'reject' && (
+
setModalType(null)}
+ onRightClick={() => {
+ console.log('매칭 거절 확인')
+ setModalType('rejectSuccess')
+ }}
+ />
+ )}
+
+ {/* 매칭 거절 성공 모달 */}
+ {modalType === 'rejectSuccess' && (
+ {
+ console.log('매칭 거절 성공 확인')
+ setModalType(null)
+ }}
+ />
+ )}
+
+ {/* 매칭 수락 확인 모달 */}
+ {modalType === 'accept' && (
+ setModalType(null)}
+ onRightClick={() => {
+ console.log('매칭 수락 확인')
+ setModalType('acceptSuccess')
+ }}
+ />
+ )}
+
+ {/* 매칭 수락 성공 모달 */}
+ {modalType === 'acceptSuccess' && (
+ {
+ console.log('매칭 수락 성공 확인')
+ setModalType(null)
+ }}
+ />
+ )}
+
+ {/* 매칭 취소 확인 모달 */}
+ {modalType === 'cancel' && (
+ setModalType(null)}
+ onRightClick={() => {
+ console.log('매칭 취소 확인')
+ setModalType('cancelSuccess')
+ }}
+ />
+ )}
+
+ {/* 매칭 취소 성공 모달 */}
+ {modalType === 'cancelSuccess' && (
+ {
+ console.log('매칭 취소 성공 확인')
+ setModalType(null)
+ }}
+ />
+ )}
+
+ )
+}
diff --git a/src/components/mypage/matching-status/MatchingTimerCard.tsx b/src/components/mypage/matching-status/MatchingTimerCard.tsx
new file mode 100644
index 00000000..ee9fa057
--- /dev/null
+++ b/src/components/mypage/matching-status/MatchingTimerCard.tsx
@@ -0,0 +1,132 @@
+import { cn } from '@/utils/cn'
+
+type MatchingStatus = 'default' | 'auto-rejected' | 'accepted'
+type RequestType = 'received' | 'sent'
+
+interface MatchingTimerCardProps {
+ /** 요청 타입 (받은 요청 / 보낸 요청) */
+ requestType: RequestType
+ /** 매칭 상태 */
+ status?: MatchingStatus
+ /** 타이머 값 (초 단위) */
+ timerSeconds?: number
+ /** 타이머 표시 텍스트 (HH:MM:SS 형식, 예: "09:58:29") */
+ timerText?: string
+ /** 수락 버튼 클릭 핸들러 (받은 요청일 때만) */
+ onAccept?: () => void
+ /** 거절 버튼 클릭 핸들러 (받은 요청일 때만) */
+ onReject?: () => void
+ /** 매칭 취소 버튼 클릭 핸들러 (보낸 요청일 때만) */
+ onCancel?: () => void
+ /** 추가 클래스명 */
+ className?: string
+}
+
+/**
+ * 타이머를 HH:MM:SS 형식으로 포맷팅
+ */
+const formatTimer = (seconds: number): string => {
+ const hours = Math.floor(seconds / 3600)
+ const minutes = Math.floor((seconds % 3600) / 60)
+ const secs = seconds % 60
+ return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
+}
+
+const MatchingTimerCard = ({
+ requestType,
+ status = 'default',
+ timerSeconds,
+ timerText,
+ onAccept,
+ onReject,
+ onCancel,
+ className,
+}: MatchingTimerCardProps) => {
+ const isAutoRejected = status === 'auto-rejected'
+ const isAccepted = status === 'accepted'
+ const isReceived = requestType === 'received'
+ const isSent = requestType === 'sent'
+
+ // 타이머 텍스트 결정
+ const displayTimer = timerText || (timerSeconds !== undefined ? formatTimer(timerSeconds) : '00:00:00')
+
+ // 상태에 따른 텍스트와 색상
+ const statusText = isAutoRejected
+ ? '자동 거절 되었습니다.'
+ : isAccepted
+ ? '매칭 수락 되었습니다.'
+ : '대기 만료까지'
+ const statusTextColor = isAutoRejected
+ ? 'text-semantic-700'
+ : isAccepted
+ ? 'text-primary-500-normal'
+ : 'text-neutral-500'
+ const timerColor = isAutoRejected || isAccepted ? 'text-neutral-200' : 'text-primary-500-normal'
+
+ return (
+
+
+ {/* 타이머 영역 */}
+
+
{statusText}
+
{displayTimer}
+
+
+ {/* 버튼 영역 */}
+ {status === 'default' && (
+
+ {/* 받은 요청: 거절/수락 버튼 */}
+ {isReceived && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+ {/* 보낸 요청: 매칭 취소 버튼 */}
+ {isSent && (
+
+
+
+ )}
+
+ )}
+
+
+ )
+}
+
+export default MatchingTimerCard
diff --git a/src/components/mypage/matching-status/ProfileCard.tsx b/src/components/mypage/matching-status/ProfileCard.tsx
new file mode 100644
index 00000000..462c2831
--- /dev/null
+++ b/src/components/mypage/matching-status/ProfileCard.tsx
@@ -0,0 +1,74 @@
+import { cn } from '@/utils/cn'
+import ChatIcon from '@/assets/icons/mypage/chat.svg?react'
+
+interface ProfileCardProps {
+ /** 프로필 이미지 URL */
+ imageUrl?: string
+ /** 사용자 닉네임 */
+ nickname: string
+ /** 사용자 파트 (예: Design, Frontend, Backend) */
+ part: string
+ /** 프로필 소개 텍스트 */
+ introduction?: string
+ /** 메시지 버튼 클릭 핸들러 */
+ onMessageClick?: () => void
+ /** 추가 클래스명 */
+ className?: string
+}
+
+const ProfileCard = ({ imageUrl, nickname, part, introduction, onMessageClick, className }: ProfileCardProps) => {
+ return (
+
+
+ {/* 프로필 이미지 */}
+
+
+ {imageUrl ? (
+

+ ) : (
+
+ )}
+
+
+
+ {/* 텍스트 영역 */}
+
+
+ {/* 닉네임 | Part */}
+
+
+
{nickname}
+
+
{part}
+
+
+ {/* 프로필 소개 */}
+
+ {introduction || '프로필 소개 (첫 문장까지 미리보기됨)'}
+
+
+
+
+ {/* 메시지 아이콘 버튼 */}
+
+
+
+
+
+
+
+
+ )
+}
+
+export default ProfileCard
diff --git a/src/components/mypage/matching-status/ProjectCard.tsx b/src/components/mypage/matching-status/ProjectCard.tsx
new file mode 100644
index 00000000..f5b2df17
--- /dev/null
+++ b/src/components/mypage/matching-status/ProjectCard.tsx
@@ -0,0 +1,63 @@
+import { cn } from '@/utils/cn'
+
+interface ProjectCardProps {
+ /** 프로젝트 이름 */
+ projectName: string
+ /** 프로젝트 카테고리 (예: "금융 · 핀테크") */
+ category: string
+ /** 프로젝트 설명 */
+ description: string
+ /** 현재 팀원 수 */
+ currentMembers: number
+ /** 전체 팀원 수 */
+ totalMembers: number
+ /** 클릭 핸들러 */
+ onClick?: () => void
+ /** 추가 클래스명 */
+ className?: string
+}
+
+const ProjectCard = ({ projectName, category, description, currentMembers, totalMembers, onClick, className }: ProjectCardProps) => {
+ return (
+
+
+ {/* 상단: 프로젝트 이름 + 카테고리 */}
+
+
+ {/* 프로젝트 이름 | 카테고리 */}
+
+
+ {projectName}
+
+
+
{category}
+
+ {/* 프로젝트 설명 */}
+
+ {description}
+
+
+
+
+ {/* 하단: 팀원 수 */}
+
+
+ 팀원
+ {` ${currentMembers}`}
+ /
+ {totalMembers}
+
+
+
+
+ )
+}
+
+export default ProjectCard
diff --git a/src/components/mypage/ongoing-project/OngoingProject.tsx b/src/components/mypage/ongoing-project/OngoingProject.tsx
index 1cca6a5f..d26db187 100644
--- a/src/components/mypage/ongoing-project/OngoingProject.tsx
+++ b/src/components/mypage/ongoing-project/OngoingProject.tsx
@@ -2,14 +2,14 @@ import { useState, useCallback, useEffect } from 'react'
import Button from '@/components/common/Button'
import { MyPageHeader } from '../MyPageHeader'
import HamburgerIcon from '@/assets/icons/common/hamburger.svg?react'
-import Tabbar from './Tabbar'
+import SegmentTabButton from '../SegmentTabButton'
import ProjectManagementView from './tab1-project-setting/ProjectManagementView'
import TeamManagementView from './tab2-team-management/TeamManagementView'
import { useNavigationBlocker } from '@/hooks/mypage/useNavigationBlocker'
import { useOngoingProjectForm } from '@/hooks/mypage/useOngoingProjectForm'
import type { TabType } from '@/types/mypage/ongoindProject'
-import CTAModal from '../../common/CTAModal'
+import CTAModal from '@/components/common/CTAModal'
import PartSettingsModal from './PartSettingsModal'
import { useNavigate } from 'react-router'
import type { ProjectSettingsType } from '@/utils/schemas/projectSchema'
@@ -212,7 +212,18 @@ const OngoingProject = () => {
{/* 탭바 */}
-
+
+ handleActivateTab('프로젝트 설정')}
+ />
+ handleActivateTab('팀원 관리')}
+ />
+
{/* 탭 01. 프로젝트 설정 */}
{activeTab === '프로젝트 설정' && (
diff --git a/src/components/mypage/ongoing-project/Tabbar.tsx b/src/components/mypage/ongoing-project/Tabbar.tsx
deleted file mode 100644
index cade4af4..00000000
--- a/src/components/mypage/ongoing-project/Tabbar.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import type { TabType } from '@/types/mypage/ongoindProject'
-
-interface ITabbar {
- currentTab: string
- onClick: (tabName: TabType) => void
-}
-
-const Tabbar = ({ currentTab, onClick }: ITabbar) => {
- return (
-
-
onClick('프로젝트 설정')} className='flex flex-col gap-3 w-30 pt-2.5'>
-
- 프로젝트 설정
-
-
-
-
-
onClick('팀원 관리')} className='flex flex-col gap-3 w-30 pt-2.5'>
-
- 팀원 관리
-
-
-
-
- )
-}
-
-export default Tabbar
diff --git a/src/components/mypage/profile-analysis/ProfileAnalysis.tsx b/src/components/mypage/profile-analysis/ProfileAnalysis.tsx
index 4e2d7dd3..7abbb594 100644
--- a/src/components/mypage/profile-analysis/ProfileAnalysis.tsx
+++ b/src/components/mypage/profile-analysis/ProfileAnalysis.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react'
-import CTAModal from '../../common/CTAModal'
+import CTAModal from '@/components/common/CTAModal'
import EmptyProfileAnalysis from './EmptyProfileAnalysis'
import { useCollaboStore, useGrowGuideStore, useRoleRecommendStore, useSkillStore } from '@/stores/profileAnalysisStore'
import ProfileRadarChart from '@/components/profile-analysis/ProfileRadarChart'
diff --git a/src/components/mypage/profile-settings/ProfileSettings.tsx b/src/components/mypage/profile-settings/ProfileSettings.tsx
index bffdccbe..d4597096 100644
--- a/src/components/mypage/profile-settings/ProfileSettings.tsx
+++ b/src/components/mypage/profile-settings/ProfileSettings.tsx
@@ -6,7 +6,7 @@ import { useProfileSettingsForm } from '@/hooks/mypage/useProfileSettingsForm'
import { useCTAModal } from '@/stores/useCTAModal'
import type { ProfileFormDataType } from '@/utils/schemas/profileSchema'
-import CTAModal from '../../common/CTAModal'
+import CTAModal from '@/components/common/CTAModal'
import { MyPageHeader } from '../MyPageHeader'
import ProfileBasicInfo from './ProfileBasicInfo'
import Section01Introduction from './sections/Section01Introduction'
diff --git a/src/constants/matchingNotice.ts b/src/constants/matchingNotice.ts
new file mode 100644
index 00000000..0f7ee4a5
--- /dev/null
+++ b/src/constants/matchingNotice.ts
@@ -0,0 +1,42 @@
+export interface NoticeItem {
+ /** 규칙 번호 */
+ number: number
+ /** 규칙 메인 텍스트 */
+ text: string
+ /** 규칙 하위 설명 텍스트 (선택, 여러 줄은 \n으로 구분) */
+ subText?: string
+}
+
+/** 받은 요청 유의사항 */
+export const RECEIVED_REQUEST_NOTICES: NoticeItem[] = [
+ {
+ number: 1,
+ text: '대기 만료 시, 매칭이 자동 거절 처리',
+ },
+ {
+ number: 2,
+ text: '24시간 동안의 매칭 취소 / 거절 / 수락은 번복 불가',
+ },
+ {
+ number: 3,
+ text: '리더가 직접 보내는 요청은 파트당 최대 3명까지 가능 (24시간 동안)',
+ subText: '리더는 24시간동안 한 프로젝트의 파트당 최대 3명에게 **매칭 요청을 직접 보낼 수 있습니다.**\n이때, 유저가 보내오는 프로젝트 매칭 요청은 포함되지 않습니다.',
+ },
+]
+
+/** 보낸 요청 유의사항 */
+export const SENT_REQUEST_NOTICES: NoticeItem[] = [
+ {
+ number: 1,
+ text: '대기 만료 시, 매칭이 자동 거절 처리',
+ },
+ {
+ number: 2,
+ text: '24시간 동안의 매칭 취소 / 거절 / 수락은 번복 불가',
+ },
+ {
+ number: 3,
+ text: '매칭 신청 후 대기 중인 24시간동안 다른 프로젝트에 신청할 수 없습니다.',
+ subText: '이때, 타 유저로 부터 받은 요청은 포함되지 않습니다.',
+ },
+]