refactor: 회원가입 페이지 API 및 타입 수정

This commit is contained in:
이상진 2025-09-13 16:54:17 +09:00
parent e1941052f9
commit b0f67543be
4 changed files with 171 additions and 28 deletions

View 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);
};

View 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;
}

View File

@ -63,3 +63,9 @@
.signup-form-v2 .btn-primary:hover {
background-color: #1B64DA;
}
.error-text {
color: #E53E3E;
font-size: 12px;
margin-top: 4px;
}

View File

@ -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>
);
};