generated from pricelees/issue-pr-template
[#44] 매장 기능 도입 #45
18
frontend/src/api/region/regionAPI.ts
Normal file
18
frontend/src/api/region/regionAPI.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import apiClient from "@_api/apiClient";
|
||||||
|
import type { DongListResponse, RegionCodeResponse, SidoListResponse, SigunguListResponse } from "./regionTypes";
|
||||||
|
|
||||||
|
export const fetchSidoList = async (): Promise<SidoListResponse> => {
|
||||||
|
return await apiClient.get(`/regions/sido`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchSigunguList = async (sidoCode: string): Promise<SigunguListResponse> => {
|
||||||
|
return await apiClient.get(`/regions/sigungu?sidoCode=${sidoCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchDongList = async (sidoCode: string, sigunguCode: string): Promise<DongListResponse> => {
|
||||||
|
return await apiClient.get(`/regions/dong?sidoCode=${sidoCode}&sigunguCode=${sigunguCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchRegionCode = async (sidoCode: string, sigunguCode: string, dongCode: string): Promise<RegionCodeResponse> => {
|
||||||
|
return await apiClient.get(`/regions/code?sidoCode=${sidoCode}&sigunguCode=${sigunguCode}&dongCode=${dongCode}`);
|
||||||
|
}
|
||||||
30
frontend/src/api/region/regionTypes.ts
Normal file
30
frontend/src/api/region/regionTypes.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export interface SidoResponse {
|
||||||
|
code: string,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SidoListResponse {
|
||||||
|
sidoList: SidoResponse[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SigunguResponse {
|
||||||
|
code: string,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SigunguListResponse {
|
||||||
|
sigunguList: SigunguResponse[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DongResponse {
|
||||||
|
code: string,
|
||||||
|
name: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DongListResponse {
|
||||||
|
dongList: DongResponse[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegionCodeResponse {
|
||||||
|
code: string
|
||||||
|
}
|
||||||
@ -69,3 +69,12 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.region-select-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-select-group select {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,14 @@
|
|||||||
|
import {
|
||||||
|
fetchDongList,
|
||||||
|
fetchRegionCode,
|
||||||
|
fetchSidoList,
|
||||||
|
fetchSigunguList,
|
||||||
|
} from '@_api/region/regionAPI';
|
||||||
|
import type {
|
||||||
|
DongResponse,
|
||||||
|
SidoResponse,
|
||||||
|
SigunguResponse,
|
||||||
|
} from '@_api/region/regionTypes';
|
||||||
import { signup } from '@_api/user/userAPI';
|
import { signup } from '@_api/user/userAPI';
|
||||||
import type { UserCreateRequest, UserCreateResponse } from '@_api/user/userTypes';
|
import type { UserCreateRequest, UserCreateResponse } from '@_api/user/userTypes';
|
||||||
import '@_css/signup-page-v2.css';
|
import '@_css/signup-page-v2.css';
|
||||||
@ -14,8 +25,67 @@ const SignupPage: React.FC = () => {
|
|||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||||
|
|
||||||
|
const [sidoList, setSidoList] = useState<SidoResponse[]>([]);
|
||||||
|
const [sigunguList, setSigunguList] = useState<SigunguResponse[]>([]);
|
||||||
|
const [dongList, setDongList] = useState<DongResponse[]>([]);
|
||||||
|
const [selectedSidoCode, setSelectedSidoCode] = useState('');
|
||||||
|
const [selectedSigunguCode, setSelectedSigunguCode] = useState('');
|
||||||
|
const [selectedDongCode, setSelectedDongCode] = useState('');
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSido = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetchSidoList();
|
||||||
|
setSidoList(response.sidoList);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('시/도 목록을 불러오는 데 실패했습니다.', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchSido();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedSidoCode) {
|
||||||
|
const fetchSigungu = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetchSigunguList(selectedSidoCode);
|
||||||
|
setSigunguList(response.sigunguList);
|
||||||
|
setDongList([]);
|
||||||
|
setSelectedSigunguCode('');
|
||||||
|
setSelectedDongCode('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('시/군/구 목록을 불러오는 데 실패했습니다.', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchSigungu();
|
||||||
|
} else {
|
||||||
|
setSigunguList([]);
|
||||||
|
setDongList([]);
|
||||||
|
setSelectedSigunguCode('');
|
||||||
|
setSelectedDongCode('');
|
||||||
|
}
|
||||||
|
}, [selectedSidoCode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedSidoCode && selectedSigunguCode) {
|
||||||
|
const fetchDong = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetchDongList(selectedSidoCode, selectedSigunguCode);
|
||||||
|
setDongList(response.dongList);
|
||||||
|
setSelectedDongCode('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('읍/면/동 목록을 불러오는 데 실패했습니다.', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchDong();
|
||||||
|
} else {
|
||||||
|
setDongList([]);
|
||||||
|
setSelectedDongCode('');
|
||||||
|
}
|
||||||
|
}, [selectedSigunguCode]);
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
const newErrors: Record<string, string> = {};
|
const newErrors: Record<string, string> = {};
|
||||||
|
|
||||||
@ -36,6 +106,12 @@ const SignupPage: React.FC = () => {
|
|||||||
newErrors.phone = '올바른 휴대폰 번호 형식이 아닙니다. (예: 01012345678)';
|
newErrors.phone = '올바른 휴대폰 번호 형식이 아닙니다. (예: 01012345678)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedSidoCode || selectedSigunguCode || selectedDongCode) {
|
||||||
|
if (!selectedSidoCode || !selectedSigunguCode || !selectedDongCode) {
|
||||||
|
newErrors.region = '모든 지역 정보를 선택해주세요.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newErrors;
|
return newErrors;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,7 +120,7 @@ const SignupPage: React.FC = () => {
|
|||||||
if (hasSubmitted) {
|
if (hasSubmitted) {
|
||||||
setErrors(validate());
|
setErrors(validate());
|
||||||
}
|
}
|
||||||
}, [email, password, name, phone, hasSubmitted]);
|
}, [email, password, name, phone, hasSubmitted, selectedSidoCode, selectedSigunguCode, selectedDongCode]);
|
||||||
|
|
||||||
const handleSignup = async (e: React.FormEvent) => {
|
const handleSignup = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -55,7 +131,23 @@ const SignupPage: React.FC = () => {
|
|||||||
|
|
||||||
if (Object.keys(newErrors).length > 0) return;
|
if (Object.keys(newErrors).length > 0) return;
|
||||||
|
|
||||||
const request: UserCreateRequest = { email, password, name, phone, regionCode: null };
|
let regionCode: string | null = null;
|
||||||
|
if (selectedSidoCode && selectedSigunguCode && selectedDongCode) {
|
||||||
|
try {
|
||||||
|
const response = await fetchRegionCode(
|
||||||
|
selectedSidoCode,
|
||||||
|
selectedSigunguCode,
|
||||||
|
selectedDongCode
|
||||||
|
);
|
||||||
|
regionCode = response.code;
|
||||||
|
} catch (error) {
|
||||||
|
alert('지역 코드를 가져오는 데 실패했습니다.');
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const request: UserCreateRequest = { email, password, name, phone, regionCode };
|
||||||
try {
|
try {
|
||||||
const response: UserCreateResponse = await signup(request);
|
const response: UserCreateResponse = await signup(request);
|
||||||
alert(`${response.name}님, 회원가입을 축하드려요. 로그인 후 이용해주세요!`);
|
alert(`${response.name}님, 회원가입을 축하드려요. 로그인 후 이용해주세요!`);
|
||||||
@ -133,6 +225,53 @@ const SignupPage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label">지역 (선택)</label>
|
||||||
|
<div className="region-select-group">
|
||||||
|
<select
|
||||||
|
className="form-input"
|
||||||
|
value={selectedSidoCode}
|
||||||
|
onChange={e => setSelectedSidoCode(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">시/도</option>
|
||||||
|
{sidoList.map(s => (
|
||||||
|
<option key={s.code} value={s.code}>
|
||||||
|
{s.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
className="form-input"
|
||||||
|
value={selectedSigunguCode}
|
||||||
|
onChange={e => setSelectedSigunguCode(e.target.value)}
|
||||||
|
disabled={!selectedSidoCode}
|
||||||
|
>
|
||||||
|
<option value="">시/군/구</option>
|
||||||
|
{sigunguList.map(s => (
|
||||||
|
<option key={s.code} value={s.code}>
|
||||||
|
{s.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
className="form-input"
|
||||||
|
value={selectedDongCode}
|
||||||
|
onChange={e => setSelectedDongCode(e.target.value)}
|
||||||
|
disabled={!selectedSigunguCode}
|
||||||
|
>
|
||||||
|
<option value="">읍/면/동</option>
|
||||||
|
{dongList.map(d => (
|
||||||
|
<option key={d.code} value={d.code}>
|
||||||
|
{d.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{hasSubmitted && errors.region && (
|
||||||
|
<p className="error-text">{errors.region}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn-primary"
|
className="btn-primary"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user