Skip to content

Commit 95be8bb

Browse files
authored
Merge pull request #47 from Ring-Us/feature/ringus-54
RINGUS-54 feat: 멘토/멘티 프로필 조회 및 수정 api 연결
2 parents 7ad1754 + aa83ad8 commit 95be8bb

26 files changed

Lines changed: 599 additions & 410 deletions

src/app/routes/Router.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const Router = () => {
5454
</Route>
5555
<Route path="/mentorship" element={<MentorshipLayout />}>
5656
<Route path="" element={<MentorList />} />
57-
<Route path="info" element={<MentorInfoView />} />
57+
<Route path="info/:mentorId" element={<MentorInfoView />} />
5858
<Route path="suggestion" element={<Suggestion />} />
5959
</Route>
6060
</Routes>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import axiosInstance from '@/global/api/axiosInstance';
2+
import { MentorData } from '@/user/types';
3+
4+
export const getMentorById = async (mentorId: number): Promise<MentorData> => {
5+
const response = await axiosInstance.get(`/v1/mentor/${mentorId}`);
6+
const data = response.data.data;
7+
8+
return {
9+
nickname: data.nickname ?? '',
10+
education: data.education ?? { schoolName: '', major: '' },
11+
organization: data.organization ?? {
12+
name: '',
13+
jobCategory: '',
14+
detailedJob: '',
15+
experience: 0,
16+
},
17+
introduction: data.introduction ?? { title: '', content: '' },
18+
timezone: data.timezone ?? { days: [], startTime: '', endTime: '' },
19+
mentoringField: data.mentoringField ?? [],
20+
hashtags: data.hashtags ?? [],
21+
message: data.message ?? '',
22+
portfolio: data.portfolio ?? { url: '', description: '', size: 0 },
23+
image: data.image ?? { fileName: '', filePath: '' },
24+
count: data.count ?? 0,
25+
};
26+
};

src/mentorship/pages/MentorInfoView.tsx

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { useState, useEffect } from 'react';
2-
import { useNavigate } from 'react-router-dom';
2+
import { useNavigate, useParams } from 'react-router-dom';
33
import { ArrowLeft } from 'lucide-react';
44
import { Bookmark } from 'lucide-react';
55
import { GlobalButton } from '@/global/ui/GlobalButton';
66

7+
import { getMentorById } from '../api/MentorViewApi';
8+
import { MentorData } from '@/user/types';
9+
import { reverseJobCategoryMapping, reverseDetailedJobMapping } from '@/user/components/Mapping';
10+
import { reverseMentoringFieldMapping, reverseDayMapping } from '@/user/components/Mapping';
11+
712
import MentorInfoProfile from '../../user/components/profileInfo/MentorInfoProfile';
813
import MentorInfoBio from '../../user/components/profileInfo/MentorInfoBio';
914
import MentorInfoFieldsHashtags from '../../user/components/profileInfo/MentorInfoFieldsHashtags';
@@ -12,50 +17,40 @@ import MentorInfoMessage from '../../user/components/profileInfo/MentorInfoMessa
1217
import MentorInfoPortfolio from '../../user/components/profileInfo/MentorInfoPortfolio';
1318

1419
const MentorInfoView = () => {
20+
const { mentorId } = useParams<{ mentorId: string }>();
1521
const navigate = useNavigate();
1622
const [isBookmarked, setIsBookmarked] = useState(false);
23+
const [mentorData, setMentorData] = useState<MentorData | null>(null);
1724

18-
// 테스트용 멘티 데이터 && localStorage에서 데이터 불러오기 (api 연결시 제거)
19-
const [mentorData, setMentorData] = useState(() => {
20-
const savedData = localStorage.getItem("mentorData");
21-
return savedData ? JSON.parse(savedData) : {
22-
nickname: "바이",
23-
24-
education: {
25-
schoolName: "경북대학교",
26-
major: "컴퓨터공학",
27-
},
28-
organization: {
29-
name: "OO주식회사",
30-
role: "브랜드 마케팅/카피라이팅",
31-
experience: 6,
32-
},
33-
count: 716,
34-
image: {
35-
fileName: "",
36-
filePath: "",
37-
},
38-
introduction: {
39-
summary: "브랜드 마케팅에 대한 모든 것을 알려드립니다.",
40-
bio: "안녕하세요!\n저는 OO대학교 경영학과를 졸업하고 현재 XXXX에 다니고 있는 ‘바이’입니다.",
41-
},
42-
availableDays: ["월", "목"],
43-
timezone: {
44-
startTime: { period: "오전", hour: "10", minute: "00"},
45-
endTime: { period: "오후", hour: "18", minute: "00"},
46-
},
47-
mentoringField: ["취업 준비", "커리어 고민"],
48-
hashtags: ["마케팅", "브랜드마케팅", "이직", "취준", "진로고민상담", "면접노하우"],
49-
message: "안녕하세요, 멘티 여러분!\n브랜드 마케팅 경험을 바탕으로 여러분의 성장을 지원하고 싶습니다.",
50-
portfolio: [
51-
{
52-
url: "https://example.com/portfolio1.pdf",
53-
description: "브랜드 마케팅 포트폴리오.pdf",
54-
size: 25,
55-
},
56-
],
25+
useEffect(() => {
26+
const fetchData = async () => {
27+
try {
28+
if (!mentorId || isNaN(Number(mentorId))) return;
29+
const data = await getMentorById(Number(mentorId));
30+
31+
// 매핑
32+
const localizedData: MentorData = {
33+
...data,
34+
mentoringField: data.mentoringField.map((f) => reverseMentoringFieldMapping[f] || f),
35+
timezone: {
36+
...data.timezone,
37+
days: data.timezone.days.map((d) => reverseDayMapping[d] || d),
38+
},
39+
organization: {
40+
...data.organization,
41+
jobCategory: reverseJobCategoryMapping[data.organization.jobCategory] || data.organization.jobCategory,
42+
detailedJob: reverseDetailedJobMapping[data.organization.detailedJob] || data.organization.detailedJob,
43+
},
44+
};
45+
46+
setMentorData(localizedData);
47+
} catch (err) {
48+
console.error("멘토 상세 불러오기 실패:", err);
49+
}
5750
};
58-
});
51+
52+
fetchData();
53+
}, [mentorId]);
5954

6055
useEffect(() => {
6156
const storedBookmark = localStorage.getItem("isBookmarked");
@@ -68,6 +63,8 @@ const MentorInfoView = () => {
6863
localStorage.setItem("isBookmarked", newBookmarkState.toString());
6964
};
7065

66+
if (!mentorData) return <div className="p-4">Loading...</div>;
67+
7168
return (
7269
<div className="h-screen flex flex-col relative overflow-hidden">
7370

@@ -98,7 +95,8 @@ const MentorInfoView = () => {
9895
<MentorInfoProfile
9996
nickname={mentorData.nickname}
10097
name={mentorData.organization.name}
101-
role={mentorData.organization.role}
98+
jobCategory={mentorData.organization.jobCategory}
99+
detailedJob={mentorData.organization.detailedJob}
102100
experience={mentorData.organization.experience}
103101
count={mentorData.count}
104102
image={mentorData?.image?.filePath || "/assets/ringusprofile.png"}
@@ -109,13 +107,13 @@ const MentorInfoView = () => {
109107

110108
{/* 자기소개 */}
111109
<MentorInfoBio
112-
summary={mentorData.introduction.summary}
113-
bio={mentorData.introduction.bio}
110+
title={mentorData.introduction.title}
111+
content={mentorData.introduction.content}
114112
/>
115113

116114
{/* 선호 시간대 */}
117115
<MentorInfoTime
118-
availableDays={mentorData.availableDays}
116+
days={mentorData.timezone.days}
119117
startTime={mentorData.timezone.startTime}
120118
endTime={mentorData.timezone.endTime}
121119
/>

src/user/api/MenteeInfoApi.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { MenteeData } from "../menteetypes";
2+
import axiosInstance from "@/global/api/axiosInstance";
3+
4+
// 멘티 프로필 조회 API
5+
export const getMenteeProfile = async (): Promise<MenteeData> => {
6+
const response = await axiosInstance.get('/v1/mentee/me');
7+
const data = response.data.data;
8+
9+
console.log('멘티 프로필 응답:', response.data);
10+
11+
return {
12+
nickname: data.nickname ?? '',
13+
education: data.education ?? {
14+
schoolName: '',
15+
major: '',
16+
},
17+
introduction: data.introduction ?? '',
18+
image: data.image ?? {
19+
filePath: '',
20+
},
21+
};
22+
};
23+
24+
// 멘티 프로필 수정 API
25+
export const updateMenteeProfile = async (menteeData: MenteeData): Promise<any> => {
26+
const response = await axiosInstance.put('/v1/mentee', menteeData);
27+
return response.data;
28+
};

src/user/api/MentorInfoApi.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { MentorData } from '../types';
2+
import axiosInstance from '@/global/api/axiosInstance';
3+
4+
// 멘토 프로필 조회 API
5+
export const getMentorProfile = async (): Promise<MentorData> => {
6+
const response = await axiosInstance.get('/v1/mentor/me');
7+
const data = response.data.data;
8+
9+
return {
10+
nickname: data.nickname ?? '',
11+
education: data.education ?? { schoolName: '', major: '' },
12+
organization: data.organization ?? {
13+
name: '',
14+
jobCategory: '',
15+
detailedJob: '',
16+
experience: 0,
17+
},
18+
introduction: data.introduction ?? { title: '', content: '' },
19+
timezone: data.timezone ?? { days: [], startTime: '', endTime: '' },
20+
mentoringField: data.mentoringField ?? [],
21+
hashtags: data.hashtags ?? [],
22+
message: data.message ?? '',
23+
portfolio: data.portfolio ?? { url: '', description: '', size: 0 },
24+
image: data.image ?? { fileName: '', filePath: '' },
25+
count: data.count ?? 0,
26+
};
27+
};
28+
29+
// 멘토 프로필 수정 API
30+
export const updateMentorProfile = async (mentorData: MentorData): Promise<any> => {
31+
const response = await axiosInstance.put('/v1/mentor', mentorData);
32+
return response.data;
33+
};

src/user/components/Mapping.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { jobCategoryMapping, detailedJobMapping } from "@/global/components/JobCategories";
2+
3+
// 직무 (한글로)
4+
export const reverseJobCategoryMapping: { [key: string]: string } = Object.entries(jobCategoryMapping)
5+
.reduce((acc, [kor, eng]) => {
6+
acc[eng] = kor;
7+
return acc;
8+
}, {} as { [key: string]: string });
9+
10+
11+
// 세부 직무 (한글로)
12+
export const reverseDetailedJobMapping: { [key: string]: string } = Object.entries(detailedJobMapping)
13+
.reduce((acc, [kor, eng]) => {
14+
acc[eng] = kor;
15+
return acc;
16+
}, {} as { [key: string]: string });
17+
18+
19+
20+
// 멘토링 분야 (영어로)
21+
export const mentoringFieldMapping: Record<string, string> = {
22+
'취업 준비': 'JOB_PREPARATION',
23+
'면접 대비': 'INTERVIEW_PREPARATION',
24+
'업계 동향': 'PRACTICAL_SKILLS',
25+
'커리어 고민': 'PORTFOLIO',
26+
};
27+
28+
// 멘토링 분야 (한글로)
29+
export const reverseMentoringFieldMapping: Record<string, string> = Object.entries(mentoringFieldMapping).reduce((acc, [kor, eng]) => {
30+
acc[eng] = kor;
31+
return acc;
32+
}, {} as Record<string, string>);
33+
34+
35+
36+
// 요일 (영어로)
37+
export const dayMapping: Record<string, string> = {
38+
'월': 'MON',
39+
'화': 'TUE',
40+
'수': 'WED',
41+
'목': 'THU',
42+
'금': 'FRI',
43+
'토': 'SAT',
44+
'일': 'SUN',
45+
};
46+
47+
// 요일 (한글로)
48+
export const reverseDayMapping: Record<string, string> = Object.entries(dayMapping).reduce((acc, [kor, eng]) => {
49+
acc[eng] = kor;
50+
return acc;
51+
}, {} as Record<string, string>);

src/user/components/ProfileEdit/EditBio.tsx

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,67 +3,59 @@ import { MenteeData } from '@/user/menteetypes';
33

44
interface EditBioProps {
55
mentorData?: MentorData;
6-
setMentorData?: React.Dispatch<React.SetStateAction<MentorData>>;
6+
setMentorData?: React.Dispatch<React.SetStateAction<MentorData | null>>;
77
menteeData?: MenteeData;
88
setMenteeData?: React.Dispatch<React.SetStateAction<MenteeData>>;
99
}
1010

1111
const EditBio = ({ mentorData, setMentorData, menteeData, setMenteeData }: EditBioProps) => {
12-
1312
if (!mentorData && !menteeData) return null;
1413

1514
return (
1615
<div className="px-4 my-2">
1716
<div className="font-bold text-[16px] my-4">자기소개</div>
1817

19-
{/* 한줄소개 */}
20-
<textarea
21-
className="w-full px-5 py-4 border-[1px] border-[#D9D7E0] rounded-[10px] text-[14px] resize-none outline-none focus:border-primary-1"
22-
rows={1}
23-
value={
24-
mentorData?.introduction.summary ||
25-
menteeData?.introduction.summary ||
26-
''
27-
}
28-
onChange={(e) => {
29-
if (mentorData && setMentorData) {
18+
{/* 한줄소개 (멘토 전용) */}
19+
{mentorData && setMentorData && (
20+
<textarea
21+
className="w-full px-5 py-4 border-[1px] border-[#D9D7E0] rounded-[10px] text-[14px] resize-none outline-none focus:border-primary-1"
22+
rows={1}
23+
value={mentorData.introduction.title}
24+
onChange={(e) =>
3025
setMentorData({
3126
...mentorData,
3227
introduction: {
3328
...mentorData.introduction,
34-
summary: e.target.value,
29+
title: e.target.value,
3530
},
36-
});
37-
} else if (menteeData && setMenteeData) {
38-
setMenteeData({
39-
...menteeData,
40-
introduction: {
41-
...menteeData.introduction,
42-
summary: e.target.value,
43-
},
44-
});
31+
})
4532
}
46-
}}
47-
placeholder="한줄 소개를 작성하세요."
48-
maxLength={30}
49-
/>
33+
placeholder="한줄 소개를 작성하세요."
34+
maxLength={30}
35+
/>
36+
)}
5037

5138
{/* 자기소개 */}
5239
<textarea
5340
className="w-full min-h-[250px] px-5 py-4 mt-2 border-[1px] border-[#D9D7E0] rounded-[10px] text-[14px] resize-none outline-none focus:border-primary-1"
5441
value={
55-
mentorData?.introduction.bio || menteeData?.introduction.bio || ''
42+
mentorData?.introduction.content ?? menteeData?.introduction ?? ''
5643
}
5744
onChange={(e) => {
45+
const value = e.target.value;
46+
5847
if (mentorData && setMentorData) {
5948
setMentorData({
6049
...mentorData,
61-
introduction: { ...mentorData.introduction, bio: e.target.value },
50+
introduction: {
51+
...mentorData.introduction,
52+
content: value,
53+
},
6254
});
6355
} else if (menteeData && setMenteeData) {
6456
setMenteeData({
6557
...menteeData,
66-
introduction: { ...menteeData.introduction, bio: e.target.value },
58+
introduction: value,
6759
});
6860
}
6961
}}
@@ -72,13 +64,13 @@ const EditBio = ({ mentorData, setMentorData, menteeData, setMenteeData }: EditB
7264
/>
7365

7466
<p className="text-right text-[#94939B] text-[14px] mt-1">
75-
{mentorData?.introduction.bio.length ||
76-
menteeData?.introduction.bio.length ||
77-
0}{' '}
67+
{(mentorData?.introduction?.content?.length ??
68+
menteeData?.introduction?.length ??
69+
0)}{' '}
7870
/ 500
7971
</p>
8072
</div>
8173
);
8274
};
8375

84-
export default EditBio;
76+
export default EditBio;

0 commit comments

Comments
 (0)