generated from pricelees/issue-pr-template
refactor: 회원가입 페이지 API 및 타입 수정
This commit is contained in:
parent
e1941052f9
commit
b0f67543be
6
frontend/src/api/user/userAPI.ts
Normal file
6
frontend/src/api/user/userAPI.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import apiClient from "@_api/apiClient";
|
||||
import type { UserCreateRequest, UserCreateResponse } from "./userTypes";
|
||||
|
||||
export const signup = async (data: UserCreateRequest): Promise<UserCreateResponse> => {
|
||||
return await apiClient.post('/users', data, false);
|
||||
};
|
||||
27
frontend/src/api/user/userTypes.ts
Normal file
27
frontend/src/api/user/userTypes.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export interface UserCreateRequest {
|
||||
/** not empty */
|
||||
name: string;
|
||||
|
||||
/** not empty, email format */
|
||||
email: string;
|
||||
|
||||
/** length >= 8 */
|
||||
password: string;
|
||||
|
||||
/** not empty, pattern: ^010([0-9]{3,4})([0-9]{4})$ */
|
||||
phone: string;
|
||||
|
||||
/** nullable */
|
||||
regionCode?: string | null;
|
||||
}
|
||||
|
||||
export interface UserCreateResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserContactRetrieveResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
phone: string;
|
||||
}
|
||||
@ -63,3 +63,9 @@
|
||||
.signup-form-v2 .btn-primary:hover {
|
||||
background-color: #1B64DA;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #E53E3E;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
@ -1,42 +1,146 @@
|
||||
import React, { useState } from 'react';
|
||||
import { signup } from '@_api/user/userAPI';
|
||||
import type { UserCreateRequest, UserCreateResponse } from '@_api/user/userTypes';
|
||||
import '@_css/signup-page-v2.css';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { signup } from '@_api/member/memberAPI';
|
||||
import type { SignupRequest } from '@_api/member/memberTypes';
|
||||
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
const SignupPage: React.FC = () => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [name, setName] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSignup = async () => {
|
||||
const request: SignupRequest = { email, password, name };
|
||||
await signup(request)
|
||||
.then((response) => {
|
||||
const validate = () => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
if (!name.trim()) {
|
||||
newErrors.name = '이름을 입력해주세요.';
|
||||
}
|
||||
if (!email.trim()) {
|
||||
newErrors.email = '이메일을 입력해주세요.';
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
newErrors.email = '올바른 이메일 형식을 입력해주세요.';
|
||||
}
|
||||
if (password.length < MIN_PASSWORD_LENGTH) {
|
||||
newErrors.password = `비밀번호는 최소 ${MIN_PASSWORD_LENGTH}자리 이상이어야 합니다.`;
|
||||
}
|
||||
if (!phone.trim()) {
|
||||
newErrors.phone = '전화번호를 입력해주세요.';
|
||||
} else if (!/^010([0-9]{3,4})([0-9]{4})$/.test(phone)) {
|
||||
newErrors.phone = '올바른 휴대폰 번호 형식이 아닙니다. (예: 01012345678)';
|
||||
}
|
||||
|
||||
return newErrors;
|
||||
};
|
||||
|
||||
// 제출 이후에는 입력값이 바뀔 때마다 다시 validate 실행
|
||||
useEffect(() => {
|
||||
if (hasSubmitted) {
|
||||
setErrors(validate());
|
||||
}
|
||||
}, [email, password, name, phone, hasSubmitted]);
|
||||
|
||||
const handleSignup = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setHasSubmitted(true);
|
||||
|
||||
const newErrors = validate();
|
||||
setErrors(newErrors);
|
||||
|
||||
if (Object.keys(newErrors).length > 0) return;
|
||||
|
||||
const request: UserCreateRequest = { email, password, name, phone, regionCode: null };
|
||||
try {
|
||||
const response: UserCreateResponse = await signup(request);
|
||||
alert(`${response.name}님, 회원가입을 축하드려요. 로그인 후 이용해주세요!`);
|
||||
navigate('/login')
|
||||
})
|
||||
.catch(error => {
|
||||
navigate('/login');
|
||||
} catch (error: any) {
|
||||
const message =
|
||||
error.response?.data?.message ||
|
||||
'회원가입에 실패했어요. 입력 정보를 확인해주세요.';
|
||||
alert(message);
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="content-container" style={{ width: '400px' }}>
|
||||
<h2 className="content-container-title">Signup</h2>
|
||||
<div className="signup-container-v2">
|
||||
<h2 className="page-title">회원가입</h2>
|
||||
<form className="signup-form-v2" onSubmit={handleSignup}>
|
||||
<div className="form-group">
|
||||
<label>Email address</label>
|
||||
<input type="email" className="form-control" placeholder="Enter email" value={email} onChange={e => setEmail(e.target.value)} />
|
||||
<label className="form-label">이메일</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-input"
|
||||
placeholder="이메일을 입력하세요"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
{hasSubmitted && errors.email && (
|
||||
<p className="error-text">{errors.email}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Password</label>
|
||||
<input type="password" className="form-control" placeholder="Enter password" value={password} onChange={e => setPassword(e.target.value)} />
|
||||
<label className="form-label">비밀번호</label>
|
||||
<input
|
||||
type="password"
|
||||
className="form-input"
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
{hasSubmitted && errors.password && (
|
||||
<p className="error-text">{errors.password}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" className="form-control" placeholder="Enter name" value={name} onChange={e => setName(e.target.value)} />
|
||||
<label className="form-label">이름</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
placeholder="이름을 입력하세요"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
{hasSubmitted && errors.name && (
|
||||
<p className="error-text">{errors.name}</p>
|
||||
)}
|
||||
</div>
|
||||
<button className="btn btn-custom" onClick={handleSignup}>Register</button>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label">전화번호</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
placeholder="전화번호를 입력하세요('-' 제외)"
|
||||
value={phone}
|
||||
onChange={e => setPhone(e.target.value)}
|
||||
required
|
||||
/>
|
||||
{hasSubmitted && errors.phone && (
|
||||
<p className="error-text">{errors.phone}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
disabled={hasSubmitted && Object.keys(errors).length > 0}
|
||||
>
|
||||
가입하기
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user