diff --git a/frontend/src/api/region/regionAPI.ts b/frontend/src/api/region/regionAPI.ts new file mode 100644 index 00000000..e9feec0e --- /dev/null +++ b/frontend/src/api/region/regionAPI.ts @@ -0,0 +1,18 @@ +import apiClient from "@_api/apiClient"; +import type { DongListResponse, RegionCodeResponse, SidoListResponse, SigunguListResponse } from "./regionTypes"; + +export const fetchSidoList = async (): Promise => { + return await apiClient.get(`/regions/sido`); +}; + +export const fetchSigunguList = async (sidoCode: string): Promise => { + return await apiClient.get(`/regions/sigungu?sidoCode=${sidoCode}`); +} + +export const fetchDongList = async (sidoCode: string, sigunguCode: string): Promise => { + return await apiClient.get(`/regions/dong?sidoCode=${sidoCode}&sigunguCode=${sigunguCode}`); +} + +export const fetchRegionCode = async (sidoCode: string, sigunguCode: string, dongCode: string): Promise => { + return await apiClient.get(`/regions/code?sidoCode=${sidoCode}&sigunguCode=${sigunguCode}&dongCode=${dongCode}`); +} diff --git a/frontend/src/api/region/regionTypes.ts b/frontend/src/api/region/regionTypes.ts new file mode 100644 index 00000000..5fefdbdd --- /dev/null +++ b/frontend/src/api/region/regionTypes.ts @@ -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 +} diff --git a/frontend/src/css/signup-page-v2.css b/frontend/src/css/signup-page-v2.css index 7918185b..8d95c48b 100644 --- a/frontend/src/css/signup-page-v2.css +++ b/frontend/src/css/signup-page-v2.css @@ -68,4 +68,13 @@ color: #E53E3E; font-size: 12px; margin-top: 4px; -} \ No newline at end of file +} + +.region-select-group { + display: flex; + gap: 10px; +} + +.region-select-group select { + flex: 1; +} diff --git a/frontend/src/pages/SignupPage.tsx b/frontend/src/pages/SignupPage.tsx index 845ac67d..e4238cf5 100644 --- a/frontend/src/pages/SignupPage.tsx +++ b/frontend/src/pages/SignupPage.tsx @@ -1,8 +1,19 @@ -import {signup} from '@_api/user/userAPI'; -import type {UserCreateRequest, UserCreateResponse} from '@_api/user/userTypes'; +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 type { UserCreateRequest, UserCreateResponse } from '@_api/user/userTypes'; import '@_css/signup-page-v2.css'; -import React, {useEffect, useState} from 'react'; -import {useNavigate} from 'react-router-dom'; +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; const MIN_PASSWORD_LENGTH = 8; @@ -14,8 +25,67 @@ const SignupPage: React.FC = () => { const [errors, setErrors] = useState>({}); const [hasSubmitted, setHasSubmitted] = useState(false); + const [sidoList, setSidoList] = useState([]); + const [sigunguList, setSigunguList] = useState([]); + const [dongList, setDongList] = useState([]); + const [selectedSidoCode, setSelectedSidoCode] = useState(''); + const [selectedSigunguCode, setSelectedSigunguCode] = useState(''); + const [selectedDongCode, setSelectedDongCode] = useState(''); + 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 newErrors: Record = {}; @@ -36,6 +106,12 @@ const SignupPage: React.FC = () => { newErrors.phone = '올바른 휴대폰 번호 형식이 아닙니다. (예: 01012345678)'; } + if (selectedSidoCode || selectedSigunguCode || selectedDongCode) { + if (!selectedSidoCode || !selectedSigunguCode || !selectedDongCode) { + newErrors.region = '모든 지역 정보를 선택해주세요.'; + } + } + return newErrors; }; @@ -44,7 +120,7 @@ const SignupPage: React.FC = () => { if (hasSubmitted) { setErrors(validate()); } - }, [email, password, name, phone, hasSubmitted]); + }, [email, password, name, phone, hasSubmitted, selectedSidoCode, selectedSigunguCode, selectedDongCode]); const handleSignup = async (e: React.FormEvent) => { e.preventDefault(); @@ -55,7 +131,23 @@ const SignupPage: React.FC = () => { 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 { const response: UserCreateResponse = await signup(request); alert(`${response.name}님, 회원가입을 축하드려요. 로그인 후 이용해주세요!`); @@ -133,6 +225,53 @@ const SignupPage: React.FC = () => { )} +
+ +
+ + + +
+ {hasSubmitted && errors.region && ( +

{errors.region}

+ )} +
+