API 재활용1

This commit is contained in:
wallace
2025-11-28 14:26:54 +09:00
parent 0963cfdf5b
commit c91dd4a30f
7 changed files with 586 additions and 438 deletions

View File

@@ -1,3 +1,5 @@
import apiService from "@/app/lib/apiService";
export type Course = { export type Course = {
id: string; id: string;
courseName: string; courseName: string;
@@ -10,40 +12,8 @@ export type Course = {
// 과목 리스트 조회 API // 과목 리스트 조회 API
export async function getCourses(): Promise<Course[]> { export async function getCourses(): Promise<Course[]> {
try { try {
const token = typeof window !== 'undefined' const response = await apiService.getSubjects();
? (localStorage.getItem('token') || document.cookie const data = response.data;
.split('; ')
.find(row => row.startsWith('token='))
?.split('=')[1])
: null;
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 응답 구조 확인 // 디버깅: API 응답 구조 확인
console.log('🔍 [getCourses] API 원본 응답:', data); console.log('🔍 [getCourses] API 원본 응답:', data);

View File

@@ -5,6 +5,7 @@ import IdFindDone from "./IdFindDone";
import IdFindFailed from "./IdFindFailed"; import IdFindFailed from "./IdFindFailed";
import FindIdOption from "./FindIdOption"; import FindIdOption from "./FindIdOption";
import LoginInputSvg from "@/app/svgs/inputformx"; import LoginInputSvg from "@/app/svgs/inputformx";
import apiService from "@/app/lib/apiService";
export default function FindIdPage() { export default function FindIdPage() {
const [isDoneOpen, setIsDoneOpen] = useState(false); const [isDoneOpen, setIsDoneOpen] = useState(false);
@@ -49,39 +50,8 @@ export default function FindIdPage() {
if (!validateAll()) return; if (!validateAll()) return;
try { try {
const response = await fetch('https://hrdi.coconutmeet.net/auth/find-id', { const response = await apiService.findUserId(name, phone);
method: 'POST', const data = response.data;
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
phone: phone,
}),
});
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 배열에서 첫 번째 이메일 가져오기 // body의 emails 배열에서 첫 번째 이메일 가져오기
if (data.emails && Array.isArray(data.emails) && data.emails.length > 0) { if (data.emails && Array.isArray(data.emails) && data.emails.length > 0) {
@@ -90,17 +60,9 @@ export default function FindIdPage() {
setFoundUserId(data.email); setFoundUserId(data.email);
} }
// 가입일 정보는 API 응답에 없음
// // header의 date 값 가져오기
// const headerDate = response.headers.get('date');
// if (headerDate) {
// setJoinedAt(formatJoinedAt(headerDate));
// }
setIsDoneOpen(true); setIsDoneOpen(true);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : '네트워크 오류가 발생했습니다.'; console.error('아이디 찾기 오류:', error);
console.error('아이디 찾기 오류:', errorMessage);
setIsFailedOpen(true); setIsFailedOpen(true);
} }
} }
@@ -153,8 +115,7 @@ export default function FindIdPage() {
onFocus={() => setFocused((p) => ({ ...p, name: true }))} onFocus={() => setFocused((p) => ({ ...p, name: true }))}
onBlur={() => setFocused((p) => ({ ...p, name: false }))} onBlur={() => setFocused((p) => ({ ...p, name: false }))}
placeholder="이름을 입력해 주세요." 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] ${ 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
errors.name
? "border-error focus:border-error" ? "border-error focus:border-error"
: "border-neutral-40 focus:border-neutral-700" : "border-neutral-40 focus:border-neutral-700"
}`} }`}
@@ -200,8 +161,7 @@ export default function FindIdPage() {
onFocus={() => setFocused((p) => ({ ...p, phone: true }))} onFocus={() => setFocused((p) => ({ ...p, phone: true }))}
onBlur={() => setFocused((p) => ({ ...p, phone: false }))} onBlur={() => setFocused((p) => ({ ...p, phone: false }))}
placeholder="-없이 입력해 주세요." 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] ${ 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
errors.phone
? "border-error focus:border-error" ? "border-error focus:border-error"
: "border-neutral-40 focus:border-neutral-700" : "border-neutral-40 focus:border-neutral-700"
}`} }`}

364
src/app/lib/apiService.ts Normal file
View File

@@ -0,0 +1,364 @@
/**
* 중앙화된 API 서비스
* 모든 외부 API 요청을 이 모듈에서 관리
*/
interface ApiResponse<T = any> {
data: T;
status: number;
message?: string;
}
interface RequestConfig {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
headers?: Record<string, string>;
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<string, string> {
const headers: Record<string, string> = {
'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<Response> {
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<T = any>(
endpoint: string,
config: RequestConfig = {}
): Promise<ApiResponse<T>> {
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 };

View File

@@ -9,6 +9,7 @@ import LoginCheckboxInactiveSvg from "@/app/svgs/logincheckboxinactivesvg";
import LoginInputSvg from "@/app/svgs/inputformx"; import LoginInputSvg from "@/app/svgs/inputformx";
import LoginErrorModal from "./LoginErrorModal"; import LoginErrorModal from "./LoginErrorModal";
import LoginOption from "@/app/login/LoginOption"; import LoginOption from "@/app/login/LoginOption";
import apiService from "@/app/lib/apiService";
export default function LoginPage() { export default function LoginPage() {
const router = useRouter(); const router = useRouter();
@@ -40,20 +41,9 @@ export default function LoginPage() {
if (savedToken && cookieToken && savedToken === cookieToken) { if (savedToken && cookieToken && savedToken === cookieToken) {
// 토큰이 유효한지 확인 // 토큰이 유효한지 확인
const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL apiService.getCurrentUser()
? `${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}`,
},
})
.then(response => { .then(response => {
if (response.ok) { const userData = response.data;
return response.json().then(userData => {
// 계정 상태 확인 // 계정 상태 확인
const userStatus = userData.status || userData.userStatus; const userStatus = userData.status || userData.userStatus;
if (userStatus === 'INACTIVE' || userStatus === 'inactive') { if (userStatus === 'INACTIVE' || userStatus === 'inactive') {
@@ -74,12 +64,6 @@ export default function LoginPage() {
const redirectPath = searchParams.get('redirect') || '/'; const redirectPath = searchParams.get('redirect') || '/';
router.push(redirectPath); router.push(redirectPath);
} }
});
} else {
// 토큰이 유효하지 않으면 삭제
localStorage.removeItem('token');
document.cookie = 'token=; path=/; max-age=0';
}
}) })
.catch(() => { .catch(() => {
// 에러 발생 시 토큰 삭제 // 에러 발생 시 토큰 삭제
@@ -121,38 +105,8 @@ export default function LoginPage() {
} }
try { try {
const response = await fetch("https://hrdi.coconutmeet.net/auth/login", { const response = await apiService.login(userId, password);
method: "POST", const data = response.data;
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();
console.log("로그인 성공:", data); console.log("로그인 성공:", data);
// 로그인 성공 시 토큰 저장 (다양한 필드명 지원) // 로그인 성공 시 토큰 저장 (다양한 필드명 지원)
@@ -176,16 +130,8 @@ export default function LoginPage() {
// 사용자 정보 가져오기 (권한 확인을 위해) // 사용자 정보 가져오기 (권한 확인을 위해)
try { try {
const userResponse = await fetch('https://hrdi.coconutmeet.net/auth/me', { const userResponse = await apiService.getCurrentUser();
method: 'GET', const userData = userResponse.data;
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (userResponse.ok) {
const userData = await userResponse.json();
const userRole = userData.role || userData.userRole; const userRole = userData.role || userData.userRole;
// admin 권한이면 /admin/id로 리다이렉트 // admin 권한이면 /admin/id로 리다이렉트
@@ -193,7 +139,6 @@ export default function LoginPage() {
router.push('/admin/id'); router.push('/admin/id');
return; return;
} }
}
} catch (error) { } catch (error) {
console.error("사용자 정보 조회 오류:", error); console.error("사용자 정보 조회 오류:", error);
// 사용자 정보 조회 실패 시에도 기존 로직대로 진행 // 사용자 정보 조회 실패 시에도 기존 로직대로 진행

View File

@@ -2,6 +2,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import ModalCloseSvg from "../svgs/closexsvg"; import ModalCloseSvg from "../svgs/closexsvg";
import apiService from "../lib/apiService";
type Props = { type Props = {
open: boolean; open: boolean;
@@ -22,6 +23,8 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif
const [isCodeSent, setIsCodeSent] = useState<boolean>(showVerification); const [isCodeSent, setIsCodeSent] = useState<boolean>(showVerification);
const canConfirm = code.trim().length > 0; const canConfirm = code.trim().length > 0;
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const [isSending, setIsSending] = useState(false);
const [isVerifying, setIsVerifying] = useState(false);
const hasError = !!error; const hasError = !!error;
// initialEmail이 변경되면 email state 업데이트 // initialEmail이 변경되면 email state 업데이트
@@ -127,14 +130,32 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif
/> />
<button <button
type="button" type="button"
onClick={() => { onClick={async () => {
if (!email.trim()) {
setError("이메일을 입력해 주세요.");
return;
}
setIsSending(true);
setError(null);
try {
await apiService.sendPasswordReset(email);
setRequireCode(true); setRequireCode(true);
setIsCodeSent(true); setIsCodeSent(true);
setError(null); } catch (err) {
setError(err instanceof Error ? err.message : "인증번호 전송에 실패했습니다.");
} finally {
setIsSending(false);
}
}} }}
className="h-10 w-[136px] rounded-[8px] bg-bg-gray-light px-3 text-[16px] font-semibold leading-[1.5] text-neutral-700 cursor-pointer" disabled={isSending}
className={[
"h-10 w-[136px] rounded-[8px] bg-bg-gray-light px-3 text-[16px] font-semibold leading-[1.5] text-neutral-700",
isSending ? "cursor-not-allowed opacity-50" : "cursor-pointer"
].join(" ")}
> >
{isCodeSent ? "인증번호 재전송" : "인증번호 전송"} {isSending ? "전송 중..." : (isCodeSent ? "인증번호 재전송" : "인증번호 전송")}
</button> </button>
</div> </div>
</div> </div>
@@ -151,13 +172,37 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif
/> />
<button <button
type="button" type="button"
disabled={!canConfirm} disabled={!canConfirm || isVerifying}
onClick={async () => {
if (!email.trim()) {
setError("이메일을 입력해 주세요.");
return;
}
if (!code.trim()) {
setError("인증번호를 입력해 주세요.");
return;
}
setIsVerifying(true);
setError(null);
try {
await apiService.verifyPasswordResetCode(email, code);
setIsVerified(true);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : "올바르지 않은 인증번호입니다. 인증번호를 확인해주세요.");
setIsVerified(false);
} finally {
setIsVerifying(false);
}
}}
className={[ className={[
"h-10 w-[136px] rounded-[8px] px-3 text-[16px] font-semibold leading-[1.5] cursor-pointer disabled:cursor-default", "h-10 w-[136px] rounded-[8px] px-3 text-[16px] font-semibold leading-[1.5]",
canConfirm ? "bg-bg-gray-light text-basic-text" : "bg-gray-50 text-text-placeholder-alt", canConfirm && !isVerifying ? "bg-bg-gray-light text-basic-text cursor-pointer" : "bg-gray-50 text-text-placeholder-alt cursor-default",
].join(" ")} ].join(" ")}
> >
{isVerifying ? "확인 중..." : "인증번호 확인"}
</button> </button>
</div> </div>
{isCodeSent && !hasError && !isVerified ? ( {isCodeSent && !hasError && !isVerified ? (

View File

@@ -6,6 +6,7 @@ import LoginCheckboxActiveSvg from "@/app/svgs/logincheckboxactivesvg";
import LoginCheckboxInactiveSvg from "@/app/svgs/logincheckboxinactivesvg"; import LoginCheckboxInactiveSvg from "@/app/svgs/logincheckboxinactivesvg";
import LoginInputSvg from "@/app/svgs/inputformx"; import LoginInputSvg from "@/app/svgs/inputformx";
import CalendarSvg from "@/app/svgs/callendar"; import CalendarSvg from "@/app/svgs/callendar";
import apiService from "@/app/lib/apiService";
type Gender = "MALE" | "FEMALE" | ""; type Gender = "MALE" | "FEMALE" | "";
@@ -159,17 +160,7 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo
async function verifyEmailCode() { async function verifyEmailCode() {
try { try {
const response = await fetch( await apiService.verifyEmailCode(email, emailCode);
"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;
}
// 인증 성공 시 상태 업데이트 // 인증 성공 시 상태 업데이트
setEmailCodeVerified(true); setEmailCodeVerified(true);
} }
@@ -183,25 +174,8 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo
async function sendEmailCode() { async function sendEmailCode() {
if (!isEmailValid) return; if (!isEmailValid) return;
// INSERT_YOUR_CODE
try { try {
const response = await fetch( await apiService.sendEmailVerification(email);
"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;
}
// 성공 시에만 상태 업데이트 // 성공 시에만 상태 업데이트
setEmailCodeSent(true); setEmailCodeSent(true);
setEmailCode(""); setEmailCode("");
@@ -214,8 +188,11 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo
}); });
} catch (error) { } catch (error) {
console.error("이메일 인증번호 전송 오류:", error); console.error("이메일 인증번호 전송 오류:", error);
if (error instanceof Error && error.message.includes("409")) {
setErrors((prev) => ({ ...prev, email: "이메일이 중복되었습니다." }));
} else {
alert("인증번호 전송실패"); alert("인증번호 전송실패");
return; }
} }
} }
@@ -224,41 +201,21 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo
onOpenCodeError("이메일 인증을 완료해주세요."); onOpenCodeError("이메일 인증을 완료해주세요.");
return false; return false;
} }
try { if (gender === "") {
const response = await fetch("https://hrdi.coconutmeet.net/auth/signup", { onOpenCodeError("성별을 선택해주세요.");
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
})
});
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; return false;
} }
try {
await apiService.register({
email,
emailCode,
password,
passwordConfirm,
name,
phone,
gender: gender as "MALE" | "FEMALE",
birthDate: birthdate
});
onOpenDone(); onOpenDone();
return true; return true;
} catch (error) { } catch (error) {

View File

@@ -5,6 +5,7 @@ import Link from "next/link";
import LoginInputSvg from "@/app/svgs/inputformx"; import LoginInputSvg from "@/app/svgs/inputformx";
import ResetPasswordDone from "./ResetPasswordDone"; import ResetPasswordDone from "./ResetPasswordDone";
import ResetPaswordOption from "./ResetPaswordOption"; import ResetPaswordOption from "./ResetPaswordOption";
import apiService from "@/app/lib/apiService";
export default function ResetPasswordPage() { export default function ResetPasswordPage() {
const [isDoneOpen, setIsDoneOpen] = useState(false); const [isDoneOpen, setIsDoneOpen] = useState(false);
@@ -40,37 +41,7 @@ export default function ResetPasswordPage() {
if (!isEmailValid) return; if (!isEmailValid) return;
try { try {
const response = await fetch("https://hrdi.coconutmeet.net/auth/password/forgot", { await apiService.sendPasswordReset(email);
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);
// 성공 시 상태 업데이트 // 성공 시 상태 업데이트
setEmailCodeSent(true); setEmailCodeSent(true);
setEmailCode(""); setEmailCode("");
@@ -90,42 +61,7 @@ export default function ResetPasswordPage() {
async function verifyEmailCode() { async function verifyEmailCode() {
try { try {
const response = await fetch("https://hrdi.coconutmeet.net/auth/password/confirm", { await apiService.verifyPasswordResetCode(email, emailCode);
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);
// 인증 성공 시 상태 업데이트 // 인증 성공 시 상태 업데이트
setEmailCodeVerified(true); setEmailCodeVerified(true);
// 성공 시 에러 메시지 제거 // 성공 시 에러 메시지 제거
@@ -135,7 +71,11 @@ export default function ResetPasswordPage() {
return next; return next;
}); });
} catch (error) { } 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); console.error("인증번호 확인 오류:", errorMessage);
setErrors((prev) => ({ ...prev, emailCode: errorMessage })); setErrors((prev) => ({ ...prev, emailCode: errorMessage }));
} }
@@ -146,40 +86,7 @@ export default function ResetPasswordPage() {
if (!validateAll()) return; if (!validateAll()) return;
try { try {
const response = await fetch("https://hrdi.coconutmeet.net/auth/password/reset", { await apiService.resetPassword(email, emailCode, password, passwordConfirm);
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);
// 성공 시 완료 오버레이 표시 // 성공 시 완료 오버레이 표시
setIsDoneOpen(true); setIsDoneOpen(true);
} catch (error) { } catch (error) {