From c91dd4a30f8b98c5db066dba58bdfe70f0ece241 Mon Sep 17 00:00:00 2001 From: wallace Date: Fri, 28 Nov 2025 14:26:54 +0900 Subject: [PATCH] =?UTF-8?q?API=20=EC=9E=AC=ED=99=9C=EC=9A=A91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/admin/courses/mockData.ts | 40 +-- src/app/find-id/page.tsx | 248 ++++++++---------- src/app/lib/apiService.ts | 364 +++++++++++++++++++++++++++ src/app/login/page.tsx | 113 +++------ src/app/menu/ChangePasswordModal.tsx | 63 ++++- src/app/register/RegisterForm.tsx | 85 ++----- src/app/reset-password/page.tsx | 111 +------- 7 files changed, 586 insertions(+), 438 deletions(-) create mode 100644 src/app/lib/apiService.ts diff --git a/src/app/admin/courses/mockData.ts b/src/app/admin/courses/mockData.ts index 2d8ca70..eac11a3 100644 --- a/src/app/admin/courses/mockData.ts +++ b/src/app/admin/courses/mockData.ts @@ -1,3 +1,5 @@ +import apiService from "@/app/lib/apiService"; + export type Course = { id: string; courseName: string; @@ -10,45 +12,13 @@ export type Course = { // 과목 리스트 조회 API export async function getCourses(): Promise { try { - const token = typeof window !== 'undefined' - ? (localStorage.getItem('token') || document.cookie - .split('; ') - .find(row => row.startsWith('token=')) - ?.split('=')[1]) - : null; + const response = await apiService.getSubjects(); + const data = response.data; - const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL - ? process.env.NEXT_PUBLIC_API_BASE_URL - : 'https://hrdi.coconutmeet.net'; - - const apiUrl = `${baseUrl}/subjects`; - - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), - }, - }); - - if (!response.ok) { - // 404 에러는 리소스가 없는 것이므로 빈 배열 반환 - if (response.status === 404) { - console.warn('⚠️ [getCourses] 과목 리스트를 찾을 수 없습니다 (404)'); - return []; - } - - const errorText = await response.text(); - console.error('❌ [getCourses] API 에러 응답:', errorText); - throw new Error(`과목 리스트 조회 실패 (${response.status})`); - } - - const data = await response.json(); - // 디버깅: API 응답 구조 확인 console.log('🔍 [getCourses] API 원본 응답:', data); console.log('🔍 [getCourses] 응답 타입:', Array.isArray(data) ? '배열' : typeof data); - + // API 응답이 배열이 아닌 경우 처리 (예: { items: [...] } 형태) let coursesArray: any[] = []; if (Array.isArray(data)) { diff --git a/src/app/find-id/page.tsx b/src/app/find-id/page.tsx index a0b5e6f..cb69782 100644 --- a/src/app/find-id/page.tsx +++ b/src/app/find-id/page.tsx @@ -5,6 +5,7 @@ import IdFindDone from "./IdFindDone"; import IdFindFailed from "./IdFindFailed"; import FindIdOption from "./FindIdOption"; import LoginInputSvg from "@/app/svgs/inputformx"; +import apiService from "@/app/lib/apiService"; export default function FindIdPage() { const [isDoneOpen, setIsDoneOpen] = useState(false); @@ -49,58 +50,19 @@ export default function FindIdPage() { if (!validateAll()) return; try { - const response = await fetch('https://hrdi.coconutmeet.net/auth/find-id', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name: name, - phone: phone, - }), - }); + const response = await apiService.findUserId(name, phone); + const data = response.data; - 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); - setIsFailedOpen(true); - return; - } - - const data = await response.json(); - // body의 emails 배열에서 첫 번째 이메일 가져오기 if (data.emails && Array.isArray(data.emails) && data.emails.length > 0) { setFoundUserId(data.emails[0]); } else if (data.email) { setFoundUserId(data.email); } - - // 가입일 정보는 API 응답에 없음 - // // header의 date 값 가져오기 - // const headerDate = response.headers.get('date'); - // if (headerDate) { - // setJoinedAt(formatJoinedAt(headerDate)); - // } - + setIsDoneOpen(true); } catch (error) { - const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.'; - console.error('아이디 찾기 오류:', errorMessage); + console.error('아이디 찾기 오류:', error); setIsFailedOpen(true); } } @@ -121,115 +83,113 @@ export default function FindIdPage() {
-
-
- 아이디 찾기 -
-

- 가입 시 등록한 회원정보를 입력해 주세요. -

-
- -
-
- -
- { - setName(e.target.value); - if (errors.name) { - setErrors((prev) => { - const next = { ...prev }; - delete next.name; - return next; - }); - } - }} - onFocus={() => setFocused((p) => ({ ...p, name: true }))} - onBlur={() => setFocused((p) => ({ ...p, name: false }))} - placeholder="이름을 입력해 주세요." - className={`h-[40px] px-[12px] py-[7px] w-full rounded-[8px] mt-3 border focus:outline-none text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px] ${ - errors.name - ? "border-error focus:border-error" - : "border-neutral-40 focus:border-neutral-700" - }`} - /> - {name.trim().length > 0 && focused.name && ( - - )} +
+
+ 아이디 찾기
- {errors.name &&

{errors.name}

} +

+ 가입 시 등록한 회원정보를 입력해 주세요. +

-
- -
- { - const numbersOnly = e.target.value.replace(/[^0-9]/g, ""); - setPhone(numbersOnly); - if (errors.phone) { - setErrors((prev) => { - const next = { ...prev }; - delete next.phone; - return next; - }); - } - }} - onFocus={() => setFocused((p) => ({ ...p, phone: true }))} - onBlur={() => setFocused((p) => ({ ...p, phone: false }))} - placeholder="-없이 입력해 주세요." - className={`h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border mt-3 focus:outline-none text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px] ${ - errors.phone - ? "border-error focus:border-error" - : "border-neutral-40 focus:border-neutral-700" - }`} - /> - {phone.length > 0 && focused.phone && ( - - )} + onFocus={() => setFocused((p) => ({ ...p, name: true }))} + onBlur={() => setFocused((p) => ({ ...p, name: false }))} + placeholder="이름을 입력해 주세요." + className={`h-[40px] px-[12px] py-[7px] w-full rounded-[8px] mt-3 border focus:outline-none text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px] ${errors.name + ? "border-error focus:border-error" + : "border-neutral-40 focus:border-neutral-700" + }`} + /> + {name.trim().length > 0 && focused.name && ( + + )} +
+ {errors.name &&

{errors.name}

}
- {errors.phone &&

{errors.phone}

} -
- +
+ +
+ { + const numbersOnly = e.target.value.replace(/[^0-9]/g, ""); + setPhone(numbersOnly); + if (errors.phone) { + setErrors((prev) => { + const next = { ...prev }; + delete next.phone; + return next; + }); + } + }} + onFocus={() => setFocused((p) => ({ ...p, phone: true }))} + onBlur={() => setFocused((p) => ({ ...p, phone: false }))} + placeholder="-없이 입력해 주세요." + className={`h-[40px] px-[12px] py-[7px] w-full rounded-[8px] border mt-3 focus:outline-none text-[18px] text-neutral-700 placeholder:text-input-placeholder-text pr-[40px] ${errors.phone + ? "border-error focus:border-error" + : "border-neutral-40 focus:border-neutral-700" + }`} + /> + {phone.length > 0 && focused.phone && ( + + )} +
+ {errors.phone &&

{errors.phone}

} +
+ + - +
diff --git a/src/app/lib/apiService.ts b/src/app/lib/apiService.ts new file mode 100644 index 0000000..c230495 --- /dev/null +++ b/src/app/lib/apiService.ts @@ -0,0 +1,364 @@ +/** + * 중앙화된 API 서비스 + * 모든 외부 API 요청을 이 모듈에서 관리 + */ + +interface ApiResponse { + data: T; + status: number; + message?: string; +} + +interface RequestConfig { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; + headers?: Record; + body?: any; + timeout?: number; +} + +class ApiService { + private baseURL: string; + private defaultTimeout: number = 30000; // 30초 + + constructor(baseURL: string) { + this.baseURL = baseURL; + } + + /** + * 토큰을 가져오는 헬퍼 함수 + */ + private getToken(): string | null { + if (typeof window === 'undefined') return null; + + // localStorage에서 토큰 우선 확인 + const localToken = localStorage.getItem('token'); + if (localToken) return localToken; + + // 쿠키에서 토큰 확인 + const cookieToken = document.cookie + .split('; ') + .find(row => row.startsWith('token=')) + ?.split('=')[1]; + + return cookieToken || null; + } + + /** + * 기본 헤더 생성 + */ + private getDefaultHeaders(): Record { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + const token = this.getToken(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + return headers; + } + + /** + * 타임아웃이 있는 fetch + */ + private async fetchWithTimeout(url: string, options: RequestInit, timeout: number): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + try { + const response = await fetch(url, { + ...options, + signal: controller.signal, + }); + clearTimeout(timeoutId); + return response; + } catch (error) { + clearTimeout(timeoutId); + if (error instanceof Error && error.name === 'AbortError') { + throw new Error('요청 시간이 초과되었습니다.'); + } + throw error; + } + } + + /** + * 공통 API 요청 함수 + */ + private async request( + endpoint: string, + config: RequestConfig = {} + ): Promise> { + const { + method = 'GET', + headers = {}, + body, + timeout = this.defaultTimeout + } = config; + + const url = endpoint.startsWith('http') ? endpoint : `${this.baseURL}${endpoint}`; + + const requestOptions: RequestInit = { + method, + headers: { + ...this.getDefaultHeaders(), + ...headers, + }, + }; + + if (body && method !== 'GET') { + requestOptions.body = JSON.stringify(body); + } + + try { + const response = await this.fetchWithTimeout(url, requestOptions, timeout); + + if (!response.ok) { + let errorMessage = `HTTP ${response.status}: ${response.statusText}`; + + try { + const errorData = await response.json(); + if (errorData.message) { + errorMessage = errorData.message; + } else if (errorData.error) { + errorMessage = errorData.error; + } + } catch (parseError) { + // JSON 파싱 실패 시 기본 에러 메시지 사용 + } + + throw new Error(errorMessage); + } + + const data = await response.json(); + + return { + data, + status: response.status, + }; + } catch (error) { + if (error instanceof Error) { + throw error; + } + throw new Error('알 수 없는 오류가 발생했습니다.'); + } + } + + // ===== 인증 관련 API ===== + + /** + * 로그인 + */ + async login(email: string, password: string) { + return this.request('/auth/login', { + method: 'POST', + body: { email, password }, + }); + } + + /** + * 사용자 정보 조회 + */ + async getCurrentUser() { + return this.request('/auth/me'); + } + + /** + * 회원가입 + */ + async register(userData: { + email: string; + emailCode: string; + password: string; + passwordConfirm: string; + name: string; + phone: string; + gender: 'MALE' | 'FEMALE'; + birthDate: string; + }) { + return this.request('/auth/signup', { + method: 'POST', + body: userData, + }); + } + + /** + * 이메일 인증번호 전송 + */ + async sendEmailVerification(email: string) { + return this.request('/auth/verify-email/send', { + method: 'POST', + body: { email }, + }); + } + + /** + * 이메일 인증번호 확인 + */ + async verifyEmailCode(email: string, emailCode: string) { + return this.request('/auth/verify-email/confirm', { + method: 'POST', + body: { email, emailCode }, + }); + } + + /** + * 아이디 찾기 + */ + async findUserId(name: string, phone: string) { + return this.request('/auth/find-id', { + method: 'POST', + body: { name, phone }, + }); + } + + /** + * 비밀번호 재설정 이메일 전송 + */ + async sendPasswordReset(email: string) { + return this.request('/auth/password/forgot', { + method: 'POST', + body: { email }, + }); + } + + /** + * 비밀번호 재설정 코드 확인 + */ + async verifyPasswordResetCode(email: string, code: string) { + return this.request('/auth/password/confirm', { + method: 'POST', + body: { email, code }, + }); + } + + /** + * 비밀번호 재설정 + */ + async resetPassword(email: string, code: string, newPassword: string, confirmPassword: string) { + return this.request('/auth/password/reset', { + method: 'POST', + body: { email, code, newPassword, confirmPassword }, + }); + } + + /** + * 계정 삭제 + */ + async deleteAccount() { + return this.request('/auth/delete/me', { + method: 'DELETE', + }); + } + + // ===== 관리자 관련 API ===== + + /** + * 과목 리스트 조회 + */ + async getSubjects() { + return this.request('/subjects'); + } + + /** + * 과목 생성 + */ + async createSubject(subjectData: { + courseName: string; + instructorName: string; + }) { + return this.request('/subjects', { + method: 'POST', + body: subjectData, + }); + } + + /** + * 과목 수정 + */ + async updateSubject(subjectId: string, subjectData: { + courseName: string; + instructorName: string; + }) { + return this.request(`/subjects/${subjectId}`, { + method: 'PUT', + body: subjectData, + }); + } + + /** + * 과목 삭제 + */ + async deleteSubject(subjectId: string) { + return this.request(`/subjects/${subjectId}`, { + method: 'DELETE', + }); + } + + /** + * 사용자 리스트 조회 + */ + async getUsers() { + return this.request('/users'); + } + + /** + * 사용자 생성 + */ + async createUser(userData: any) { + return this.request('/users', { + method: 'POST', + body: userData, + }); + } + + /** + * 사용자 수정 + */ + async updateUser(userId: string, userData: any) { + return this.request(`/users/${userId}`, { + method: 'PUT', + body: userData, + }); + } + + /** + * 사용자 삭제 + */ + async deleteUser(userId: string) { + return this.request(`/users/${userId}`, { + method: 'DELETE', + }); + } + + // ===== 기타 API ===== + + /** + * 공지사항 조회 + */ + async getNotices() { + return this.request('/notices'); + } + + /** + * 강좌 조회 + */ + async getLessons() { + return this.request('/lessons'); + } + + /** + * 리소스 조회 + */ + async getResources() { + return this.request('/resources'); + } +} + +// 기본 API 서비스 인스턴스 생성 +const apiService = new ApiService( + process.env.NEXT_PUBLIC_API_BASE_URL || 'https://hrdi.coconutmeet.net' +); + +export default apiService; +export type { ApiResponse, RequestConfig }; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 81ae69e..0fd1b03 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -9,6 +9,7 @@ import LoginCheckboxInactiveSvg from "@/app/svgs/logincheckboxinactivesvg"; import LoginInputSvg from "@/app/svgs/inputformx"; import LoginErrorModal from "./LoginErrorModal"; import LoginOption from "@/app/login/LoginOption"; +import apiService from "@/app/lib/apiService"; export default function LoginPage() { const router = useRouter(); @@ -40,45 +41,28 @@ export default function LoginPage() { if (savedToken && cookieToken && savedToken === 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'; - - fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${savedToken}`, - }, - }) + apiService.getCurrentUser() .then(response => { - if (response.ok) { - return response.json().then(userData => { - // 계정 상태 확인 - const userStatus = userData.status || userData.userStatus; - if (userStatus === 'INACTIVE' || userStatus === 'inactive') { - // 비활성화된 계정인 경우 로그아웃 처리 - localStorage.removeItem('token'); - document.cookie = 'token=; path=/; max-age=0'; - return; - } - - // 사용자 권한 확인 - const userRole = userData.role || userData.userRole; - if (userRole === 'ADMIN' || userRole === 'admin') { - // admin 권한이면 /admin/id로 리다이렉트 - router.push('/admin/id'); - } else { - // 그 외의 경우 기존 로직대로 리다이렉트 - const searchParams = new URLSearchParams(window.location.search); - const redirectPath = searchParams.get('redirect') || '/'; - router.push(redirectPath); - } - }); - } else { - // 토큰이 유효하지 않으면 삭제 + const userData = response.data; + // 계정 상태 확인 + const userStatus = userData.status || userData.userStatus; + if (userStatus === 'INACTIVE' || userStatus === 'inactive') { + // 비활성화된 계정인 경우 로그아웃 처리 localStorage.removeItem('token'); document.cookie = 'token=; path=/; max-age=0'; + return; + } + + // 사용자 권한 확인 + const userRole = userData.role || userData.userRole; + if (userRole === 'ADMIN' || userRole === 'admin') { + // admin 권한이면 /admin/id로 리다이렉트 + router.push('/admin/id'); + } else { + // 그 외의 경우 기존 로직대로 리다이렉트 + const searchParams = new URLSearchParams(window.location.search); + const redirectPath = searchParams.get('redirect') || '/'; + router.push(redirectPath); } }) .catch(() => { @@ -121,38 +105,8 @@ export default function LoginPage() { } try { - const response = await fetch("https://hrdi.coconutmeet.net/auth/login", { - method: "POST", - headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - email: userId, - password: password - }) - }); - - 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); - setLoginErrorMessage(errorMessage); - setIsLoginErrorOpen(true); - return; - } - - const data = await response.json(); + const response = await apiService.login(userId, password); + const data = response.data; console.log("로그인 성공:", data); // 로그인 성공 시 토큰 저장 (다양한 필드명 지원) @@ -176,23 +130,14 @@ export default function LoginPage() { // 사용자 정보 가져오기 (권한 확인을 위해) try { - const userResponse = await fetch('https://hrdi.coconutmeet.net/auth/me', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}`, - }, - }); + const userResponse = await apiService.getCurrentUser(); + const userData = userResponse.data; + const userRole = userData.role || userData.userRole; - if (userResponse.ok) { - const userData = await userResponse.json(); - const userRole = userData.role || userData.userRole; - - // admin 권한이면 /admin/id로 리다이렉트 - if (userRole === 'ADMIN' || userRole === 'admin') { - router.push('/admin/id'); - return; - } + // admin 권한이면 /admin/id로 리다이렉트 + if (userRole === 'ADMIN' || userRole === 'admin') { + router.push('/admin/id'); + return; } } catch (error) { console.error("사용자 정보 조회 오류:", error); diff --git a/src/app/menu/ChangePasswordModal.tsx b/src/app/menu/ChangePasswordModal.tsx index 9d29bfd..46901b9 100644 --- a/src/app/menu/ChangePasswordModal.tsx +++ b/src/app/menu/ChangePasswordModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import ModalCloseSvg from "../svgs/closexsvg"; +import apiService from "../lib/apiService"; type Props = { open: boolean; @@ -22,6 +23,8 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif const [isCodeSent, setIsCodeSent] = useState(showVerification); const canConfirm = code.trim().length > 0; const [isVerified, setIsVerified] = useState(false); + const [isSending, setIsSending] = useState(false); + const [isVerifying, setIsVerifying] = useState(false); const hasError = !!error; // initialEmail이 변경되면 email state 업데이트 @@ -127,14 +130,32 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif />
@@ -151,13 +172,37 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif /> {isCodeSent && !hasError && !isVerified ? ( diff --git a/src/app/register/RegisterForm.tsx b/src/app/register/RegisterForm.tsx index fd76923..41cd1f1 100644 --- a/src/app/register/RegisterForm.tsx +++ b/src/app/register/RegisterForm.tsx @@ -6,6 +6,7 @@ import LoginCheckboxActiveSvg from "@/app/svgs/logincheckboxactivesvg"; import LoginCheckboxInactiveSvg from "@/app/svgs/logincheckboxinactivesvg"; import LoginInputSvg from "@/app/svgs/inputformx"; import CalendarSvg from "@/app/svgs/callendar"; +import apiService from "@/app/lib/apiService"; type Gender = "MALE" | "FEMALE" | ""; @@ -159,17 +160,7 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo async function verifyEmailCode() { try { - const response = await fetch( - "https://hrdi.coconutmeet.net/auth/verify-email/confirm", - { - method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ email: email, emailCode: emailCode }) - }); - if (!response.ok) { - console.error("이메일 인증번호 검증 실패:", response.statusText); - onOpenCodeError(); - return; - } + await apiService.verifyEmailCode(email, emailCode); // 인증 성공 시 상태 업데이트 setEmailCodeVerified(true); } @@ -183,25 +174,8 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo async function sendEmailCode() { if (!isEmailValid) return; - // INSERT_YOUR_CODE try { - const response = await fetch( - "https://hrdi.coconutmeet.net/auth/verify-email/send", - { - method: "POST", - headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ email: email }) - } - ); - if (!response.ok) { - if (response.status === 409) { - setErrors((prev) => ({ ...prev, email: "이메일이 중복되었습니다." })); - return; - } - console.error("이메일 인증번호 전송 실패:", response.statusText); - alert("인증번호 전송실패"); - return; - } + await apiService.sendEmailVerification(email); // 성공 시에만 상태 업데이트 setEmailCodeSent(true); setEmailCode(""); @@ -214,8 +188,11 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo }); } catch (error) { console.error("이메일 인증번호 전송 오류:", error); - alert("인증번호 전송실패"); - return; + if (error instanceof Error && error.message.includes("409")) { + setErrors((prev) => ({ ...prev, email: "이메일이 중복되었습니다." })); + } else { + alert("인증번호 전송실패"); + } } } @@ -224,41 +201,21 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo onOpenCodeError("이메일 인증을 완료해주세요."); return false; } + if (gender === "") { + onOpenCodeError("성별을 선택해주세요."); + return false; + } try { - const response = await fetch("https://hrdi.coconutmeet.net/auth/signup", { - method: "POST", - headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - email: email, - emailCode: emailCode, - password: password, - passwordConfirm: passwordConfirm, - name: name, - phone: phone, - gender: gender, - birthDate: birthdate - }) + await apiService.register({ + email, + emailCode, + password, + passwordConfirm, + name, + phone, + gender: gender as "MALE" | "FEMALE", + birthDate: birthdate }); - 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); - onOpenCodeError(errorMessage); - return false; - } onOpenDone(); return true; } catch (error) { diff --git a/src/app/reset-password/page.tsx b/src/app/reset-password/page.tsx index a5bc2c0..710d47f 100644 --- a/src/app/reset-password/page.tsx +++ b/src/app/reset-password/page.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import LoginInputSvg from "@/app/svgs/inputformx"; import ResetPasswordDone from "./ResetPasswordDone"; import ResetPaswordOption from "./ResetPaswordOption"; +import apiService from "@/app/lib/apiService"; export default function ResetPasswordPage() { const [isDoneOpen, setIsDoneOpen] = useState(false); @@ -40,37 +41,7 @@ export default function ResetPasswordPage() { if (!isEmailValid) return; try { - const response = await fetch("https://hrdi.coconutmeet.net/auth/password/forgot", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: email - }) - }); - - 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); - setErrors((prev) => ({ ...prev, email: errorMessage })); - return; - } - - const data = await response.json(); - console.log("인증번호 전송 성공:", data); + await apiService.sendPasswordReset(email); // 성공 시 상태 업데이트 setEmailCodeSent(true); setEmailCode(""); @@ -90,42 +61,7 @@ export default function ResetPasswordPage() { async function verifyEmailCode() { try { - const response = await fetch("https://hrdi.coconutmeet.net/auth/password/confirm", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: email, - emailCode: emailCode - }) - }); - - 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})`; - } - } - // 특정 에러 메시지를 사용자 친화적인 메시지로 변경 - if (errorMessage.includes("잘못되었거나 만료된 코드") || errorMessage.includes("잘못되었거나") || errorMessage.includes("만료된")) { - errorMessage = "올바르지 않은 인증번호입니다. 인증번호를 확인해주세요."; - } - console.error("인증번호 확인 실패:", errorMessage); - setErrors((prev) => ({ ...prev, emailCode: errorMessage })); - return; - } - - const data = await response.json(); - console.log("인증번호 확인 성공:", data); + await apiService.verifyPasswordResetCode(email, emailCode); // 인증 성공 시 상태 업데이트 setEmailCodeVerified(true); // 성공 시 에러 메시지 제거 @@ -135,7 +71,11 @@ export default function ResetPasswordPage() { return next; }); } catch (error) { - const errorMessage = error instanceof Error ? error.message : "네트워크 오류가 발생했습니다."; + let errorMessage = error instanceof Error ? error.message : "네트워크 오류가 발생했습니다."; + // 특정 에러 메시지를 사용자 친화적인 메시지로 변경 + if (errorMessage.includes("잘못되었거나 만료된 코드") || errorMessage.includes("잘못되었거나") || errorMessage.includes("만료된")) { + errorMessage = "올바르지 않은 인증번호입니다. 인증번호를 확인해주세요."; + } console.error("인증번호 확인 오류:", errorMessage); setErrors((prev) => ({ ...prev, emailCode: errorMessage })); } @@ -146,40 +86,7 @@ export default function ResetPasswordPage() { if (!validateAll()) return; try { - const response = await fetch("https://hrdi.coconutmeet.net/auth/password/reset", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - email: email, - emailCode: emailCode, - newPassword: password, - newPasswordConfirm: passwordConfirm - }) - }); - - 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); - setErrors((prev) => ({ ...prev, submit: errorMessage })); - return; - } - - const data = await response.json(); - console.log("비밀번호 재설정 성공:", data); + await apiService.resetPassword(email, emailCode, password, passwordConfirm); // 성공 시 완료 오버레이 표시 setIsDoneOpen(true); } catch (error) {