어드민 회원 가입신청 기능 추가

This commit is contained in:
2026-01-22 16:11:55 +09:00
parent 64d9fb8c6c
commit 1f2c39869c
4 changed files with 190 additions and 74 deletions

View File

@@ -73,7 +73,7 @@ const App = () => {
> >
<Routes> <Routes>
{/* 1. 로그인 여부와 관계없이 항상 독립적으로 표시되는 페이지 */} {/* 1. 로그인 여부와 관계없이 항상 독립적으로 표시되는 페이지 */}
<Route path="/register" element={<Register />} /> <Route path="/admin/member/register" element={<Register />} />
<Route path="/404" element={<Page404 />} /> <Route path="/404" element={<Page404 />} />
<Route path="/500" element={<Page500 />} /> <Route path="/500" element={<Page500 />} />

View File

@@ -62,3 +62,9 @@ export const deleteAdminMember = async (memberSeq: number): Promise<AdminMemberR
const response = await axios.post<AdminMemberResponse>('/admin/member/delete', { memberSeq }); const response = await axios.post<AdminMemberResponse>('/admin/member/delete', { memberSeq });
return response.data; return response.data;
}; };
// 회원 가입 (비로그인 상태에서 회원가입)
export const registerAdminMember = async (member: Partial<AdminMember>): Promise<AdminMemberResponse> => {
const response = await axios.post<AdminMemberResponse>('/admin/member/register', member);
return response.data;
};

View File

