api link to page
This commit is contained in:
@@ -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,19 +55,8 @@ 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');
|
||||
@@ -75,11 +65,10 @@ export default function NavBar() {
|
||||
if (isMounted && pathname !== '/login') {
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = response.data;
|
||||
|
||||
// 계정 상태 확인
|
||||
const userStatus = data.status || data.userStatus;
|
||||
|
||||
@@ -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 파싱 실패 시 기본 메시지 사용
|
||||
}
|
||||
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 (createError) {
|
||||
const errorMessage = createError instanceof Error ? createError.message : '과목 등록 중 오류가 발생했습니다.';
|
||||
console.error('과목 등록 실패:', errorMessage);
|
||||
setErrors({ submit: errorMessage });
|
||||
setIsSaving(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.';
|
||||
|
||||
@@ -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<UserRow[]> {
|
||||
?.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,
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
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 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 (
|
||||
<div className="bg-white min-h-screen flex flex-col">
|
||||
<div className="flex-1 max-w-[1440px] w-full mx-auto px-0">
|
||||
<div className="flex flex-col gap-[10px] h-[100px] items-start px-[32px]">
|
||||
<div className="flex flex-col h-[100px] items-start justify-center px-[32px] ">
|
||||
<h1 className="text-[24px] font-bold leading-[1.5] text-[#1b2027]">
|
||||
강좌 현황
|
||||
</h1>
|
||||
@@ -625,9 +602,14 @@ export default function InstructorCoursesPage() {
|
||||
{/* 테이블 바디 */}
|
||||
<div className="bg-white">
|
||||
{paginatedData.map((item) => (
|
||||
<div
|
||||
<button
|
||||
key={item.id}
|
||||
className="h-[48px] flex items-center border-b border-[#dee1e6] last:border-b-0"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
// 상세 페이지로 이동 (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"
|
||||
>
|
||||
<div className="flex-1 border-r border-[#dee1e6] px-[16px] py-[12px]">
|
||||
<span className="text-[15px] font-medium leading-[1.5] text-[#1b2027]">{item.courseName}</span>
|
||||
@@ -666,7 +648,7 @@ export default function InstructorCoursesPage() {
|
||||
<StatusTag text="미완료" type="default" color="gray" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const data = response.data;
|
||||
|
||||
if (isMounted) {
|
||||
const role = data.role || data.userRole || '';
|
||||
|
||||
@@ -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 =====
|
||||
|
||||
/**
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,19 +52,8 @@ 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.ok) {
|
||||
if (response.status === 401) {
|
||||
// 토큰이 만료되었거나 유효하지 않은 경우
|
||||
localStorage.removeItem('token');
|
||||
@@ -72,17 +62,9 @@ export default function AccountPage() {
|
||||
}
|
||||
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');
|
||||
|
||||
@@ -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) {
|
||||
// 사용자 권한 확인
|
||||
|
||||
Reference in New Issue
Block a user