From ef5c5e01921e809d8e6a686f5141deac33dd736b Mon Sep 17 00:00:00 2001 From: Choe JinHyeong Date: Sun, 19 Jan 2025 15:45:43 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat=20:=20EmailVerification,=20SignUp?= =?UTF-8?q?,=20Login?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이메일 인증 -> 인증확인 - 회원가입 기능 - 로그인 기능 --- src/Pages/BackendQuestionPage.tsx | 2 +- src/Pages/CompletedPage.tsx | 2 +- src/Pages/DesignQuestionPage.tsx | 2 +- src/Pages/FrontendQuestionPage.tsx | 2 +- src/Pages/LoginPage.tsx | 121 ++++++++++++++++---- src/Pages/MyPage.tsx | 15 +++ src/Pages/ProjectIntroPage.tsx | 2 +- src/Pages/SignupPage.tsx | 176 +++++++++++++++++++++-------- src/Pages/WarningPage.tsx | 1 - src/store/useAuthStore.ts | 4 +- 10 files changed, 248 insertions(+), 79 deletions(-) diff --git a/src/Pages/BackendQuestionPage.tsx b/src/Pages/BackendQuestionPage.tsx index bc1932c..f2264e0 100644 --- a/src/Pages/BackendQuestionPage.tsx +++ b/src/Pages/BackendQuestionPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import styles from '../assets/BackendQuestionPage.module.css'; diff --git a/src/Pages/CompletedPage.tsx b/src/Pages/CompletedPage.tsx index 7a220bc..4b8b5a9 100644 --- a/src/Pages/CompletedPage.tsx +++ b/src/Pages/CompletedPage.tsx @@ -1,4 +1,4 @@ -import React from "react"; + import { useNavigate } from "react-router-dom"; import styles from '../assets/CompletedPage.module.css'; diff --git a/src/Pages/DesignQuestionPage.tsx b/src/Pages/DesignQuestionPage.tsx index bdb3a49..3bb1b4a 100644 --- a/src/Pages/DesignQuestionPage.tsx +++ b/src/Pages/DesignQuestionPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import styles from '../assets/DesignQuestionPage.module.css'; diff --git a/src/Pages/FrontendQuestionPage.tsx b/src/Pages/FrontendQuestionPage.tsx index 6733d23..6be3f80 100644 --- a/src/Pages/FrontendQuestionPage.tsx +++ b/src/Pages/FrontendQuestionPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import styles from '../assets/FrontendQuestionPage.module.css'; diff --git a/src/Pages/LoginPage.tsx b/src/Pages/LoginPage.tsx index bb90658..d35925d 100644 --- a/src/Pages/LoginPage.tsx +++ b/src/Pages/LoginPage.tsx @@ -1,20 +1,78 @@ +import { useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { useAuthStore } from '../store/useAuthStore' +import axios, { AxiosError } from 'axios' const LoginPage = () => { - const { isLoggedIn, logout, testLogin } = useAuthStore() + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState('') + const [showPassword, setShowPassword] = useState(false) + + const [formData, setFormData] = useState({ + email: '', + password: '' + }) + + + const { isLoggedIn, login, logout} = useAuthStore() const navigate = useNavigate() - const handleTestLogin = () => { - alert('테스트 로그인 성공, 메인페이지로 이동할게요') - testLogin() - navigate('/') + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setFormData(prev => ({ + ...prev, + [name]: value + })) + } + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError('') + + try { + const response = await axios.post('http://localhost:8080/api/v1/login', formData, { + headers: { + 'accept': '*/*', + 'Content-Type': 'application/json', + } + }) + + console.log('[로그인 응답]', response.data) + + // JWT 토큰 저장 + localStorage.setItem('token', response.data.token) + + // db 에서 유저 정보 가져오기 + + + // 로그인 상태 업데이트 + login({ + email: formData.email, + password: formData.password, + name: response.data.name || '', + department: response.data.department || '', + studentId: response.data.studentId || '', + grade: response.data.grade || '', + phone: response.data.phone || '', + }) + + navigate('/') + } catch (err) { + console.error('[로그인 에러]', err) + if (axios.isAxiosError(err)) { + setError((err as AxiosError<{ message: string }>).response?.data?.message || '로그인에 실패했습니다.') + } else { + setError('로그인 중 오류가 발생했습니다.') + } + } finally { + setIsLoading(false) + } } return (

로그인 페이지

-
강남대학교 멋쟁이 사자처럼에 오신걸 환영해요!
{isLoggedIn ? ( @@ -24,30 +82,43 @@ const LoginPage = () => {
) : (
-
- - -
- - +
+ {error &&
{error}
} +
+ + + + + + +
+ + +
+ +
- + {/* */}
)} diff --git a/src/Pages/MyPage.tsx b/src/Pages/MyPage.tsx index ac759e6..e2f4d27 100644 --- a/src/Pages/MyPage.tsx +++ b/src/Pages/MyPage.tsx @@ -78,6 +78,21 @@ const MyPage = () => {
비밀번호 변경
+ +
+
+
비밀번호 확인
+
diff --git a/src/Pages/ProjectIntroPage.tsx b/src/Pages/ProjectIntroPage.tsx index 7c7b7bf..0c5029e 100644 --- a/src/Pages/ProjectIntroPage.tsx +++ b/src/Pages/ProjectIntroPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; import styles from '../assets/ProjectIntroPage.module.css'; import 침 from '../assets/images/chim.jpg'; diff --git a/src/Pages/SignupPage.tsx b/src/Pages/SignupPage.tsx index 713045d..1f72d18 100644 --- a/src/Pages/SignupPage.tsx +++ b/src/Pages/SignupPage.tsx @@ -1,10 +1,12 @@ import { useState } from 'react' import { useNavigate } from 'react-router-dom' -import { useAuthStore } from '../store/useAuthStore' const SignupPage = () => { const [isCompleted, setIsCompleted] = useState(false) const [showPassword, setShowPassword] = useState(false) + const [isEmailVerified, setIsEmailVerified] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState('') const [formData, setFormData] = useState({ name: '', department: '', @@ -14,9 +16,13 @@ const SignupPage = () => { email: '', verificationCode: '', password: '', - passwordConfirm: '', + passwordCheck: '', }) - const { login } = useAuthStore() + + const [sendResponseMessage, setSendResponseMessage] = useState('') + const [verificationMessage, setVerificationMessage] = useState('') + + const navigate = useNavigate() const handleChange = (e: React.ChangeEvent) => { @@ -27,47 +33,105 @@ const SignupPage = () => { })) } - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() + const handleSendVerification = async () => { + setIsLoading(true) + setError('') + try { + const url = `http://localhost:8080/api/v1/send?email=${formData.email}&verifyCode=string` + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + + if (response.ok) { + setSendResponseMessage(await response.text()) + } else { + throw new Error('인증번호 전송에 실패했습니다.') + } + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError('인증번호 전송에 실패했습니다.') + } + console.error('[이메일 인증 에러]', err) + } finally { + setIsLoading(false) + } + } - // test code - alert('임시 회원가입 성공 - 폼 데이터 콘솔로 확인해주세요') - console.log('회원가입 폼 데이터:', JSON.stringify(formData)) - login({ - name: formData.name, - department: formData.department, - studentId: formData.studentId, - grade: formData.grade, - phone: formData.phone, - email: formData.email, - }) - setIsCompleted(true) - // test code - - // try { - // const response = await fetch('백엔드URL/signup', { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - // body: JSON.stringify(formData), - // }) - - // if (response.ok) { - // alert('회원가입 성공!') - // login({ - // name: formData.name, - // department: formData.department, - // studentId: formData.studentId, - // grade: formData.grade, - // phone: formData.phone, - // email: formData.email, - // }) - // } - // } catch (error) { - // console.error('회원가입 실패:', error) - // alert('회원가입에 실패했습니다.') - // } + const handleVerification = async () => { + setIsLoading(true) + setError('') + try { + const url = `http://localhost:8080/api/v1/verify?email=${formData.email}&verifyCode=${formData.verificationCode}` + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + + if (response.ok) { + setVerificationMessage(await response.text()) + if (verificationMessage === '인증이 완료되었습니다.') { + setIsEmailVerified(true) + } + } else { + throw new Error('인증 확인에 실패했습니다.') + } + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError('인증 확인에 실패했습니다.') + } + console.error('[인증 확인 에러]', err) + } finally { + setIsLoading(false) + } + } + + const handleSignUp = async (e: React.FormEvent) => { + e.preventDefault() + setIsLoading(true) + setError('') + + try { + const signUpData = { + name: formData.name, + department: formData.department, + studentId: formData.studentId, + phone: formData.phone, + email: formData.email, + password: formData.password, + passwordCheck: formData.passwordCheck, + grade: formData.grade + } + + const response = await fetch('http://localhost:8080/api/v1/sign-up', { + method: 'POST', + headers: { + 'accept': '*/*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(signUpData) + }) + + if (response.ok) { + alert('회원가입이 완료되었습니다.') + setIsCompleted(true) + } else { + throw new Error('회원가입에 실패했습니다.') + } + } catch (err) { + setError(err instanceof Error ? err.message : '회원가입에 실패했습니다.') + console.error('[회원가입 에러]', err) + } finally { + setIsLoading(false) + } } if (isCompleted) { @@ -83,7 +147,8 @@ const SignupPage = () => { return (

회원가입

-
+ {error &&
{error}
} +
이름
{ name='email' value={formData.email} onChange={handleChange} + disabled={isEmailVerified} /> - + + {sendResponseMessage &&
인증코드 전송 완료
}
인증번호
{ name='verificationCode' value={formData.verificationCode} onChange={handleChange} + disabled={isEmailVerified} /> + + {isEmailVerified &&
인증 완료
}
비밀번호
{
비밀번호 확인
diff --git a/src/Pages/WarningPage.tsx b/src/Pages/WarningPage.tsx index 965f1ec..62542a0 100644 --- a/src/Pages/WarningPage.tsx +++ b/src/Pages/WarningPage.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { useNavigate } from "react-router-dom"; import styles from '../assets/WarningPage.module.css'; diff --git a/src/store/useAuthStore.ts b/src/store/useAuthStore.ts index 0531f2e..428b480 100644 --- a/src/store/useAuthStore.ts +++ b/src/store/useAuthStore.ts @@ -7,6 +7,7 @@ interface User { grade: string phone: string email: string + password: string } interface AuthState { @@ -22,5 +23,6 @@ export const useAuthStore = create((set) => ({ user: null, login: (user) => set({ isLoggedIn: true, user }), logout: () => set({ isLoggedIn: false, user: null }), - testLogin: () => set({ isLoggedIn: true, user: { name: '테스트', department: '테스트 학부', studentId: '12345678', grade: '1', phone: '01012345678', email: 'test@kangnam.ac.kr' } }), + testLogin: () => + set({ isLoggedIn: true, user: { name: '테스트', department: '테스트 학부', studentId: '12345678', grade: '1', phone: '01012345678', email: 'test@kangnam.ac.kr', password: '0000' } }), }))