diff --git a/ringus.pem b/ringus.pem new file mode 100644 index 0000000..84a5f54 --- /dev/null +++ b/ringus.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEArZaqVLRApg80VuUUiOwP3NJiFgxsu0TNgR4aJ6St7YR7mkSQ +hkDBCcJIVSBfqkNDtyyuQci7JrFuAyN+m5zHkbv0X0oFKB8pFKWCGEeUn9UWJXDb +3OAp7MamsEjUhSds9SD8vtHGRhA3tWq8kbUD60YpMpfoTj0EviZj/Ik+LaSshUDE +ryazI6+xIa3uhLNmn+4O2uizu28QAgl2Iq3MYAFhrNrjIYAgRtSh+CAS7lqvtW2d +oDqeWUqh1VE+k+jlYMCB1x5lasOW8VBKA1jUm8t265jBrVKL8lB7Dk2LltMpavg3 +YI3UzkXGtrS7AapEf98RTdITbtHLGwZRSFSzzQIDAQABAoIBADImkzNBmGPhhKeO +K26bdMHBbmEcWdWIvS6OedP2OeGjIuqg4HhJAPxGywr8/WZ9ZHTpTbbnvVoibLwv +ZwaiNu4dtS5Kfk7nIcE7R+in1YKP14QdpQedI7+qbMIFaJDHoSz4yMyAYp4fVVju +a2hVObqhXImnZZAlBNfC06REKliY/sbWwzzb2NuhjCG40AROIqVVeNfwuIJZtmFC +h3X2OVdI7DWNn+Fix97n08JAjvx3ov/BSvG3aJpx8ZhO/hPO1yVca4PHpv6Orb43 +pXzk/dm0QC9NwweWMBZ0ZwdWvU1gsPIfNnvVGoz4qKc0QYYcLYRQibFe5/ZnGGF7 +CGrEz+kCgYEA4OV1VPRquveWCeW1zyX5vOutQl0qm3BYL9oNr5DMcQOM0k5bIUlJ +jHu/M70vYetsgYLfuDXdPb7xqIc9QaX94q39vYZZT+3lxfcgJIPk1Jq5o4XqX5+z +LYmGtDFfA6oBEcTUe//AWU9Mh47/ATmvheoI4JMK5D6oeU4xUS4zvbMCgYEAxZii +jIiVw46UQwCFCc2Qw3tNc6QzgIzS4keAWXOZKoEQz79/mkT9zPZ9NMQykwkrI4Y+ +QARqDi7Z2iZmijWXk0IjwI3XIxYx8BALsuO3Pd2CSsN2DS3roTFR4fY+3KvCZ2/W +p/LG9dRvUGjJNm/GV+4Lb0AVZZPZL4EEIoBUCH8CgYEA3FURycSYGErebR3nLGZp +MQS4vy7lwlmjnGYGSH4VPZebzKLFt2vEqeTG41qy5D0xFgVxR1lGQusieNjeU8Xb +YczSrm7Ea4GIPpYpoHyzoPNhcmqNv1eHxNJa7Yj9LGrPF2h+QGnFOfpt4NVg0gOB +CjLKtbJ51jno9sd7m6wnNu8CgYAKJUUrXBP8f4SjUBKEp1Zogxs99c9jKVfmoG85 +qJLTuN7JG/cMT8CMVpelLvG91PmvEER/+voLEmLDLbeUHx5SRFIbn+zM82XLArfn +DoQpHAeFmTWlhZcUpriilocxw2vu7bIi23dVxfuVMFwsfF69ww45PxwaJBZBtykk +MFygNwKBgQDbRMJH6IcbOF8HWa2VQp10VnnJaw7P1zYvuIXDgHvizePxNj2OD0zS +hC8tnCBTyhUPSRPs3QIN5PQmzMfFT1HzzQMGqb5TKL4WXR8VO2YNcsvnx60sigMD +MK3GcLuMjKSPZcNNY5x81fLPSxbj2ZTmkyXqp5fIoQ9OxSZ113Litg== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/mentorship/api/fetchMentors.ts b/src/mentorship/api/fetchMentors.ts new file mode 100644 index 0000000..aed64e8 --- /dev/null +++ b/src/mentorship/api/fetchMentors.ts @@ -0,0 +1,60 @@ +import axiosInstance from '@/global/api/axiosInstance'; + +export interface Mentor { + mentorId: number; + nickname: string; + imgUrl: string; + introduction: { + title: string; + content: string; + }; + organization: { + name: string; + jobCategory: string; + detailedJob: string; + experience: number; + }; + message: string; + mentoringCount: number; +} + +export interface MentorResponse { + status: number; + message: string; + data: { + content: Mentor[]; + sliceInfo: { + size: number; + numberOfElements: number; + last: boolean; + empty: boolean; + cursor: number; + }; + }; +} + +// ✅ 멘토 목록 조회 API +export const fetchMentors = async (cursor?: number , size: number = 5): Promise => { + try { + // URL 객체 + const nextUrl = new URL(`${import.meta.env.VITE_API_BASE_URL}/v1/mentor`); + // 파라미터 추가 + nextUrl.searchParams.append('bookmarked', 'false'); + nextUrl.searchParams.append('suggested', 'false'); + nextUrl.searchParams.append('commissioned', 'false'); + nextUrl.searchParams.append('size', size.toString()); + nextUrl.searchParams.append('sort', 'mentorId,DESC'); + + // 커서 있을때만 추가 + if (cursor !== undefined){ + nextUrl.searchParams.append('cursor', cursor.toString()); + } + + //요청 + const response = await axiosInstance.get(nextUrl.toString()); + return response.data; + } catch (error: any) { + console.error('❌ 멘토 목록 조회 실패:', error.response); + throw error.response?.data?.message || '멘토 목록 조회 실패'; + } +}; diff --git a/src/mentorship/components/mentorlist/MentorItem.tsx b/src/mentorship/components/mentorlist/MentorItem.tsx index 651f8dc..c94cbce 100644 --- a/src/mentorship/components/mentorlist/MentorItem.tsx +++ b/src/mentorship/components/mentorlist/MentorItem.tsx @@ -1,60 +1,58 @@ -import React from 'react'; -import { MentorType } from '@/mentorship/pages/mentorlist/MentorshipList.types'; +import React, { forwardRef } from 'react'; +import { Mentor } from '../../api/fetchMentors'; import { Bookmark } from 'lucide-react'; interface MentorItemProps { - mentor: MentorType; + mentor: Mentor; isBookmarked: boolean; - onToggleBookmark: (mName: string) => void; + onToggleBookmark: (nickname: string) => void; } -const MentorItem: React.FC = ({ - mentor, - isBookmarked, - onToggleBookmark, -}) => { - return ( -
- {/* 북마크 아이콘을 우측 상단에 절대 위치로 배치 */} -
onToggleBookmark(mentor.mName)} - > - -
- - {/* 프로필 + 멘토 정보 */} -
-
- 프로필( + ({ mentor, isBookmarked, onToggleBookmark }, ref) => { + return ( +
+ {/* 북마크 아이콘을 우측 상단에 절대 위치로 배치 */} +
onToggleBookmark(mentor.nickname)} + > + -
-
{mentor.mName}
-
{mentor.mcompany}
-
{mentor.mJobDetail}
-
{mentor.mYear}
-
- 멘토링 횟수 - {mentor.respond}회 +
+ + {/* 프로필 + 멘토 정보 */} +
+
+ 프로필 +
+
{mentor.nickname}
+
{mentor.organization.name}
+
{mentor.organization.detailedJob}
+
+ 멘토링 횟수 + {mentor.mentoringCount}회 +
+

"{mentor.introduction.title}"

- - {/* 멘토 소개 */} -

{mentor.intro}

-
- ); -}; + ); + } +); + +// displayName 붙이면 디버깅 편해~! +MentorItem.displayName = 'MentorItem'; export default MentorItem; \ No newline at end of file diff --git a/src/mentorship/components/mentorlist/MentorList.tsx b/src/mentorship/components/mentorlist/MentorList.tsx index 146e278..dc21141 100644 --- a/src/mentorship/components/mentorlist/MentorList.tsx +++ b/src/mentorship/components/mentorlist/MentorList.tsx @@ -1,34 +1,41 @@ import React from 'react'; -import { MentorType } from '@/mentorship/pages/mentorlist/MentorshipList.types'; +import { Mentor } from '../../api/fetchMentors'; import MentorItem from '@/mentorship/components/mentorlist/MentorItem'; interface MentorListProps { - mentors: MentorType[]; + mentors: Mentor[]; bookmarked: { [key: string]: boolean }; - onToggleBookmark: (mName: string) => void; + onToggleBookmark: (nickname: string) => void; + lastMentorRef?: (node: Element | null) => void; // ✅ ref prop 추가 } const MentorList: React.FC = ({ mentors, bookmarked, onToggleBookmark, + lastMentorRef, }) => { return (
{mentors.length > 0 ? ( - mentors.map((mentor) => ( - - )) + mentors.map((mentor, index) => { + const isLast = index === mentors.length - 1; + + return ( + + ); + }) ) : ( -
검색 결과가 없습니다.
+
멘토 내역이 없습니다.
)}
); }; -export default MentorList; +export default MentorList; \ No newline at end of file diff --git a/src/mentorship/components/mentorlist/MentorshipFilterBar.tsx b/src/mentorship/components/mentorlist/MentorshipFilterBar.tsx index 5f627ea..5637faf 100644 --- a/src/mentorship/components/mentorlist/MentorshipFilterBar.tsx +++ b/src/mentorship/components/mentorlist/MentorshipFilterBar.tsx @@ -4,8 +4,7 @@ import { ChevronDown } from 'lucide-react'; interface MentorshipFilterBarProps { selectedField: string | null; selectedSubField: string | null; - selectedYear: string | null; - onFilterClick: (filterType: string) => void; + onFilterClick: (filterType: '직무' | '세부직무') => void; // ✅ 타입 명확화 } const MentorshipFilterBar: React.FC = ({ diff --git a/src/mentorship/components/mentorlist/MentorshipFooter.tsx b/src/mentorship/components/mentorlist/MentorshipFooter.tsx index 2a09571..9b8c2f5 100644 --- a/src/mentorship/components/mentorlist/MentorshipFooter.tsx +++ b/src/mentorship/components/mentorlist/MentorshipFooter.tsx @@ -7,40 +7,29 @@ interface MentorshipFooterProps { } const MentorshipFooter: React.FC = ({ isVisible }) => { + const menuItems = [ + { menu: '홈', icon: , link: '/' }, + { menu: '멘토링 현황', icon: , link: '/mentorship' }, + { menu: '북마크', icon: , link: '/bookmark' }, + { menu: '마이', icon: , link: '/user' }, + ]; + return (
- {[ - { menu: '홈', icon: , link: '/' }, - { - menu: '멘토링 현황', - icon: , - link: '/mentorship', - }, - { menu: '북마크', icon: , link: '/bookmark' }, - { menu: '마이', icon: , link: '/user' }, - ].map(({ menu, icon, link }) => - link ? ( - -
{icon}
-
{menu}
- - ) : ( -
-
{icon}
-
{menu}
-
- ), - )} + {menuItems.map(({ menu, icon, link }) => ( + + {icon} +
{menu}
+ + ))}
); }; -export default MentorshipFooter; +export default MentorshipFooter; \ No newline at end of file diff --git a/src/mentorship/components/mentorlist/MentorshipListFilter.tsx b/src/mentorship/components/mentorlist/MentorshipListFilter.tsx index 1e8638e..495d89e 100644 --- a/src/mentorship/components/mentorlist/MentorshipListFilter.tsx +++ b/src/mentorship/components/mentorlist/MentorshipListFilter.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { X } from 'lucide-react'; interface MentorshipListFilterProps { - filterType: string; + filterType: '직무' | '세부직무'; onClose: () => void; selectedField: string | null; onFieldSelect: (field: string) => void; @@ -11,125 +11,27 @@ interface MentorshipListFilterProps { } const fieldOptions: string[] = [ - '전체', - '마케팅', - '서비스 기획', - '디자인', - '개발', - '대학원', - '인사', - '영업', - '금융', - '데이터', - '의료', - '법률', + '전체', '마케팅', '서비스 기획', '디자인', '개발', + '대학원', '인사', '영업', '금융', '데이터', '의료', '법률' ]; -const subFieldOptions: { [key: string]: string[] } = { +const subFieldOptions: Record = { 전체: [], 마케팅: [ - '브랜드 마케팅', - '퍼포먼스 마케팅', - '디지털/소셜 마케팅', - '그로스 마케팅', - 'PR', - 'AE', - '콘텐츠 마케팅', - '크리에이티브 디렉팅', - '카피라이터', - '미디어 플래너', - '방송PD/영상PD', - '기타', - ], - '서비스 기획': [ - '서비스기획', - 'PM/PO', - '전략 기획', - '운영/기획', - '사업 개발', - 'CX 매니저', - '창업', - '기타', - ], - 디자인: [ - 'UX/UI디자인', - '그래픽 디자인', - '상품 디자인', - '브랜드 디자인', - '웹 디자인', - '아트 디렉터', - '기타', - ], - 개발: [ - '프론트엔드', - '백엔드', - '풀스택 개발자', - 'iOS/Android 개발자', - 'DevOps 엔지니어', - '클라우드 엔지니어', - '시스템/네트워크 엔지니어', - '보안 엔지니어', - '기타', + '브랜드 마케팅', '퍼포먼스 마케팅', '디지털/소셜 마케팅', + '그로스 마케팅', 'PR', 'AE', '콘텐츠 마케팅', '크리에이티브 디렉팅', + '카피라이터', '미디어 플래너', '방송PD/영상PD', '기타', ], + '서비스 기획': ['서비스기획', 'PM/PO', '전략 기획', '운영/기획', '사업 개발', 'CX 매니저', '창업', '기타'], + 디자인: ['UX/UI디자인', '그래픽 디자인', '상품 디자인', '브랜드 디자인', '웹 디자인', '아트 디렉터', '기타'], + 개발: ['프론트엔드', '백엔드', '풀스택 개발자', 'iOS/Android 개발자', 'DevOps 엔지니어', '클라우드 엔지니어', '시스템/네트워크 엔지니어', '보안 엔지니어', '기타'], 대학원: ['국내 대학원', '해외 대학원', '기타'], - 인사: [ - '인사기획', - '채용담당', - '인재육성/교육담당', - '조직문화담당', - '노무담당', - '총무/경영지원', - '인사운영', - '리크루터', - '기타', - ], - 영업: [ - '기업영업(B2B)', - '개인영업(B2C)', - '해외영업', - '기술영업', - '솔루션 컨설턴트', - '주요고객관리(KAM)', - '영업관리/지원', - 'CSM/CX', - '기타', - ], - 금융: [ - '컨설턴트', - 'VC/투자', - 'IB/PE/대체투자', - '에널리스트', - '회계/재무', - '기타', - ], - 데이터: [ - '데이터 사이언티스트', - '데이터 엔지니어', - '데이터 애널리스트', - 'BI 엔지니어', - '머신러닝 엔지니어', - '데이터 아키텍트', - '리서치 애널리스트', - '기타', - ], - 의료: [ - '임상의사', - '임상연구원', - '의료기기 연구개발', - '제약회사 연구원', - '바이오 연구원', - '기타', - ], - 법률: [ - '변호사', - '법무담당', - '특허담당', - '준법감시인(컴플라이언스)', - '법무법인 사무직', - '법률자문', - '특허엔지니어', - '기타', - ], + 인사: ['인사기획', '채용담당', '인재육성/교육담당', '조직문화담당', '노무담당', '총무/경영지원', '인사운영', '리크루터', '기타'], + 영업: ['기업영업(B2B)', '개인영업(B2C)', '해외영업', '기술영업', '솔루션 컨설턴트', '주요고객관리(KAM)', '영업관리/지원', 'CSM/CX', '기타'], + 금융: ['컨설턴트', 'VC/투자', 'IB/PE/대체투자', '에널리스트', '회계/재무', '기타'], + 데이터: ['데이터 사이언티스트', '데이터 엔지니어', '데이터 애널리스트', 'BI 엔지니어', '머신러닝 엔지니어', '데이터 아키텍트', '리서치 애널리스트', '기타'], + 의료: ['임상의사', '임상연구원', '의료기기 연구개발', '제약회사 연구원', '바이오 연구원', '기타'], + 법률: ['변호사', '법무담당', '특허담당', '준법감시인(컴플라이언스)', '법무법인 사무직', '법률자문', '특허엔지니어', '기타'], }; const MentorshipListFilter: React.FC = ({ @@ -148,34 +50,22 @@ const MentorshipListFilter: React.FC = ({ } }, [filterType, onSubFieldSelect]); - const handleClose = () => { - setIsClosing(true); - }; + const handleClose = () => setIsClosing(true); const handleAnimationEnd = () => { - if (isClosing) { - onClose(); - } + if (isClosing) onClose(); }; return ( <> - {/* 필터 슬라이드로 닫히도록 */} +
= ({

- {filterType === '직무' && '직무 선택'} - {filterType === '세부직무' && '세부직무 선택'} + {filterType === '직무' ? '직무 선택' : '세부직무 선택'}

+ {/* 직무 선택 필터 */} {filterType === '직무' && (
{fieldOptions.map((field) => ( @@ -218,21 +108,20 @@ const MentorshipListFilter: React.FC = ({
)} + {/* 세부직무 선택 필터 */} {filterType === '세부직무' && (
- {selectedField && - subFieldOptions[selectedField] && - subFieldOptions[selectedField].length > 0 ? ( - subFieldOptions[selectedField].map((subField) => ( + {subFieldOptions[selectedField || '전체']?.length > 0 ? ( + subFieldOptions[selectedField || '전체'].map((subField) => (