From 3fbe4058762bcff454c2cd9600a48bba6cf7b504 Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Thu, 10 Oct 2024 03:14:07 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20API=20=ED=98=B8=EC=B6=9C=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/common/register/index.tsx | 25 ++++++++++++++++++-- src/shared/api/auth/user-register.ts | 34 ++++++++++++++++++++++++++++ src/shared/api/instance/index.ts | 4 +++- 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/shared/api/auth/user-register.ts diff --git a/src/pages/common/register/index.tsx b/src/pages/common/register/index.tsx index 9677a74c..94f267f7 100644 --- a/src/pages/common/register/index.tsx +++ b/src/pages/common/register/index.tsx @@ -5,9 +5,11 @@ import { RegisterFields } from './components/register-fields'; import { RegisterType } from './components/register-type'; import { Tos } from './components/tos'; import { FormValues } from './types'; +import { SignupResponse, registerUser } from '@/shared/api/auth/user-register'; import { BasicButton } from '@/shared/components/common/button'; import { Divider } from '@chakra-ui/react'; import styled from '@emotion/styled'; +import { useMutation } from '@tanstack/react-query'; const RegisterPage = () => { const [userType, setUserType] = useState(''); @@ -17,13 +19,32 @@ const RegisterPage = () => { formState: { errors }, } = useForm(); + // 회원가입 처리 + const mutation = useMutation({ + mutationFn: registerUser, + onSuccess: (data: SignupResponse) => { + //성공 + console.log('회원가입 성공', data); + }, + }); + const handleUserType = (id: string) => { setUserType(id); }; const onSubmit = (data: FormValues) => { - // 회원가입 api - console.log(userType, data); + // request 에 맞게 데이터 병합 + const isSinitto = userType === 'sinitto'; + + const requestData = { + name: data.name, + phoneNumber: data.phoneNumber, + email: 'aaa@example.com', // 임시 (카카오 로그인 후 넘겨받기) + isSinitto, + }; + console.log(requestData); + // 회원가입 API 호출 + mutation.mutate(requestData); }; return ( diff --git a/src/shared/api/auth/user-register.ts b/src/shared/api/auth/user-register.ts new file mode 100644 index 00000000..257d7538 --- /dev/null +++ b/src/shared/api/auth/user-register.ts @@ -0,0 +1,34 @@ +import { fetchInstance } from '../instance'; + +// request(요청) 타입 +export type SignupReguestParams = { + name: string; + phoneNumber: string; + email: string; + isSinitto: boolean; +}; + +// response(응답) 타입 +export type SignupResponse = { + accessToken: string; + refreshToken: string; + isSinitto?: 'true' | 'false'; +}; +export const registerUser = async ({ + name, + phoneNumber, + email, + isSinitto, +}: SignupReguestParams): Promise => { + try { + const response = await fetchInstance.post('/api/members/sinitto', { + name, + phoneNumber, + email, + isSinitto, + }); + return response.data; + } catch (err) { + throw new Error('회원가입 실패'); + } +}; diff --git a/src/shared/api/instance/index.ts b/src/shared/api/instance/index.ts index 439db8ec..dbe8c22c 100644 --- a/src/shared/api/instance/index.ts +++ b/src/shared/api/instance/index.ts @@ -17,8 +17,10 @@ const initInstance = (config: AxiosRequestConfig): AxiosInstance => { return instance; }; +export const BASE_URL = 'http://43.201.254.198:8080'; + export const fetchInstance = initInstance({ - baseURL: 'https://api.example.com', + baseURL: 'http://43.201.254.198:8080', }); export const queryClient = new QueryClient({ From 06184210b6ce4a3176b9377f936889d4d9785c72 Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Thu, 10 Oct 2024 03:20:44 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Feat:=20API=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=88=98=EC=A0=95=20(=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=ED=83=80=EC=9E=85=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20API=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EA=B2=B0=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/api/auth/user-register.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/shared/api/auth/user-register.ts b/src/shared/api/auth/user-register.ts index 257d7538..152732a5 100644 --- a/src/shared/api/auth/user-register.ts +++ b/src/shared/api/auth/user-register.ts @@ -21,7 +21,10 @@ export const registerUser = async ({ isSinitto, }: SignupReguestParams): Promise => { try { - const response = await fetchInstance.post('/api/members/sinitto', { + // 시니또 보호자에 따라 API 엔드포인트 구분 + const endpoint = isSinitto ? 'sinitto' : 'guard'; + + const response = await fetchInstance.post(`/api/members/${endpoint}`, { name, phoneNumber, email, From c5a1cd0032c4d7afdc06055f75bd9abbecb947c0 Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Thu, 10 Oct 2024 04:38:10 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20API=20=ED=98=B8=EC=B6=9C=20=ED=9B=84=20response=20?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=98=88=EC=99=B8,=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/common/register/index.tsx | 26 ++++++++++++++++++++++---- src/shared/api/auth/user-register.ts | 22 +++++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/pages/common/register/index.tsx b/src/pages/common/register/index.tsx index 94f267f7..51e08e55 100644 --- a/src/pages/common/register/index.tsx +++ b/src/pages/common/register/index.tsx @@ -1,11 +1,16 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; +import { AxiosError } from 'axios'; + import { RegisterFields } from './components/register-fields'; import { RegisterType } from './components/register-type'; import { Tos } from './components/tos'; import { FormValues } from './types'; -import { SignupResponse, registerUser } from '@/shared/api/auth/user-register'; +import { + SignupApiResponse, + registerUser, +} from '@/shared/api/auth/user-register'; import { BasicButton } from '@/shared/components/common/button'; import { Divider } from '@chakra-ui/react'; import styled from '@emotion/styled'; @@ -22,9 +27,22 @@ const RegisterPage = () => { // 회원가입 처리 const mutation = useMutation({ mutationFn: registerUser, - onSuccess: (data: SignupResponse) => { - //성공 - console.log('회원가입 성공', data); + onSuccess: (data: SignupApiResponse) => { + // 207 (이미 이메일이 존재하면) + if ('status' in data && data.status === 207) { + alert(data.detail); + } else { + // 200 (성공) -> 토큰 저장 + console.log(data); + alert('회원가입이 완료되었습니다.'); + } + }, + onError: (error: AxiosError) => { + if (error.response && error.response.data) { + alert('회원가입 중 오류가 발생했습니다.'); + } else { + alert('회원가입 중 네트워크 오류가 발생했습니다.'); + } }, }); diff --git a/src/shared/api/auth/user-register.ts b/src/shared/api/auth/user-register.ts index 152732a5..f628e925 100644 --- a/src/shared/api/auth/user-register.ts +++ b/src/shared/api/auth/user-register.ts @@ -8,18 +8,28 @@ export type SignupReguestParams = { isSinitto: boolean; }; -// response(응답) 타입 +// response(응답) 타입 - 성공 export type SignupResponse = { accessToken: string; refreshToken: string; isSinitto?: 'true' | 'false'; }; + +// 에러(아직 정확히 에러코드 확인 x) or 예외 +export type SignupErrorResponse = { + status: number; + detail: string; +}; + +// 공통 타입 정의 (onSuccess 내부에서 분기) +export type SignupApiResponse = SignupResponse | SignupErrorResponse; + export const registerUser = async ({ name, phoneNumber, email, isSinitto, -}: SignupReguestParams): Promise => { +}: SignupReguestParams): Promise => { try { // 시니또 보호자에 따라 API 엔드포인트 구분 const endpoint = isSinitto ? 'sinitto' : 'guard'; @@ -30,7 +40,13 @@ export const registerUser = async ({ email, isSinitto, }); - return response.data; + if (response.status === 207) { + return { + status: response.status, + detail: response.data.detail, + } as SignupErrorResponse; // 207 (예외 - 중복 이메일) + } + return response.data; // 200 } catch (err) { throw new Error('회원가입 실패'); } From da4033dc1be2b8c937f29d41f1ec86b14500cb9b Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:17:27 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Feat:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=EA=B4=80=EB=A0=A8=20util=20=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/utils/storage/index.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/shared/utils/storage/index.ts diff --git a/src/shared/utils/storage/index.ts b/src/shared/utils/storage/index.ts new file mode 100644 index 00000000..c562a0b8 --- /dev/null +++ b/src/shared/utils/storage/index.ts @@ -0,0 +1,24 @@ +type StorageKey = { + accessToken?: string; +}; + +const initStorage = (key: T, storage: Storage) => { + const storageKey = `${key}`; + + const get = (): StorageKey[T] => { + const value = storage.getItem(storageKey); + return value as StorageKey[T]; + }; + + const set = (value: StorageKey[T]) => { + if (value == undefined || value == null) { + return storage.removeItem(storageKey); + } + const stringifiedValue = JSON.stringify(value); + storage.setItem(storageKey, stringifiedValue); + }; + + return { get, set }; +}; + +export const authLocalStorage = initStorage('accessToken', localStorage); From 22625b78cb4dadcfdb4d82520c323f988c98be83 Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:41:16 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=84=B1=EA=B3=B5=20=ED=9B=84=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/common/register/index.tsx | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pages/common/register/index.tsx b/src/pages/common/register/index.tsx index 51e08e55..3c0d4fad 100644 --- a/src/pages/common/register/index.tsx +++ b/src/pages/common/register/index.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; import { AxiosError } from 'axios'; @@ -7,11 +8,13 @@ import { RegisterFields } from './components/register-fields'; import { RegisterType } from './components/register-type'; import { Tos } from './components/tos'; import { FormValues } from './types'; +import { RouterPath } from '@/app/routes/path'; import { SignupApiResponse, registerUser, } from '@/shared/api/auth/user-register'; import { BasicButton } from '@/shared/components/common/button'; +import { authLocalStorage } from '@/shared/utils/storage'; import { Divider } from '@chakra-ui/react'; import styled from '@emotion/styled'; import { useMutation } from '@tanstack/react-query'; @@ -24,17 +27,31 @@ const RegisterPage = () => { formState: { errors }, } = useForm(); + const navigate = useNavigate(); + // 회원가입 처리 const mutation = useMutation({ mutationFn: registerUser, onSuccess: (data: SignupApiResponse) => { - // 207 (이미 이메일이 존재하면) + // 207 (이미 이메일이 존재하면 Multi-Status) if ('status' in data && data.status === 207) { alert(data.detail); } else { // 200 (성공) -> 토큰 저장 console.log(data); - alert('회원가입이 완료되었습니다.'); + // 타입가드 + if ('accessToken' in data) { + authLocalStorage.set(data.accessToken); + alert('회원가입이 완료되었습니다.'); + // 유저 타입에 따라 네비게이션 + if (data.isSinitto === 'true') { + navigate(RouterPath.ROOT); + // navigate(RouterPath.SINITTO_HOME); + } else { + navigate(RouterPath.ROOT); + // navigate(RouterPath.GUARD_HOME); + } + } } }, onError: (error: AxiosError) => { @@ -57,7 +74,7 @@ const RegisterPage = () => { const requestData = { name: data.name, phoneNumber: data.phoneNumber, - email: 'aaa@example.com', // 임시 (카카오 로그인 후 넘겨받기) + email: 'test1@example.com', // 임시 (카카오 로그인 후 넘겨받기) isSinitto, }; console.log(requestData); From 65e77c46df04249148d21b6248d0e28deff83d4a Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:07:32 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Chore:=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EB=A1=9C=EC=BB=AC=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/common/register/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/common/register/index.tsx b/src/pages/common/register/index.tsx index 3c0d4fad..168d8e9a 100644 --- a/src/pages/common/register/index.tsx +++ b/src/pages/common/register/index.tsx @@ -42,6 +42,7 @@ const RegisterPage = () => { // 타입가드 if ('accessToken' in data) { authLocalStorage.set(data.accessToken); + authLocalStorage.set(data.refreshToken); alert('회원가입이 완료되었습니다.'); // 유저 타입에 따라 네비게이션 if (data.isSinitto === 'true') { From f00db5e73305686e9b807e313ad58dc98035a173 Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Fri, 11 Oct 2024 06:22:46 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Refactor:=20useRegister=20hook=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/common/register/index.tsx | 47 ++--------------------------- src/shared/hooks/useRegister.ts | 47 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 src/shared/hooks/useRegister.ts diff --git a/src/pages/common/register/index.tsx b/src/pages/common/register/index.tsx index 168d8e9a..4307c405 100644 --- a/src/pages/common/register/index.tsx +++ b/src/pages/common/register/index.tsx @@ -1,23 +1,14 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom'; - -import { AxiosError } from 'axios'; import { RegisterFields } from './components/register-fields'; import { RegisterType } from './components/register-type'; import { Tos } from './components/tos'; import { FormValues } from './types'; -import { RouterPath } from '@/app/routes/path'; -import { - SignupApiResponse, - registerUser, -} from '@/shared/api/auth/user-register'; import { BasicButton } from '@/shared/components/common/button'; -import { authLocalStorage } from '@/shared/utils/storage'; +import useRegister from '@/shared/hooks/useRegister'; import { Divider } from '@chakra-ui/react'; import styled from '@emotion/styled'; -import { useMutation } from '@tanstack/react-query'; const RegisterPage = () => { const [userType, setUserType] = useState(''); @@ -27,42 +18,8 @@ const RegisterPage = () => { formState: { errors }, } = useForm(); - const navigate = useNavigate(); - // 회원가입 처리 - const mutation = useMutation({ - mutationFn: registerUser, - onSuccess: (data: SignupApiResponse) => { - // 207 (이미 이메일이 존재하면 Multi-Status) - if ('status' in data && data.status === 207) { - alert(data.detail); - } else { - // 200 (성공) -> 토큰 저장 - console.log(data); - // 타입가드 - if ('accessToken' in data) { - authLocalStorage.set(data.accessToken); - authLocalStorage.set(data.refreshToken); - alert('회원가입이 완료되었습니다.'); - // 유저 타입에 따라 네비게이션 - if (data.isSinitto === 'true') { - navigate(RouterPath.ROOT); - // navigate(RouterPath.SINITTO_HOME); - } else { - navigate(RouterPath.ROOT); - // navigate(RouterPath.GUARD_HOME); - } - } - } - }, - onError: (error: AxiosError) => { - if (error.response && error.response.data) { - alert('회원가입 중 오류가 발생했습니다.'); - } else { - alert('회원가입 중 네트워크 오류가 발생했습니다.'); - } - }, - }); + const mutation = useRegister(); const handleUserType = (id: string) => { setUserType(id); diff --git a/src/shared/hooks/useRegister.ts b/src/shared/hooks/useRegister.ts new file mode 100644 index 00000000..f5e201e8 --- /dev/null +++ b/src/shared/hooks/useRegister.ts @@ -0,0 +1,47 @@ +import { useNavigate } from 'react-router-dom'; + +import { AxiosError } from 'axios'; + +import { RouterPath } from '@/app/routes/path'; +import { + SignupApiResponse, + registerUser, +} from '@/shared/api/auth/user-register'; +import { authLocalStorage } from '@/shared/utils/storage'; +import { useMutation } from '@tanstack/react-query'; + +const useRegister = () => { + const navigate = useNavigate(); + + const handleSuccess = (data: SignupApiResponse) => { + if ('status' in data && data.status === 207) { + alert(data.detail); + } else { + console.log(data); + if ('accessToken' in data) { + authLocalStorage.set(data.accessToken); + authLocalStorage.set(data.refreshToken); + alert('회원가입이 완료되었습니다.'); + navigate(data.isSinitto === 'true' ? RouterPath.ROOT : RouterPath.ROOT); + } + } + }; + + const handleError = (error: AxiosError) => { + if (error.response && error.response.data) { + alert('회원가입 중 오류가 발생했습니다.'); + } else { + alert('회원가입 중 네트워크 오류가 발생했습니다.'); + } + }; + + const mutation = useMutation({ + mutationFn: registerUser, + onSuccess: handleSuccess, + onError: handleError, + }); + + return mutation; +}; + +export default useRegister; From f3e5060f1eb547ae9d0365b8c5d49b3c45d0bbe2 Mon Sep 17 00:00:00 2001 From: diwoni <70441308+Diwoni@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:37:50 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Chore:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20API=20hook=20useRegister.ts=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/{shared => pages/common/register/api}/hooks/useRegister.ts | 0 src/pages/common/register/index.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{shared => pages/common/register/api}/hooks/useRegister.ts (100%) diff --git a/src/shared/hooks/useRegister.ts b/src/pages/common/register/api/hooks/useRegister.ts similarity index 100% rename from src/shared/hooks/useRegister.ts rename to src/pages/common/register/api/hooks/useRegister.ts diff --git a/src/pages/common/register/index.tsx b/src/pages/common/register/index.tsx index 4307c405..b3dce8dd 100644 --- a/src/pages/common/register/index.tsx +++ b/src/pages/common/register/index.tsx @@ -1,12 +1,12 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; +import useRegister from './api/hooks/useRegister'; import { RegisterFields } from './components/register-fields'; import { RegisterType } from './components/register-type'; import { Tos } from './components/tos'; import { FormValues } from './types'; import { BasicButton } from '@/shared/components/common/button'; -import useRegister from '@/shared/hooks/useRegister'; import { Divider } from '@chakra-ui/react'; import styled from '@emotion/styled';