@@ -53,7 +53,11 @@ const Login = () => {
setError('쿠키에서 accessToken을 찾을 수 없습니다.'); setError('쿠키에서 accessToken을 찾을 수 없습니다.');
} }
} else { } else {
setError(response.resultMessage || '로그인에 실패했습니다.'); let errorMsg = response.resultMessage || '로그인에 실패했습니다.'
if (response.resultData && typeof response.resultData === 'string') {
errorMsg += `\n${response.resultData}`
}
setError(errorMsg)
} }
} catch (err: any) { } catch (err: any) {
setError(err.response?.data?.message || '로그인 중 오류가 발생했습니다.'); setError(err.response?.data?.message || '로그인 중 오류가 발생했습니다.');
@@ -65,16 +69,16 @@ const Login = () => {
return ( return (
<div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center"> <div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center">
<CContainer> <CContainer fluid>
<CRow className="justify-content-center"> <CRow className="justify-content-center">
<CCol md={8}> <CCol md={8}>
<CCardGroup> <CCardGroup style={{ minWidth: '300px' }}>
<CCard className="p-4"> <CCard className="p-4">
<CCardBody> <CCardBody>
<CForm onSubmit={handleSubmit}> <CForm onSubmit={handleSubmit}>
<h1>Login</h1> <h1>Login</h1>
<p className="text-body-secondary">Sign In to your account</p> <p className="text-body-secondary">Sign In to your account</p>
{error && <div className="text-danger mb-3">{error}</div>} {error && <div className="text-danger mb-3" style={{ whiteSpace: 'pre-line' }}>{error}</div>}
<CInputGroup className="mb-3"> <CInputGroup className="mb-3">
<CInputGroupText> <CInputGroupText>
<CIcon icon={cilUser} /> <CIcon icon={cilUser} />
@@ -108,16 +112,19 @@ const Login = () => {
</CForm> </CForm>
</CCardBody> </CCardBody>
</CCard> </CCard>
<CCard className="text-white bg-primary py-5"> <CCard className="text-white bg-primary py-5" style={{ minWidth: '300px' }}>
<CCardBody className="text-center"> <CCardBody className="text-center">
<div> <div>
<h2>Sign up</h2> <h2></h2>
<br/>
<p> <p>
, . <br/>
<br/>
.
</p> </p>
<Link to="/register"> <Link to="/admin/member/register">
<CButton color="primary" className="mt-3" active tabIndex={-1}> <CButton color="primary" className="mt-3" active tabIndex={-1}>
Register Now!
</CButton> </CButton>
</Link> </Link>
</div> </div>

View File

@@ -1,71 +1,174 @@
import React from 'react' import React, { useState, useEffect } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { import {
CButton, CButton,
CCard, CCard,
CCardBody, CCardBody,
CCol, CCol,
CContainer, CContainer,
CForm, CForm,
CFormInput, CFormInput,
CInputGroup, CFormLabel,
CInputGroupText, CRow,
CRow, CSpinner,
} from '@coreui/react' } from '@coreui/react'
import CIcon from '@coreui/icons-react' import { registerAdminMember } from 'src/services/adminMemberService'
import { cilLockLocked, cilUser } from '@coreui/icons' import { logout } from 'src/axios/authService'
const Register = () => { const Register = () => {
return ( const navigate = useNavigate()
<div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center"> const [loading, setLoading] = useState(false)
<CContainer>
<CRow className="justify-content-center"> // 회원가입 페이지 진입 시 기존 토큰 제거
<CCol md={9} lg={7} xl={6}> useEffect(() => {
<CCard className="mx-4"> logout()
<CCardBody className="p-4"> }, [])
<CForm> const [formData, setFormData] = useState({
<h1>Register</h1> memberId: '',
<p className="text-body-secondary">Create your account</p> memberName: '',
<CInputGroup className="mb-3"> password: '',
<CInputGroupText> passwordConfirm: '',
<CIcon icon={cilUser} /> })
</CInputGroupText>
<CFormInput placeholder="Username" autoComplete="username" /> const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
</CInputGroup> const { name, value } = e.target
<CInputGroup className="mb-3"> setFormData(prev => ({
<CInputGroupText>@</CInputGroupText> ...prev,
<CFormInput placeholder="Email" autoComplete="email" /> [name]: value,
</CInputGroup> }))
<CInputGroup className="mb-3"> }
<CInputGroupText>
<CIcon icon={cilLockLocked} /> const handleSubmit = async (e: React.FormEvent) => {
</CInputGroupText> e.preventDefault()
<CFormInput
type="password" // 유효성 검사
placeholder="Password" if (!formData.memberId.trim()) {
autoComplete="new-password" alert('어드민 회원 ID를 입력해주세요.')
/> return
</CInputGroup> }
<CInputGroup className="mb-4"> if (!formData.memberName.trim()) {
<CInputGroupText> alert('이름을 입력해주세요.')
<CIcon icon={cilLockLocked} /> return
</CInputGroupText> }
<CFormInput if (!formData.password) {
type="password" alert('비밀번호를 입력해주세요.')
placeholder="Repeat password" return
autoComplete="new-password" }
/> if (formData.password !== formData.passwordConfirm) {
</CInputGroup> alert('비밀번호가 일치하지 않습니다.')
<div className="d-grid"> return
<CButton color="success">Create Account</CButton> }
</div>
</CForm> setLoading(true)
</CCardBody> try {
</CCard> const response = await registerAdminMember({
</CCol> memberId: formData.memberId,
</CRow> memberName: formData.memberName,
</CContainer> password: formData.password,
</div> })
)
if (response.resultCode === '200') {
alert('회원가입이 완료되었습니다. 관리자 승인 후 로그인이 가능합니다.')
navigate('/login')
} else {
alert(response.resultMessage || '회원가입에 실패했습니다.')
}
} catch (error) {
console.error('회원가입 실패:', error)
alert('회원가입에 실패했습니다.')
} finally {
setLoading(false)
}
}
return (
<div className="bg-body-tertiary min-vh-100 d-flex flex-row align-items-center">
<CContainer>
<CRow className="justify-content-center">
<CCol md={9} lg={7} xl={6}>
<CCard className="mx-4">
<CCardBody className="p-4">
<CForm onSubmit={handleSubmit}>
<h1> </h1>
<p className="text-body-secondary"> </p>
<div className="mb-3">
<CFormLabel htmlFor="memberId"> ID *</CFormLabel>
<CFormInput
type="text"
id="memberId"
name="memberId"
value={formData.memberId}
onChange={handleInputChange}
placeholder="어드민 회원 ID를 입력하세요"
autoComplete="username"
/>
</div>
<div className="mb-3">
<CFormLabel htmlFor="memberName"> *</CFormLabel>
<CFormInput
type="text"
id="memberName"
name="memberName"
value={formData.memberName}
onChange={handleInputChange}
placeholder="이름을 입력하세요"
autoComplete="name"
/>
</div>
<div className="mb-3">
<CFormLabel htmlFor="password"> *</CFormLabel>
<CFormInput
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleInputChange}
placeholder="비밀번호를 입력하세요"
autoComplete="new-password"
/>
</div>
<div className="mb-3">
<CFormLabel htmlFor="passwordConfirm"> *</CFormLabel>
<CFormInput
type="password"
id="passwordConfirm"
name="passwordConfirm"
value={formData.passwordConfirm}
onChange={handleInputChange}
placeholder="비밀번호를 다시 입력하세요"
autoComplete="new-password"
/>
</div>
<div className="d-grid">
<CButton color="success" type="submit" disabled={loading}>
{loading ? (
<>
<CSpinner size="sm" className="me-2" />
...
</>
) : (
'회원가입'
)}
</CButton>
</div>
<div className="text-center mt-3">
<span className="text-body-secondary"> ? </span>
<Link to="/login"></Link>
</div>
</CForm>
</CCardBody>
</CCard>
</CCol>
</CRow>
</CContainer>
</div>
)
} }
export default Register export default Register