From 39d21a475b8af9466854a89b0a336d786ce0aebe Mon Sep 17 00:00:00 2001 From: wallace Date: Sat, 29 Nov 2025 13:00:50 +0900 Subject: [PATCH] api link to page --- src/app/NavBar.tsx | 31 +-- .../admin/courses/CourseRegistrationModal.tsx | 87 ++------ src/app/admin/id/mockData.ts | 41 +--- src/app/admin/id/page.tsx | 121 +---------- src/app/admin/lessons/page.tsx | 19 +- src/app/instructor/courses/page.tsx | 66 +++--- src/app/instructor/page.tsx | 27 +-- src/app/lib/apiService.ts | 202 +++++++++++++++++- src/app/menu/AccountDeleteModal.tsx | 50 +---- src/app/menu/account/page.tsx | 74 ++----- src/app/page.tsx | 17 +- 11 files changed, 288 insertions(+), 447 deletions(-) diff --git a/src/app/NavBar.tsx b/src/app/NavBar.tsx index 4314ddc..fb183f1 100644 --- a/src/app/NavBar.tsx +++ b/src/app/NavBar.tsx @@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from "react"; import { usePathname, useRouter } from "next/navigation"; import MainLogoSvg from "./svgs/mainlogosvg"; import ChevronDownSvg from "./svgs/chevrondownsvg"; +import apiService from "./lib/apiService"; const NAV_ITEMS = [ { label: "교육 과정 목록", href: "/course-list" }, @@ -54,32 +55,20 @@ export default function NavBar() { localStorage.setItem('token', cookieToken); } - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me` - : 'https://hrdi.coconutmeet.net/auth/me'; + const response = await apiService.getCurrentUser(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - if (response.status === 401) { - // 토큰이 만료되었거나 유효하지 않은 경우 - localStorage.removeItem('token'); - document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - // 로그인 페이지가 아닐 때만 리다이렉트 - if (isMounted && pathname !== '/login') { - router.push('/login'); - } + if (response.status === 401) { + // 토큰이 만료되었거나 유효하지 않은 경우 + localStorage.removeItem('token'); + document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + // 로그인 페이지가 아닐 때만 리다이렉트 + if (isMounted && pathname !== '/login') { + router.push('/login'); } return; } - const data = await response.json(); + const data = response.data; // 계정 상태 확인 const userStatus = data.status || data.userStatus; diff --git a/src/app/admin/courses/CourseRegistrationModal.tsx b/src/app/admin/courses/CourseRegistrationModal.tsx index 46c8416..cb21ebc 100644 --- a/src/app/admin/courses/CourseRegistrationModal.tsx +++ b/src/app/admin/courses/CourseRegistrationModal.tsx @@ -42,33 +42,9 @@ export default function CourseRegistrationModal({ open, onClose, onSave, onDelet setIsLoadingInstructors(true); try { - const token = localStorage.getItem('token') || document.cookie - .split('; ') - .find(row => row.startsWith('token=')) - ?.split('=')[1]; - // 외부 API 호출 - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/admin/users/compact` - : 'https://hrdi.coconutmeet.net/admin/users/compact'; - - // 쿼리 파라미터 추가: type=ADMIN - const apiUrl = new URL(baseUrl); - apiUrl.searchParams.set('type', 'ADMIN'); - - const response = await fetch(apiUrl.toString(), { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); - - if (!response.ok) { - throw new Error(`강사 목록을 가져오는데 실패했습니다. (${response.status})`); - } - - const data = await response.json(); + const response = await apiService.getUsersCompact(); + const data = response.data; // API 응답이 배열이 아닌 경우 처리 (예: { items: [...] } 형태) let usersArray: any[] = []; @@ -293,15 +269,6 @@ export default function CourseRegistrationModal({ open, onClose, onSave, onDelet } } - const token = localStorage.getItem('token') || document.cookie - .split('; ') - .find(row => row.startsWith('token=')) - ?.split('=')[1]; - - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? process.env.NEXT_PUBLIC_API_BASE_URL - : 'https://hrdi.coconutmeet.net'; - const requestBody: { title: string; instructor: string; @@ -354,52 +321,24 @@ export default function CourseRegistrationModal({ open, onClose, onSave, onDelet } } else { // 등록 모드: POST /subjects - const response = await fetch(`${baseUrl}/subjects`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - body: JSON.stringify(requestBody), - }); + try { + await apiService.createSubject({ + courseName: courseName.trim(), + instructorName: selectedInstructor.name, + }); - if (!response.ok) { - let errorMessage = `과목 등록 실패 (${response.status})`; - try { - const errorData = await response.json(); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } - } catch (parseError) { - // JSON 파싱 실패 시 기본 메시지 사용 + // 성공 시 onSave 콜백 호출 및 모달 닫기 + if (onSave && selectedInstructor) { + onSave(courseName.trim(), selectedInstructor.name); } + onClose(); // 모달 닫기 + } catch (createError) { + const errorMessage = createError instanceof Error ? createError.message : '과목 등록 중 오류가 발생했습니다.'; console.error('과목 등록 실패:', errorMessage); setErrors({ submit: errorMessage }); setIsSaving(false); return; } - - // 응답에서 id 추출하여 저장 - try { - const responseData = await response.json(); - - // 응답에서 id 추출 (다양한 가능한 필드명 확인) - const subjectId = responseData.id - || responseData.data?.id - || responseData.subjectId - || responseData.data?.subjectId - || null; - } catch (parseError) { - // 응답 파싱 실패 시 무시 - } - - // 성공 시 onSave 콜백 호출 및 모달 닫기 - if (onSave && selectedInstructor) { - onSave(courseName.trim(), selectedInstructor.name); - } - onClose(); // 모달 닫기 } } catch (error) { const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.'; diff --git a/src/app/admin/id/mockData.ts b/src/app/admin/id/mockData.ts index fd57d3e..206527f 100644 --- a/src/app/admin/id/mockData.ts +++ b/src/app/admin/id/mockData.ts @@ -1,3 +1,5 @@ +import apiService from "@/app/lib/apiService"; + type RoleType = 'learner' | 'instructor' | 'admin'; type AccountStatus = 'active' | 'inactive'; @@ -22,43 +24,8 @@ export async function getInstructors(): Promise { ?.split('=')[1]) : null; - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/admin/users/compact` - : 'https://hrdi.coconutmeet.net/admin/users/compact'; - - // 쿼리 파라미터 추가: type=ADMIN, limit=10 - const apiUrl = new URL(baseUrl); - apiUrl.searchParams.set('type', 'ADMIN'); - apiUrl.searchParams.set('limit', '10'); - - console.log('🔍 [getInstructors] API 호출 정보:', { - url: apiUrl.toString(), - hasToken: !!token, - tokenLength: token?.length || 0 - }); - - const response = await fetch(apiUrl.toString(), { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); - - console.log('📡 [getInstructors] API 응답 상태:', { - status: response.status, - statusText: response.statusText, - ok: response.ok - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error('❌ [getInstructors] API 에러 응답:', errorText); - console.error('강사 목록 가져오기 실패:', response.status); - return []; - } - - const data = await response.json(); + const response = await apiService.getUsersCompact(); + const data = response.data; console.log('📦 [getInstructors] 원본 API 응답 데이터:', { type: typeof data, diff --git a/src/app/admin/id/page.tsx b/src/app/admin/id/page.tsx index a04ac41..5018b5c 100644 --- a/src/app/admin/id/page.tsx +++ b/src/app/admin/id/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useRef, useMemo } from "react"; import AdminSidebar from "@/app/components/AdminSidebar"; import ChevronDownSvg from "@/app/svgs/chevrondownsvg"; import { type UserRow } from "./mockData"; +import apiService from "@/app/lib/apiService"; type TabType = 'all' | 'learner' | 'instructor' | 'admin'; type RoleType = 'learner' | 'instructor' | 'admin'; @@ -45,35 +46,9 @@ export default function AdminIdPage() { setIsLoading(true); setError(null); - const token = localStorage.getItem('token') || document.cookie - .split('; ') - .find(row => row.startsWith('token=')) - ?.split('=')[1]; - // 외부 API 호출 - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/admin/users/compact` - : 'https://hrdi.coconutmeet.net/admin/users/compact'; - - // 쿼리 파라미터 추가: type=STUDENT, limit=10, page=currentPage - const apiUrl = new URL(baseUrl); - apiUrl.searchParams.set('type', 'STUDENT'); - apiUrl.searchParams.set('limit', '10'); - apiUrl.searchParams.set('page', String(currentPage)); - - const response = await fetch(apiUrl.toString(), { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); - - if (!response.ok) { - throw new Error(`사용자 데이터를 가져오는데 실패했습니다. (${response.status})`); - } - - const data = await response.json(); + const response = await apiService.getUsersCompact(); + const data = response.data; // API 응답이 배열이 아닌 경우 처리 (예: { items: [...] } 형태) let usersArray: any[] = []; @@ -206,48 +181,7 @@ export default function AdminIdPage() { return; } - const token = localStorage.getItem('token') || document.cookie - .split('; ') - .find(row => row.startsWith('token=')) - ?.split('=')[1]; - - if (!token) { - setToastMessage('로그인이 필요합니다.'); - setShowToast(true); - setTimeout(() => { - setShowToast(false); - }, 3000); - setIsActivateModalOpen(false); - setSelectedUserId(null); - return; - } - - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/admin/users/${selectedUserId}/unsuspend` - : `https://hrdi.coconutmeet.net/admin/users/${selectedUserId}/unsuspend`; - - const response = await fetch(apiUrl, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - let errorMessage = `계정 활성화 실패 (${response.status})`; - try { - const errorData = await response.json(); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } - } catch (parseError) { - // ignore - } - throw new Error(errorMessage); - } + await apiService.unsuspendUser(selectedUserId); // API 호출 성공 시 로컬 상태 업데이트 setUsers(prevUsers => @@ -295,52 +229,7 @@ export default function AdminIdPage() { } try { - const token = localStorage.getItem('token') || document.cookie - .split('; ') - .find(row => row.startsWith('token=')) - ?.split('=')[1]; - - if (!token) { - setToastMessage('로그인이 필요합니다.'); - setShowToast(true); - setTimeout(() => { - setShowToast(false); - }, 3000); - setIsDeactivateModalOpen(false); - setSelectedUserId(null); - setDeactivateReason(''); - return; - } - - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/admin/users/${selectedUserId}/suspend` - : `https://hrdi.coconutmeet.net/admin/users/${selectedUserId}/suspend`; - - const response = await fetch(apiUrl, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ - reason: deactivateReason, - }), - }); - - if (!response.ok) { - let errorMessage = `계정 비활성화 실패 (${response.status})`; - try { - const errorData = await response.json(); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } - } catch (parseError) { - // ignore - } - throw new Error(errorMessage); - } + await apiService.suspendUser(selectedUserId); // API 호출 성공 시 로컬 상태 업데이트 setUsers(prevUsers => diff --git a/src/app/admin/lessons/page.tsx b/src/app/admin/lessons/page.tsx index 9a53108..db8cf21 100644 --- a/src/app/admin/lessons/page.tsx +++ b/src/app/admin/lessons/page.tsx @@ -158,23 +158,10 @@ export default function AdminLessonsPage() { return; } - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me` - : 'https://hrdi.coconutmeet.net/auth/me'; + const response = await apiService.getCurrentUser(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - if (response.ok) { - const data = await response.json(); - if (data.name) { - setCurrentUser(data.name); - } + if (response.data && response.data.name) { + setCurrentUser(response.data.name); } } catch (error) { console.error('사용자 정보 조회 오류:', error); diff --git a/src/app/instructor/courses/page.tsx b/src/app/instructor/courses/page.tsx index 2d99f88..fbdbfb4 100644 --- a/src/app/instructor/courses/page.tsx +++ b/src/app/instructor/courses/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useState, useMemo } from 'react'; import { useRouter } from 'next/navigation'; import ChevronDownSvg from '../../svgs/chevrondownsvg'; +import apiService from '../../lib/apiService'; // 드롭다운 아이콘 컴포넌트 function ArrowDownIcon({ className }: { className?: string }) { @@ -139,30 +140,18 @@ export default function InstructorCoursesPage() { return; } - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me` - : 'https://hrdi.coconutmeet.net/auth/me'; + const response = await apiService.getCurrentUser(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - if (response.status === 401) { - localStorage.removeItem('token'); - document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - if (isMounted) { - router.push('/login'); - } + if (response.status === 401) { + localStorage.removeItem('token'); + document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + if (isMounted) { + router.push('/login'); } return; } - const data = await response.json(); + const data = response.data; if (isMounted) { const role = data.role || data.userRole || ''; @@ -198,26 +187,14 @@ export default function InstructorCoursesPage() { .find(row => row.startsWith('token=')) ?.split('=')[1]; - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/subjects` - : 'https://hrdi.coconutmeet.net/subjects'; + const response = await apiService.getSubjects(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); - - if (response.ok) { - const data = await response.json(); - const coursesArray = Array.isArray(data) ? data : (data.items || data.courses || data.data || []); - setCourses(coursesArray.map((item: any) => ({ - id: String(item.id || item.subjectId || ''), - name: item.courseName || item.name || item.subjectName || '', - }))); - } + const data = response.data; + const coursesArray = Array.isArray(data) ? data : (data.items || data.courses || data.data || []); + setCourses(coursesArray.map((item: any) => ({ + id: String(item.id || item.subjectId || ''), + name: item.courseName || item.name || item.subjectName || '', + }))); } catch (error) { console.error('교육 과정 목록 조회 오류:', error); } @@ -391,7 +368,7 @@ export default function InstructorCoursesPage() { return (
-
+

강좌 현황

@@ -625,9 +602,14 @@ export default function InstructorCoursesPage() { {/* 테이블 바디 */}
{paginatedData.map((item) => ( -
{ + // 상세 페이지로 이동 (API 연동 시 item.id 사용) + router.push(`/instructor/courses/${item.id}`); + }} + className="h-[48px] w-full flex items-center border-b border-[#dee1e6] last:border-b-0 cursor-pointer hover:bg-[#F5F7FF] transition-colors text-left" >
{item.courseName} @@ -666,7 +648,7 @@ export default function InstructorCoursesPage() { )}
-
+ ))}
diff --git a/src/app/instructor/page.tsx b/src/app/instructor/page.tsx index 6404dbb..7ef5e3a 100644 --- a/src/app/instructor/page.tsx +++ b/src/app/instructor/page.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import Link from 'next/link'; import MainLogoSvg from '../svgs/mainlogosvg'; import ChevronDownSvg from '../svgs/chevrondownsvg'; +import apiService from '../lib/apiService'; // 아이콘 컴포넌트들 function BookIcon({ className }: { className?: string }) { @@ -194,30 +195,18 @@ export default function InstructorPage() { return; } - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me` - : 'https://hrdi.coconutmeet.net/auth/me'; + const response = await apiService.getCurrentUser(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { - if (response.status === 401) { - localStorage.removeItem('token'); - document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - if (isMounted) { - router.push('/login'); - } + if (response.status === 401) { + localStorage.removeItem('token'); + document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + if (isMounted) { + router.push('/login'); } return; } - const data = await response.json(); + const data = response.data; if (isMounted) { const role = data.role || data.userRole || ''; diff --git a/src/app/lib/apiService.ts b/src/app/lib/apiService.ts index 5dfc953..6651c14 100644 --- a/src/app/lib/apiService.ts +++ b/src/app/lib/apiService.ts @@ -339,15 +339,86 @@ class ApiService { }); } + /** + * 회원 리스트 조회 (컴팩트) - 관리자 전용 + */ + async getUsersCompact() { + return this.request('/admin/users/compact'); + } + + /** + * 단일 회원 정지 (ID 기준) - 관리자 전용 + */ + async suspendUser(userId: string | number) { + return this.request(`/admin/users/${userId}/suspend`, { + method: 'PATCH', + }); + } + + /** + * 단일 회원 정지 해제 (ID 기준) - 관리자 전용 + */ + async unsuspendUser(userId: string | number) { + return this.request(`/admin/users/${userId}/unsuspend`, { + method: 'PATCH', + }); + } + + /** + * 대량 정지 (ID 배열) - 관리자 전용 + */ + async suspendUsers(userIds: (string | number)[]) { + return this.request('/admin/users/suspend', { + method: 'POST', + body: { userIds }, + }); + } + // ===== 기타 API ===== /** - * 공지사항 조회 + * 공지사항 목록 조회 (페이징) */ async getNotices() { return this.request('/notices'); } + /** + * 공지 단건 조회 + */ + async getNotice(id: string | number) { + return this.request(`/notices/${id}`); + } + + /** + * 공지 등록 (ADMIN) + */ + async createNotice(noticeData: any) { + return this.request('/notices', { + method: 'POST', + body: noticeData, + }); + } + + /** + * 공지 수정 (ADMIN) + */ + async updateNotice(id: string | number, noticeData: any) { + return this.request(`/notices/${id}`, { + method: 'PATCH', + body: noticeData, + }); + } + + /** + * 공지 삭제 (ADMIN) + */ + async deleteNotice(id: string | number) { + return this.request(`/notices/${id}`, { + method: 'DELETE', + }); + } + /** * 강좌 조회 */ @@ -424,6 +495,135 @@ class ApiService { return this.request('/resources'); } + // ===== 학습 자료실 (Library) 관련 API ===== + + /** + * 학습 자료실 목록 조회 (로그인 필요, 페이징) + */ + async getLibrary() { + return this.request('/library'); + } + + /** + * 학습 자료 단건 조회 + */ + async getLibraryItem(id: string | number) { + return this.request(`/library/${id}`); + } + + /** + * 학습 자료 등록 (ADMIN) + */ + async createLibraryItem(libraryData: any) { + return this.request('/library', { + method: 'POST', + body: libraryData, + }); + } + + /** + * 학습 자료 수정 (ADMIN) + */ + async updateLibraryItem(id: string | number, libraryData: any) { + return this.request(`/library/${id}`, { + method: 'PATCH', + body: libraryData, + }); + } + + /** + * 학습 자료 삭제 (ADMIN) + */ + async deleteLibraryItem(id: string | number) { + return this.request(`/library/${id}`, { + method: 'DELETE', + }); + } + + // ===== 진행률 (Progress) 관련 API ===== + + /** + * 특정 강의에서 "내" 진행률 조회 + */ + async getLectureProgress(lectureId: string | number) { + return this.request(`/progress/lectures/${lectureId}`); + } + + /** + * 강의 진행(Heartbeat) 업서트 + */ + async updateLectureProgress(progressData: any) { + return this.request('/progress/lectures/progress', { + method: 'POST', + body: progressData, + }); + } + + /** + * 과목 진행률 요약(내 기준) + */ + async getSubjectProgressSummary(subjectId: string | number) { + return this.request(`/progress/subjects/${subjectId}/summary`); + } + + // ===== 평가 (Evaluation) 관련 API ===== + + /** + * 강의 평가 제출 (60점 이상만 저장) + */ + async submitEvaluation(evaluationData: any) { + return this.request('/evaluations', { + method: 'POST', + body: evaluationData, + }); + } + + /** + * 특정 강의에서 "내" 마지막 평가 결과 조회 + */ + async getMyEvaluation(lectureId: string | number) { + return this.request(`/evaluations/lectures/${lectureId}/me`); + } + + /** + * 특정 강의에서 특정 수강생의 마지막 평가 결과 조회 (ADMIN/INSTRUCTOR) + */ + async getUserEvaluation(lectureId: string | number, userId: string | number) { + return this.request(`/evaluations/lectures/${lectureId}/users/${userId}`); + } + + // ===== 관리자 강의 현황 관련 API ===== + + /** + * 강좌별 학습/문제 제출/수료 현황 리스트 (관리자/강사용) + */ + async getLecturesStatus() { + return this.request('/admin/lectures/status'); + } + + /** + * 특정 강좌 + 특정 학습자의 수강/문제 풀이 상세 (관리자/강사용) + */ + async getLectureStudentDetail(lectureId: string | number, userId: string | number) { + return this.request(`/admin/lectures/${lectureId}/students/${userId}/detail`); + } + + // ===== 수료증 및 결과 관련 API ===== + + /** + * 특정 과목 수료증용 정보 조회 (학생 본인) + */ + async getCertificate(subjectId: string | number) { + return this.request(`/certificates/subjects/${subjectId}`); + } + + /** + * 학생 기준 학습 결과(수료 과목 목록) + */ + async getMyResults() { + return this.request('/results/my-subjects'); + } + // ===== 파일 업로드 관련 API ===== /** diff --git a/src/app/menu/AccountDeleteModal.tsx b/src/app/menu/AccountDeleteModal.tsx index 591722a..d3eaad8 100644 --- a/src/app/menu/AccountDeleteModal.tsx +++ b/src/app/menu/AccountDeleteModal.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import ModalCloseSvg from "../svgs/closexsvg"; +import apiService from "../lib/apiService"; type Props = { open: boolean; @@ -22,54 +23,7 @@ export default function AccountDeleteModal({ open, onClose, onConfirm }: Props) setIsLoading(true); try { - const token = localStorage.getItem('token'); - if (!token) { - alert('로그인이 필요합니다.'); - setIsLoading(false); - onClose(); - router.push('/login'); - return; - } - - console.log('회원 탈퇴 요청 시작, 토큰 존재:', !!token); - console.log('토큰 길이:', token?.length); - console.log('토큰 시작 부분:', token?.substring(0, 20)); - - const response = await fetch('https://hrdi.coconutmeet.net/auth/delete/me', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - console.log('회원 탈퇴 응답 상태:', response.status); - - if (!response.ok) { - let errorMessage = `회원 탈퇴 실패 (${response.status})`; - try { - const errorData = await response.json(); - console.error('회원 탈퇴 API 오류 응답:', errorData); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } else if (errorData.errorMessage) { - errorMessage = errorData.errorMessage; - } else if (response.statusText) { - errorMessage = `${response.statusText} (${response.status})`; - } - } catch (parseError) { - console.error('응답 파싱 오류:', parseError); - if (response.statusText) { - errorMessage = `${response.statusText} (${response.status})`; - } - } - console.error('회원 탈퇴 실패:', errorMessage, '상태 코드:', response.status); - alert(errorMessage); - setIsLoading(false); - return; - } + await apiService.deleteAccount(); // 성공 시 토큰 제거 및 로그인 페이지로 이동 localStorage.removeItem('token'); diff --git a/src/app/menu/account/page.tsx b/src/app/menu/account/page.tsx index 710e197..602a684 100644 --- a/src/app/menu/account/page.tsx +++ b/src/app/menu/account/page.tsx @@ -6,6 +6,7 @@ import ChangePasswordModal from "../ChangePasswordModal"; import PasswordChangeDoneModal from "../PasswordChangeDoneModal"; import AccountDeleteModal from "../AccountDeleteModal"; import MenuAccountOption from "@/app/menu/account/MenuAccountOption"; +import apiService from "@/app/lib/apiService"; type VerificationState = 'initial' | 'sent' | 'verified' | 'failed' | 'changed'; @@ -51,38 +52,19 @@ export default function AccountPage() { localStorage.setItem('token', cookieToken); } - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me` - : 'https://hrdi.coconutmeet.net/auth/me'; + const response = await apiService.getCurrentUser(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); + if (response.status === 401) { + // 토큰이 만료되었거나 유효하지 않은 경우 + localStorage.removeItem('token'); + if (isMounted) { + router.push('/login'); + } + return; + } - if (!response.ok) { - if (response.status === 401) { - // 토큰이 만료되었거나 유효하지 않은 경우 - localStorage.removeItem('token'); - if (isMounted) { - router.push('/login'); - } - return; - } - let errorMessage = `사용자 정보 조회 실패 (${response.status})`; - try { - const errorData = await response.json(); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } - } catch (parseError) { - // ignore - } + if (response.status !== 200) { + const errorMessage = response.message || `사용자 정보 조회 실패 (${response.status})`; console.error('사용자 정보 조회 실패:', errorMessage); if (isMounted) { setIsLoading(false); @@ -90,7 +72,7 @@ export default function AccountPage() { return; } - const data = await response.json(); + const data = response.data; if (isMounted) { setUserInfo(data); setIsLoading(false); @@ -191,35 +173,7 @@ export default function AccountPage() { onClose={() => setDeleteOpen(false)} onConfirm={async () => { try { - const token = localStorage.getItem('token'); - const response = await fetch('https://hrdi.coconutmeet.net/auth/delete/me', { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); - - if (!response.ok) { - let errorMessage = `회원 탈퇴 실패 (${response.status})`; - try { - const errorData = await response.json(); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } else if (response.statusText) { - errorMessage = `${response.statusText} (${response.status})`; - } - } catch (parseError) { - if (response.statusText) { - errorMessage = `${response.statusText} (${response.status})`; - } - } - console.error('회원 탈퇴 실패:', errorMessage); - alert(errorMessage); - return; - } + await apiService.deleteAccount(); // 성공 시 토큰 제거 및 로그인 페이지로 이동 localStorage.removeItem('token'); diff --git a/src/app/page.tsx b/src/app/page.tsx index 9782865..22eea48 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { useRouter } from 'next/navigation'; import MainLogoSvg from './svgs/mainlogosvg'; +import apiService from './lib/apiService'; export default function Home() { const router = useRouter(); @@ -133,23 +134,13 @@ export default function Home() { return; } - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/me` - : 'https://hrdi.coconutmeet.net/auth/me'; + const response = await apiService.getCurrentUser(); - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); - - if (!response.ok) { + if (response.status !== 200) { return; } - const data = await response.json(); + const data = response.data; if (isMounted) { // 사용자 권한 확인