Compare commits
2 Commits
0963cfdf5b
...
c021303182
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c021303182 | ||
|
|
c91dd4a30f |
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,115 +83,113 @@ export default function FindIdPage() {
|
|||||||
<div className="flex-1 flex items-center justify-center w-full py-6">
|
<div className="flex-1 flex items-center justify-center w-full py-6">
|
||||||
<div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full relative">
|
<div className="rounded-xl bg-white max-w-[560px] px-[40px] w-full relative">
|
||||||
|
|
||||||
<div className="my-15 flex flex-col items-center">
|
<div className="my-15 flex flex-col items-center">
|
||||||
<div className="text-[24px] font-extrabold leading-[150%] text-neutral-700">
|
<div className="text-[24px] font-extrabold leading-[150%] text-neutral-700">
|
||||||
아이디 찾기
|
아이디 찾기
|
||||||
</div>
|
|
||||||
<p className="text-[18px] leading-[150%] text-[#6c7682] mt-[8px] text-center">
|
|
||||||
가입 시 등록한 회원정보를 입력해 주세요.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<label htmlFor="name" className="text-[15px] font-semibold text-[#6c7682]">
|
|
||||||
이름
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
|
||||||
<input
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => {
|
|
||||||
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 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onMouseDown={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setName("");
|
|
||||||
}}
|
|
||||||
aria-label="입력 지우기"
|
|
||||||
className="absolute right-3 top-[32px] -translate-y-1/2 cursor-pointer flex items-center justify-center">
|
|
||||||
<LoginInputSvg />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{errors.name && <p className="text-error text-[13px] leading-tight">{errors.name}</p>}
|
<p className="text-[18px] leading-[150%] text-[#6c7682] mt-[8px] text-center">
|
||||||
|
가입 시 등록한 회원정보를 입력해 주세요.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 mt-6">
|
<form onSubmit={handleSubmit}>
|
||||||
<label htmlFor="phone" className="text-[15px] font-semibold text-[#6c7682]">
|
<div className="space-y-2">
|
||||||
휴대폰 번호
|
<label htmlFor="name" className="text-[15px] font-semibold text-[#6c7682]">
|
||||||
</label>
|
이름
|
||||||
<div className="relative">
|
</label>
|
||||||
<input
|
<div className="relative">
|
||||||
id="phone"
|
<input
|
||||||
name="phone"
|
id="name"
|
||||||
type="tel"
|
name="name"
|
||||||
inputMode="numeric"
|
value={name}
|
||||||
value={formatPhoneNumber(phone)}
|
onChange={(e) => {
|
||||||
onChange={(e) => {
|
setName(e.target.value);
|
||||||
const numbersOnly = e.target.value.replace(/[^0-9]/g, "");
|
if (errors.name) {
|
||||||
setPhone(numbersOnly);
|
setErrors((prev) => {
|
||||||
if (errors.phone) {
|
const next = { ...prev };
|
||||||
setErrors((prev) => {
|
delete next.name;
|
||||||
const next = { ...prev };
|
return next;
|
||||||
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 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onMouseDown={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setPhone("");
|
|
||||||
}}
|
}}
|
||||||
aria-label="입력 지우기"
|
onFocus={() => setFocused((p) => ({ ...p, name: true }))}
|
||||||
className="absolute right-3 top-[32px] -translate-y-1/2 cursor-pointer flex items-center justify-center">
|
onBlur={() => setFocused((p) => ({ ...p, name: false }))}
|
||||||
<LoginInputSvg />
|
placeholder="이름을 입력해 주세요."
|
||||||
</button>
|
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 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setName("");
|
||||||
|
}}
|
||||||
|
aria-label="입력 지우기"
|
||||||
|
className="absolute right-3 top-[32px] -translate-y-1/2 cursor-pointer flex items-center justify-center">
|
||||||
|
<LoginInputSvg />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{errors.name && <p className="text-error text-[13px] leading-tight">{errors.name}</p>}
|
||||||
</div>
|
</div>
|
||||||
{errors.phone && <p className="text-error text-[13px] leading-tight">{errors.phone}</p>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<div className="space-y-2 mt-6">
|
||||||
type="submit"
|
<label htmlFor="phone" className="text-[15px] font-semibold text-[#6c7682]">
|
||||||
className={`h-[56px] w-full rounded-lg text-[16px] font-semibold text-white transition-opacity cursor-pointer mb-3 hover:bg-[#1F2B91] mt-[60px] ${canSubmit ? "bg-active-button" : "bg-inactive-button"}`}>
|
휴대폰 번호
|
||||||
다음
|
</label>
|
||||||
</button>
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
id="phone"
|
||||||
|
name="phone"
|
||||||
|
type="tel"
|
||||||
|
inputMode="numeric"
|
||||||
|
value={formatPhoneNumber(phone)}
|
||||||
|
onChange={(e) => {
|
||||||
|
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 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setPhone("");
|
||||||
|
}}
|
||||||
|
aria-label="입력 지우기"
|
||||||
|
className="absolute right-3 top-[32px] -translate-y-1/2 cursor-pointer flex items-center justify-center">
|
||||||
|
<LoginInputSvg />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{errors.phone && <p className="text-error text-[13px] leading-tight">{errors.phone}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className={`h-[56px] w-full rounded-lg text-[16px] font-semibold text-white transition-opacity cursor-pointer mb-3 hover:bg-[#1F2B91] mt-[60px] ${canSubmit ? "bg-active-button" : "bg-inactive-button"}`}>
|
||||||
|
다음
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
364
src/app/lib/apiService.ts
Normal file
364
src/app/lib/apiService.ts
Normal 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 };
|
||||||
@@ -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,45 +41,28 @@ 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') {
|
// 비활성화된 계정인 경우 로그아웃 처리
|
||||||
// 비활성화된 계정인 경우 로그아웃 처리
|
|
||||||
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 {
|
|
||||||
// 토큰이 유효하지 않으면 삭제
|
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
document.cookie = 'token=; path=/; max-age=0';
|
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(() => {
|
.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,23 +130,14 @@ 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: {
|
const userRole = userData.role || userData.userRole;
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userResponse.ok) {
|
// admin 권한이면 /admin/id로 리다이렉트
|
||||||
const userData = await userResponse.json();
|
if (userRole === 'ADMIN' || userRole === 'admin') {
|
||||||
const userRole = userData.role || userData.userRole;
|
router.push('/admin/id');
|
||||||
|
return;
|
||||||
// admin 권한이면 /admin/id로 리다이렉트
|
|
||||||
if (userRole === 'ADMIN' || userRole === 'admin') {
|
|
||||||
router.push('/admin/id');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("사용자 정보 조회 오류:", error);
|
console.error("사용자 정보 조회 오류:", error);
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
setRequireCode(true);
|
if (!email.trim()) {
|
||||||
setIsCodeSent(true);
|
setError("이메일을 입력해 주세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiService.sendPasswordReset(email);
|
||||||
|
setRequireCode(true);
|
||||||
|
setIsCodeSent(true);
|
||||||
|
} 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>
|
||||||
@@ -146,18 +167,42 @@ export default function ChangePasswordModal({ open, onClose, onSubmit, showVerif
|
|||||||
type="text"
|
type="text"
|
||||||
value={code}
|
value={code}
|
||||||
onChange={(e) => setCode(e.target.value)}
|
onChange={(e) => setCode(e.target.value)}
|
||||||
className="h-10 flex-1 rounded-[8px] border border-input-border bg-white px-3 text-[16px] leading-[1.5] text-neutral-700 placeholder:text-text-placeholder-alt outline-none"
|
className="h-10 flex-1 rounded-[8px] border border-input-border bg-white px-3 text-[16px] leading-normal text-neutral-700 placeholder:text-text-placeholder-alt outline-none"
|
||||||
placeholder="인증번호를 입력해 주세요."
|
placeholder="인증번호를 입력해 주세요."
|
||||||
/>
|
/>
|
||||||
<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 ? (
|
||||||
|
|||||||
@@ -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);
|
||||||
alert("인증번호 전송실패");
|
if (error instanceof Error && error.message.includes("409")) {
|
||||||
return;
|
setErrors((prev) => ({ ...prev, email: "이메일이 중복되었습니다." }));
|
||||||
|
} else {
|
||||||
|
alert("인증번호 전송실패");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,41 +201,21 @@ export default function RegisterForm({ onOpenDone, onOpenCodeError }: RegisterFo
|
|||||||
onOpenCodeError("이메일 인증을 완료해주세요.");
|
onOpenCodeError("이메일 인증을 완료해주세요.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (gender === "") {
|
||||||
|
onOpenCodeError("성별을 선택해주세요.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://hrdi.coconutmeet.net/auth/signup", {
|
await apiService.register({
|
||||||
method: "POST",
|
email,
|
||||||
headers: { "Content-Type": "application/json", },
|
emailCode,
|
||||||
body: JSON.stringify({
|
password,
|
||||||
email: email,
|
passwordConfirm,
|
||||||
emailCode: emailCode,
|
name,
|
||||||
password: password,
|
phone,
|
||||||
passwordConfirm: passwordConfirm,
|
gender: gender as "MALE" | "FEMALE",
|
||||||
name: name,
|
birthDate: birthdate
|
||||||
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;
|
|
||||||
}
|
|
||||||
onOpenDone();
|
onOpenDone();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user