From 20618fad134af11cacc8f4330dc8f3389a435d4a Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 12:28:38 +0900 Subject: [PATCH 01/18] =?UTF-8?q?#4=20feat:=20Sign=20up=20data=EC=97=90=20?= =?UTF-8?q?passwordConfirm=20field=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/pages/SignUpPage/SignUpPage.tsx | 5 +++-- fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx | 4 ++-- fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fe/src/pages/SignUpPage/SignUpPage.tsx b/fe/src/pages/SignUpPage/SignUpPage.tsx index d84f158..c0a076f 100644 --- a/fe/src/pages/SignUpPage/SignUpPage.tsx +++ b/fe/src/pages/SignUpPage/SignUpPage.tsx @@ -19,6 +19,7 @@ export default function SignUpPage() { nickname: "", email: "", password: "", + passwordConfirm: "", verificationCode: "", }); @@ -53,8 +54,8 @@ export default function SignUpPage() { )} {subPage === "password" && ( { - setSignUpData((prev) => ({ ...prev, password: data })); + onNext={(password: string, passwordConfirm: string) => { + setSignUpData((prev) => ({ ...prev, password, passwordConfirm })); setSubPage("verification"); }} /> diff --git a/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx b/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx index c590ecf..87fa313 100644 --- a/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx @@ -3,7 +3,7 @@ import { validatePassword } from "@utils/textValidators"; import SubPage from "./SubPage"; type Props = { - onNext: (data: string) => void; + onNext: (password: string, passwordConfirm: string) => void; }; export default function PasswordSubPage({ onNext }: Props) { @@ -45,7 +45,7 @@ export default function PasswordSubPage({ onNext }: Props) { {/* TODO: Disabled condition */} - diff --git a/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx b/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx index 29bccc3..82a4ba4 100644 --- a/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx @@ -8,7 +8,6 @@ type Props = { export default function VerificationCodeSubPage({ email, onNext }: Props) { const onDigitsFilled = (digits: string) => { - console.log("digits:", digits); onNext(digits); }; From 39aee66cf8fda113c39dad295a9a265c6d82d416 Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 12:29:02 +0900 Subject: [PATCH 02/18] =?UTF-8?q?#4=20feat:=20Sign=20up,=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84/=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EA=B2=80=EC=82=AC=20api=20=EB=B0=8F=20mock=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/api/auth/index.ts | 20 +++++++++-- fe/src/mocks/browserServiceWorker.ts | 2 +- fe/src/mocks/data/authData.ts | 48 +++++++++++++++++++++++++++ fe/src/mocks/handlers/authHandlers.ts | 45 +++++++++++++++++++++++++ fe/src/mocks/handlers/index.ts | 4 +-- 5 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 fe/src/mocks/data/authData.ts create mode 100644 fe/src/mocks/handlers/authHandlers.ts diff --git a/fe/src/api/auth/index.ts b/fe/src/api/auth/index.ts index 71fc5d4..422d994 100644 --- a/fe/src/api/auth/index.ts +++ b/fe/src/api/auth/index.ts @@ -15,6 +15,7 @@ export type SignUpData = { nickname: string; email: string; password: string; + passwordConfirm: string; verificationCode: string; }; @@ -31,8 +32,7 @@ type AccessTokenData = { }; export const postSignUp = async (body: SignUpData) => { - console.log("body:", body); - const res = await fetcher.post>("/users", body); + const res = await fetcher.post>("/auth/signup", body); return res.data; }; @@ -78,3 +78,19 @@ export const patchUserInfo = async (body: FormData) => { }); return res.data; }; + +export const postNicknameDuplicateCheck = async (nickname: string) => { + const res = await fetcher.post>( + "/auth/signup/duplicationcheck/nickname", + { nickname } + ); + return res.data; +}; + +export const postEmailDuplicateCheck = async (email: string) => { + const res = await fetcher.post>( + "/auth/signup/duplicationcheck/email", + { email } + ); + return res.data; +}; diff --git a/fe/src/mocks/browserServiceWorker.ts b/fe/src/mocks/browserServiceWorker.ts index 73f1dfd..8cf119f 100644 --- a/fe/src/mocks/browserServiceWorker.ts +++ b/fe/src/mocks/browserServiceWorker.ts @@ -1,4 +1,4 @@ import { setupWorker } from "msw"; -import handlers from "./handlers/index"; +import handlers from "./handlers"; export default setupWorker(...handlers); diff --git a/fe/src/mocks/data/authData.ts b/fe/src/mocks/data/authData.ts new file mode 100644 index 0000000..d53d062 --- /dev/null +++ b/fe/src/mocks/data/authData.ts @@ -0,0 +1,48 @@ +import { HTTPSTATUS } from "@api/types"; + +export const successfulSignUpData = { + code: HTTPSTATUS.created, + status: "Created", + message: "회원가입이 완료되었습니다", + data: { + member: { + nickname: "Kakamotobi", + email: "daeram.chung@gmail.com", + }, + }, +}; + +export const unsuccessfulSignUpData = { + code: HTTPSTATUS.badRequest, + status: "Bad Request", + message: "회원가입이 실패했습니다", + data: null, +}; + +export const successfulNicknameDuplicationCheckData = { + code: HTTPSTATUS.success, + status: "Success", + message: "닉네임이 사용 가능합니다", + data: null, +}; + +export const unsuccessfulNicknameDuplicationCheckData = { + code: HTTPSTATUS.badRequest, + status: "Bad Request", + message: "닉네임이 중복되었습니다", + data: null, +}; + +export const successfulEmailDuplicationCheckData = { + code: HTTPSTATUS.success, + status: "Success", + message: "이메일이 사용 가능합니다", + data: null, +}; + +export const unsuccessfulEmailDuplicationCheckData = { + code: HTTPSTATUS.badRequest, + status: "Bad Request", + message: "이메일이 중복되었습니다", + data: null, +}; diff --git a/fe/src/mocks/handlers/authHandlers.ts b/fe/src/mocks/handlers/authHandlers.ts new file mode 100644 index 0000000..e752da9 --- /dev/null +++ b/fe/src/mocks/handlers/authHandlers.ts @@ -0,0 +1,45 @@ +import { HTTPSTATUS } from "@api/types"; +import { + successfulEmailDuplicationCheckData, + successfulNicknameDuplicationCheckData, + successfulSignUpData, + unsuccessfulEmailDuplicationCheckData, + unsuccessfulNicknameDuplicationCheckData, + unsuccessfulSignUpData, +} from "mocks/data/authData"; +import { rest } from "msw"; + +export default [ + rest.post("/api/auth/signup", async (_, res, ctx) => { + return res(ctx.status(HTTPSTATUS.created), ctx.json(successfulSignUpData)); + return res( + ctx.status(HTTPSTATUS.badRequest), + ctx.json(unsuccessfulSignUpData) + ); + }), + + rest.post( + "/api/auth/signup/duplicationcheck/nickname", + async (_, res, ctx) => { + return res( + ctx.status(HTTPSTATUS.success), + ctx.json(successfulNicknameDuplicationCheckData) + ); + return res( + ctx.status(HTTPSTATUS.badRequest), + ctx.json(unsuccessfulNicknameDuplicationCheckData) + ); + } + ), + + rest.post("/api/auth/signup/duplicationcheck/email", async (_, res, ctx) => { + return res( + ctx.status(HTTPSTATUS.success), + ctx.json(successfulEmailDuplicationCheckData) + ); + return res( + ctx.status(HTTPSTATUS.badRequest), + ctx.json(unsuccessfulEmailDuplicationCheckData) + ); + }), +]; diff --git a/fe/src/mocks/handlers/index.ts b/fe/src/mocks/handlers/index.ts index 97e04f6..165b805 100644 --- a/fe/src/mocks/handlers/index.ts +++ b/fe/src/mocks/handlers/index.ts @@ -1,3 +1,3 @@ -// import & export handlers +import authHandlers from "./authHandlers"; -export default []; +export default [...authHandlers]; From eecf64c41e427aa1e11ab3b40c9f17d3a78ac706 Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 15:26:08 +0900 Subject: [PATCH 03/18] =?UTF-8?q?#4=20fix:=20useText=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=EA=B0=92=20validate=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/hooks/useText.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fe/src/hooks/useText.tsx b/fe/src/hooks/useText.tsx index d3c7135..3095cee 100644 --- a/fe/src/hooks/useText.tsx +++ b/fe/src/hooks/useText.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; type Props = { initialValue?: string; @@ -31,6 +31,10 @@ export default function useText(options?: Props) { setValue(newVal); }; + useEffect(() => { + onChange(initialValue); + }, []); + return { value, error, From d2554672a88f81f2ce282a640cce1758eda07f1d Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 17:10:57 +0900 Subject: [PATCH 04/18] =?UTF-8?q?#4=20feat:=20NicknameSubPage=20"=EB=8B=A4?= =?UTF-8?q?=EC=9D=8C"=20=EB=B2=84=ED=8A=BC=20disabled=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/SignUpPage/subPages/NicknameSubPage.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx b/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx index a2f7b89..16653cf 100644 --- a/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx @@ -7,7 +7,11 @@ type Props = { }; export default function NicknameSubPage({ onNext }: Props) { - const { value: nickname, onChange } = useText({ + const { + value: nickname, + isError: isNicknameError, + onChange, + } = useText({ validators: [validateNickname], }); @@ -21,14 +25,17 @@ export default function NicknameSubPage({ onNext }: Props) { value={nickname} onChange={(e) => onChange(e.target.value.trim())} /> + {/* TODO: 중복 확인 */}

영문/한글/숫자 (2~10자)

- {/* TODO: Disabled condition */} - From bfbe37f10ce56d28724c544984ca175752b74dce Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 17:13:41 +0900 Subject: [PATCH 05/18] =?UTF-8?q?#4=20feat:=20Email=20verification=20code?= =?UTF-8?q?=20api=20=EB=B0=8F=20mock=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/api/auth/index.ts | 7 +++++++ fe/src/mocks/data/authData.ts | 14 ++++++++++++++ fe/src/mocks/handlers/authHandlers.ts | 13 +++++++++++++ 3 files changed, 34 insertions(+) diff --git a/fe/src/api/auth/index.ts b/fe/src/api/auth/index.ts index 422d994..4c56951 100644 --- a/fe/src/api/auth/index.ts +++ b/fe/src/api/auth/index.ts @@ -94,3 +94,10 @@ export const postEmailDuplicateCheck = async (email: string) => { ); return res.data; }; + +export const postEmailVerification = async (email: string) => { + const res = await fetcher.post>("/auth/signup/verifyEmail", { + email, + }); + return res.data; +}; diff --git a/fe/src/mocks/data/authData.ts b/fe/src/mocks/data/authData.ts index d53d062..efae0b3 100644 --- a/fe/src/mocks/data/authData.ts +++ b/fe/src/mocks/data/authData.ts @@ -46,3 +46,17 @@ export const unsuccessfulEmailDuplicationCheckData = { message: "이메일이 중복되었습니다", data: null, }; + +export const successfulEmailVerificationData = { + code: HTTPSTATUS.success, + status: "Success", + message: "이메일로 검증코드를 전송하였습니다", + data: null, +}; + +export const unsuccessfulEmailVerificationData = { + code: HTTPSTATUS.badRequest, + status: "Bad Request", + message: "이메일 검증코드 전송을 실패했습니다", + data: null, +}; diff --git a/fe/src/mocks/handlers/authHandlers.ts b/fe/src/mocks/handlers/authHandlers.ts index e752da9..5cce203 100644 --- a/fe/src/mocks/handlers/authHandlers.ts +++ b/fe/src/mocks/handlers/authHandlers.ts @@ -1,9 +1,11 @@ import { HTTPSTATUS } from "@api/types"; import { successfulEmailDuplicationCheckData, + successfulEmailVerificationData, successfulNicknameDuplicationCheckData, successfulSignUpData, unsuccessfulEmailDuplicationCheckData, + unsuccessfulEmailVerificationData, unsuccessfulNicknameDuplicationCheckData, unsuccessfulSignUpData, } from "mocks/data/authData"; @@ -42,4 +44,15 @@ export default [ ctx.json(unsuccessfulEmailDuplicationCheckData) ); }), + + rest.post("/api/auth/signup/verifyEmail", async (_, res, ctx) => { + return res( + ctx.status(HTTPSTATUS.success), + ctx.json(successfulEmailVerificationData) + ); + return res( + ctx.status(HTTPSTATUS.badRequest), + ctx.json(unsuccessfulEmailVerificationData) + ); + }), ]; From 15228c82a2414e83eb8e955a4135b7915face7bc Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 17:59:48 +0900 Subject: [PATCH 06/18] =?UTF-8?q?#4=20feat:=20Sign=20up=20subpage=20"?= =?UTF-8?q?=EB=8B=A4=EC=9D=8C"=20=EB=B2=84=ED=8A=BC=20disabled=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/pages/SignUpPage/SignUpPage.tsx | 1 + .../SignUpPage/subPages/EmailSubPage.tsx | 10 ++++-- .../SignUpPage/subPages/NicknameSubPage.tsx | 7 ++-- .../SignUpPage/subPages/PasswordSubPage.tsx | 34 ++++++++++++------- .../subPages/VerificationSubPage.tsx | 2 +- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/fe/src/pages/SignUpPage/SignUpPage.tsx b/fe/src/pages/SignUpPage/SignUpPage.tsx index c0a076f..91339d1 100644 --- a/fe/src/pages/SignUpPage/SignUpPage.tsx +++ b/fe/src/pages/SignUpPage/SignUpPage.tsx @@ -57,6 +57,7 @@ export default function SignUpPage() { onNext={(password: string, passwordConfirm: string) => { setSignUpData((prev) => ({ ...prev, password, passwordConfirm })); setSubPage("verification"); + // TODO: request server to send verification code to the email. }} /> )} diff --git a/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx b/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx index fb4ee52..29ce3d2 100644 --- a/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx @@ -7,7 +7,11 @@ type Props = { }; export default function EmailSubPage({ onNext }: Props) { - const { value: email, onChange } = useText({ validators: [validateEmail] }); + const { + value: email, + isError, + onChange, + } = useText({ validators: [validateEmail] }); return ( @@ -19,12 +23,12 @@ export default function EmailSubPage({ onNext }: Props) { value={email} onChange={(e) => onChange(e.target.value.trim())} /> + {/* TODO: 중복 확인 */} - {/* TODO: Disabled condition */} - diff --git a/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx b/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx index 16653cf..2f965ce 100644 --- a/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx @@ -9,7 +9,7 @@ type Props = { export default function NicknameSubPage({ onNext }: Props) { const { value: nickname, - isError: isNicknameError, + isError, onChange, } = useText({ validators: [validateNickname], @@ -32,10 +32,7 @@ export default function NicknameSubPage({ onNext }: Props) {

영문/한글/숫자 (2~10자)

- diff --git a/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx b/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx index 87fa313..e202833 100644 --- a/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx @@ -7,23 +7,27 @@ type Props = { }; export default function PasswordSubPage({ onNext }: Props) { - const { value: password, onChange: onPasswordChange } = useText({ + const { + value: password, + isError: isPasswordError, + onChange: onPasswordChange, + } = useText({ + validators: [validatePassword], + }); + const { + value: passwordConfirm, + isError: isPasswordConfirmError, + onChange: onPasswordConfirmChange, + } = useText({ validators: [validatePassword], }); - const { value: passwordConfirm, onChange: onPasswordConfirmChange } = useText( - { - validators: [validatePassword], - } - ); - - // const isPasswordMatch = password === passwordConfirm; return (
- {/* TODO: Disabled condition */} - diff --git a/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx b/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx index 82a4ba4..b31935f 100644 --- a/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/VerificationSubPage.tsx @@ -14,7 +14,7 @@ export default function VerificationCodeSubPage({ email, onNext }: Props) { return (

가입을 완료하려면 인증 코드를 입력해주세요

-

{email}로 전송된 인증 코드를 입력해주세요

+

{email}으로 전송된 인증 코드를 입력해주세요

From 9ff976245e698f367144c90bafc8963801f65aa4 Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 22:23:21 +0900 Subject: [PATCH 07/18] =?UTF-8?q?#4=20feat:=20Signup=20nickname=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B2=B4=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/mocks/handlers/authHandlers.ts | 23 ++++++---- .../SignUpPage/subPages/NicknameSubPage.tsx | 45 +++++++++++++++++-- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/fe/src/mocks/handlers/authHandlers.ts b/fe/src/mocks/handlers/authHandlers.ts index 5cce203..b4c63d2 100644 --- a/fe/src/mocks/handlers/authHandlers.ts +++ b/fe/src/mocks/handlers/authHandlers.ts @@ -22,15 +22,20 @@ export default [ rest.post( "/api/auth/signup/duplicationcheck/nickname", - async (_, res, ctx) => { - return res( - ctx.status(HTTPSTATUS.success), - ctx.json(successfulNicknameDuplicationCheckData) - ); - return res( - ctx.status(HTTPSTATUS.badRequest), - ctx.json(unsuccessfulNicknameDuplicationCheckData) - ); + async (req, res, ctx) => { + const { nickname } = await req.json(); + + if (nickname === "test") { + return res( + ctx.status(HTTPSTATUS.success), + ctx.json(successfulNicknameDuplicationCheckData) + ); + } else { + return res( + ctx.status(HTTPSTATUS.badRequest), + ctx.json(unsuccessfulNicknameDuplicationCheckData) + ); + } } ), diff --git a/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx b/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx index 2f965ce..d1ba473 100644 --- a/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/NicknameSubPage.tsx @@ -1,5 +1,9 @@ +import { postNicknameDuplicateCheck } from "@api/auth"; +import { HTTPSTATUS } from "@api/types"; import useText from "@hooks/useText"; import { validateNickname } from "@utils/textValidators"; +import axios from "axios"; +import { ChangeEvent, useState } from "react"; import SubPage from "./SubPage"; type Props = { @@ -15,6 +19,32 @@ export default function NicknameSubPage({ onNext }: Props) { validators: [validateNickname], }); + const [isDuplicateChecked, setIsDuplicateChecked] = useState(false); + const [duplicateCheckErrorMsg, setDuplicateCheckErrorMsg] = useState(""); + + const onNicknameChange = (e: ChangeEvent) => { + onChange(e.target.value.trim()); + setIsDuplicateChecked(false); + setDuplicateCheckErrorMsg(""); + }; + + const onDuplicateCheckButtonClick = async () => { + try { + const res = await postNicknameDuplicateCheck(nickname); + + if (res.code === HTTPSTATUS.success) { + setIsDuplicateChecked(true); + setDuplicateCheckErrorMsg(""); + } + } catch (error) { + if (axios.isAxiosError(error)) { + setDuplicateCheckErrorMsg(error.response?.data.message); + } else { + setDuplicateCheckErrorMsg((error as Error).message); + } + } + }; + return ( @@ -23,16 +53,23 @@ export default function NicknameSubPage({ onNext }: Props) { placeholder="닉네임" id="nicknameInput" value={nickname} - onChange={(e) => onChange(e.target.value.trim())} + onChange={onNicknameChange} /> - {/* TODO: 중복 확인 */} -

영문/한글/숫자 (2~10자)

-
From 6e8cef75fe7f83f62d3daff4580e2940a0b63a7c Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 22:28:58 +0900 Subject: [PATCH 08/18] =?UTF-8?q?#4=20feat:=20Signup=20email=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=B2=B4=ED=81=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/mocks/handlers/authHandlers.ts | 36 +++++++++------ .../SignUpPage/subPages/EmailSubPage.tsx | 46 +++++++++++++++++-- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/fe/src/mocks/handlers/authHandlers.ts b/fe/src/mocks/handlers/authHandlers.ts index b4c63d2..c6e1585 100644 --- a/fe/src/mocks/handlers/authHandlers.ts +++ b/fe/src/mocks/handlers/authHandlers.ts @@ -25,31 +25,39 @@ export default [ async (req, res, ctx) => { const { nickname } = await req.json(); - if (nickname === "test") { + if (nickname === "duplicate") { + return res( + ctx.status(HTTPSTATUS.badRequest), + ctx.json(unsuccessfulNicknameDuplicationCheckData) + ); + } else { return res( ctx.status(HTTPSTATUS.success), ctx.json(successfulNicknameDuplicationCheckData) ); - } else { + } + } + ), + + rest.post( + "/api/auth/signup/duplicationcheck/email", + async (req, res, ctx) => { + const { email } = await req.json(); + + if (email === "duplicate@email.com") { return res( ctx.status(HTTPSTATUS.badRequest), - ctx.json(unsuccessfulNicknameDuplicationCheckData) + ctx.json(unsuccessfulEmailDuplicationCheckData) + ); + } else { + return res( + ctx.status(HTTPSTATUS.success), + ctx.json(successfulEmailDuplicationCheckData) ); } } ), - rest.post("/api/auth/signup/duplicationcheck/email", async (_, res, ctx) => { - return res( - ctx.status(HTTPSTATUS.success), - ctx.json(successfulEmailDuplicationCheckData) - ); - return res( - ctx.status(HTTPSTATUS.badRequest), - ctx.json(unsuccessfulEmailDuplicationCheckData) - ); - }), - rest.post("/api/auth/signup/verifyEmail", async (_, res, ctx) => { return res( ctx.status(HTTPSTATUS.success), diff --git a/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx b/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx index 29ce3d2..18bb31d 100644 --- a/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/EmailSubPage.tsx @@ -1,5 +1,9 @@ +import { postEmailDuplicateCheck } from "@api/auth"; +import { HTTPSTATUS } from "@api/types"; import useText from "@hooks/useText"; import { validateEmail } from "@utils/textValidators"; +import axios from "axios"; +import { ChangeEvent, useState } from "react"; import SubPage from "./SubPage"; type Props = { @@ -13,6 +17,32 @@ export default function EmailSubPage({ onNext }: Props) { onChange, } = useText({ validators: [validateEmail] }); + const [isDuplicateChecked, setIsDuplicateChecked] = useState(false); + const [duplicateCheckErrorMsg, setDuplicateCheckErrorMsg] = useState(""); + + const onEmailChange = (e: ChangeEvent) => { + onChange(e.target.value.trim()); + setIsDuplicateChecked(false); + setDuplicateCheckErrorMsg(""); + }; + + const onDuplicateCheckButtonClick = async () => { + try { + const res = await postEmailDuplicateCheck(email); + + if (res.code === HTTPSTATUS.success) { + setIsDuplicateChecked(true); + setDuplicateCheckErrorMsg(""); + } + } catch (error) { + if (axios.isAxiosError(error)) { + setDuplicateCheckErrorMsg(error.response?.data.message); + } else { + setDuplicateCheckErrorMsg((error as Error).message); + } + } + }; + return ( @@ -21,14 +51,22 @@ export default function EmailSubPage({ onNext }: Props) { placeholder="이메일" id="emailInput" value={email} - onChange={(e) => onChange(e.target.value.trim())} + onChange={onEmailChange} /> - {/* TODO: 중복 확인 */} - - From 2d85e91ad1ee431d15a12d00d824a77727bbf8cf Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Thu, 19 Oct 2023 22:38:54 +0900 Subject: [PATCH 09/18] =?UTF-8?q?#4=20feat:=20Signup=20password=20confirm?= =?UTF-8?q?=20mismatch=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx b/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx index e202833..70ee872 100644 --- a/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/PasswordSubPage.tsx @@ -14,6 +14,7 @@ export default function PasswordSubPage({ onNext }: Props) { } = useText({ validators: [validatePassword], }); + const { value: passwordConfirm, isError: isPasswordConfirmError, @@ -22,6 +23,8 @@ export default function PasswordSubPage({ onNext }: Props) { validators: [validatePassword], }); + const isPasswordMismatch = !isPasswordError && password !== passwordConfirm; + return (
@@ -45,7 +48,8 @@ export default function PasswordSubPage({ onNext }: Props) { value={passwordConfirm} onChange={(e) => onPasswordConfirmChange(e.target.value.trim())} /> - {/* TODO: Error 비밀번호가 일치하지 않습니다 */} + + {isPasswordMismatch &&

비밀번호가 일치하지 않습니다.

}
diff --git a/fe/src/router/router.tsx b/fe/src/router/router.tsx index 62e55e4..17111ed 100644 --- a/fe/src/router/router.tsx +++ b/fe/src/router/router.tsx @@ -1,5 +1,6 @@ import { User } from "@api/auth"; import SignUpPage from "@pages/SignUpPage/SignUpPage"; +import { GoogleOAuthProvider } from "@react-oauth/google"; import { Route, createBrowserRouter, @@ -27,7 +28,13 @@ export default (user: User | undefined) => {/* }/> */} - }> + + + + }> {/* } /> */} } /> From 77bdfb7d0610dd9c67e28c64d1fcee97f35194bd Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Sun, 22 Oct 2023 14:54:05 +0900 Subject: [PATCH 12/18] =?UTF-8?q?#4=20feat:=20Popup=20window=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/src/context/WindowContext.tsx | 34 ++++++++++++++++++++++++++++++++ fe/src/router/router.tsx | 11 +++++++---- fe/src/utils/openPopUpWindow.ts | 14 +++++++++++++ fe/tsconfig.json | 1 + 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 fe/src/context/WindowContext.tsx create mode 100644 fe/src/utils/openPopUpWindow.ts diff --git a/fe/src/context/WindowContext.tsx b/fe/src/context/WindowContext.tsx new file mode 100644 index 0000000..4a41bc9 --- /dev/null +++ b/fe/src/context/WindowContext.tsx @@ -0,0 +1,34 @@ +import { ReactNode, createContext, useState } from "react"; + +export const WindowContext = createContext<{ + popUpWindow: Window | null; + onOpenPopUpWindow: (targetWindow: Window) => void; + closePopUpWindow: () => void; +}>({ + popUpWindow: null, + onOpenPopUpWindow: () => {}, + closePopUpWindow: () => {}, +}); + +export function WindowProvider({ children }: { children: ReactNode }) { + const [popUpWindow, setPopUpWindow] = useState(null); + + const onOpenPopUpWindow = (targetWindow: Window) => { + console.log("targetWindow", targetWindow); + setPopUpWindow(targetWindow); + }; + + const closePopUpWindow = () => { + if (popUpWindow) { + popUpWindow.close(); + setPopUpWindow(null); + } + }; + + return ( + + {children} + + ); +} diff --git a/fe/src/router/router.tsx b/fe/src/router/router.tsx index 17111ed..36f11f5 100644 --- a/fe/src/router/router.tsx +++ b/fe/src/router/router.tsx @@ -1,4 +1,5 @@ import { User } from "@api/auth"; +import { WindowProvider } from "@context/WindowContext"; import SignUpPage from "@pages/SignUpPage/SignUpPage"; import { GoogleOAuthProvider } from "@react-oauth/google"; import { @@ -30,10 +31,12 @@ export default (user: User | undefined) => - - + + + + + }> {/* } /> */} } /> diff --git a/fe/src/utils/openPopUpWindow.ts b/fe/src/utils/openPopUpWindow.ts new file mode 100644 index 0000000..b830c6b --- /dev/null +++ b/fe/src/utils/openPopUpWindow.ts @@ -0,0 +1,14 @@ +export default function openPopUpWindow( + url: string, + target: string, + width: number, + height: number +) { + const y = window.outerHeight / 2 + window.screenY - height / 2; + const x = window.outerWidth / 2 + window.screenX - width / 2; + return window.open( + url, + target, + `toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${y}, left=${x}` + ); +} diff --git a/fe/tsconfig.json b/fe/tsconfig.json index d0e0d3e..08e191f 100644 --- a/fe/tsconfig.json +++ b/fe/tsconfig.json @@ -28,6 +28,7 @@ "@assets/*": ["assets/*"], "@pages/*": ["pages/*"], "@components/*": ["components/*"], + "@context/*": ["context/*"], "@hooks/*": ["hooks/*"], "@utils/*": ["utils/*"] } From 2472c39b40825f54efbaa57273da15340196b49a Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Sun, 22 Oct 2023 14:55:57 +0900 Subject: [PATCH 13/18] =?UTF-8?q?#4=20feat:=20Kakao=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../images/kakao_login_medium_narrow.png | Bin 0 -> 2946 bytes .../assets/images/kakao_login_medium_wide.png | Bin 0 -> 3307 bytes fe/src/components/auth/KakaoSignInButton.tsx | 39 +++++ fe/src/config/index.ts | 4 + fe/src/pages/SignInPage.tsx | 136 ++++++++++++++++++ .../pages/SignUpPage/subPages/MainSubPage.tsx | 5 +- fe/src/router/router.tsx | 3 +- 7 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 fe/src/assets/images/kakao_login_medium_narrow.png create mode 100644 fe/src/assets/images/kakao_login_medium_wide.png create mode 100644 fe/src/components/auth/KakaoSignInButton.tsx create mode 100644 fe/src/config/index.ts create mode 100644 fe/src/pages/SignInPage.tsx diff --git a/fe/src/assets/images/kakao_login_medium_narrow.png b/fe/src/assets/images/kakao_login_medium_narrow.png new file mode 100644 index 0000000000000000000000000000000000000000..09bb358843a08576d0029ff8427120c9f5f5c36d GIT binary patch literal 2946 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 pfG!z#l@SOs1cZ>002ovPDHLkV1oS~Dl`B9 literal 0 HcmV?d00001 diff --git a/fe/src/components/auth/KakaoSignInButton.tsx b/fe/src/components/auth/KakaoSignInButton.tsx new file mode 100644 index 0000000..55882d2 --- /dev/null +++ b/fe/src/components/auth/KakaoSignInButton.tsx @@ -0,0 +1,39 @@ +import kakaoLoginButtonImage from "@assets/images/kakao_login_medium_wide.png"; +import { WindowContext } from "@context/WindowContext"; +import openPopUpWindow from "@utils/openPopUpWindow"; +import { CLIENT_URL } from "config"; +import { useContext } from "react"; +import { styled } from "styled-components"; + +export default function KakaoSignInButton() { + const { onOpenPopUpWindow } = useContext(WindowContext); + + const onClick = () => { + const oAuthPopUpWindow = openPopUpWindow( + `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${ + import.meta.env.VITE_KAKAO_CLIENT_ID + }&redirect_uri=${CLIENT_URL}/signin?provider=kakao`, + "kakaoOAuth", + 500, + 600 + )!; // TODO: handle case where popup doesn't show (Ex: user blocked popups) + + onOpenPopUpWindow(oAuthPopUpWindow); + }; + + return ( + + 카카오 로그인 + + ); +} + +const StyledKakaoSignInButton = styled.button` + width: 228px; + height: 44px; + + img { + width: 100%; + object-fit: fill; + } +`; diff --git a/fe/src/config/index.ts b/fe/src/config/index.ts new file mode 100644 index 0000000..220dfc0 --- /dev/null +++ b/fe/src/config/index.ts @@ -0,0 +1,4 @@ +export const CLIENT_URL = + process.env.NODE_ENV === "production" + ? import.meta.env.VITE_CLIENT_URL_PROD + : import.meta.env.VITE_CLIENT_URL_DEV; diff --git a/fe/src/pages/SignInPage.tsx b/fe/src/pages/SignInPage.tsx new file mode 100644 index 0000000..6d51bfb --- /dev/null +++ b/fe/src/pages/SignInPage.tsx @@ -0,0 +1,136 @@ +import useOAuthSignInMutation from "@api/auth/queries/useOAuthSignInMutation"; +import useSignInMutation from "@api/auth/queries/useSignInMutation"; +import GoogleSignInButton from "@components/auth/GoogleSignInButton"; +import KakaoSignInButton from "@components/auth/KakaoSignInButton"; +import useText from "@hooks/useText"; +import { validateEmail } from "@utils/textValidators"; +import { CLIENT_URL } from "config"; +import { WindowContext } from "context/WindowContext"; +import { FormEvent, useContext, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import Routes from "router/Routes"; +import styled from "styled-components"; +import BasePage from "./BasePage"; + +export default function SignInPage() { + const navigate = useNavigate(); + const { popUpWindow, closePopUpWindow } = useContext(WindowContext); + + const { mutate: signInMutate } = useSignInMutation(); + const { mutate: oAuthSignInMutate } = useOAuthSignInMutation(); + + const { + value: email, + error: emailError, + onChange: onEmailChange, + } = useText({ validators: [validateEmail] }); + const { value: password, onChange: onPasswordChange } = useText(); + + const onSignIn = async (e: FormEvent) => { + e.preventDefault(); + signInMutate({ email, password }); + }; + + // Receive OAuth provider and auth code in the popup window from the OAuth provider and send it to the original window. + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const provider = urlParams.get("provider"); + const authCode = urlParams.get("code"); + + if (provider && authCode) { + // Send OAuth provider and auth code to original window. + window.opener.postMessage({ provider, authCode }, CLIENT_URL); + } + }, []); + + // Receive OAuth provider and auth code in original window from popup window. + useEffect(() => { + if (!popUpWindow) return; + + const closeWindowMessageHandler = (e: MessageEvent) => { + if (e.origin === CLIENT_URL) { + const { provider, authCode } = e.data; + if (provider && authCode) { + oAuthSignInMutate({ provider, authCode }); + } + closePopUpWindow(); + } + }; + + window.addEventListener("message", closeWindowMessageHandler); + + return () => { + window.removeEventListener("message", closeWindowMessageHandler); + }; + }, [closePopUpWindow, oAuthSignInMutate, popUpWindow]); + + const isAllFieldsFilled = !!email && !emailError && !!password; + + return ( + +

로그인

+ +
+ + 이메일 + onEmailChange(e.target.value.trim())} + /> + {{emailError}} + + + 비밀번호 + onPasswordChange(e.target.value.trim())} + /> + + + +
+ + + + {/* */} + + +
+ ); +} + +const StyledSignInPage = styled(BasePage)``; + +const Form = styled.form` + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const InputControl = styled.div` + width: 100%; + margin-bottom: 8px; + + &:last-of-type { + margin-bottom: 40px; + } +`; + +const TextInputLabel = styled.label` + width: 100%; + display: block; +`; + +const TextInputError = styled.p` + height: 18px; +`; diff --git a/fe/src/pages/SignUpPage/subPages/MainSubPage.tsx b/fe/src/pages/SignUpPage/subPages/MainSubPage.tsx index ec5b858..aa6e6b0 100644 --- a/fe/src/pages/SignUpPage/subPages/MainSubPage.tsx +++ b/fe/src/pages/SignUpPage/subPages/MainSubPage.tsx @@ -1,4 +1,5 @@ import GoogleSignInButton from "@components/auth/GoogleSignInButton"; +import KakaoSignInButton from "@components/auth/KakaoSignInButton"; import { useNavigate } from "react-router-dom"; import Routes from "router/Routes"; @@ -12,11 +13,9 @@ export default function MainSubPage({ onNext }: Props) { return (
- + {/* TODO: Naver Sign In Button */} - {/* TODO: Kakao Sign In Button */} - diff --git a/fe/src/router/router.tsx b/fe/src/router/router.tsx index 36f11f5..d04d7bf 100644 --- a/fe/src/router/router.tsx +++ b/fe/src/router/router.tsx @@ -1,5 +1,6 @@ import { User } from "@api/auth"; import { WindowProvider } from "@context/WindowContext"; +import SignInPage from "@pages/SignInPage"; import SignUpPage from "@pages/SignUpPage/SignUpPage"; import { GoogleOAuthProvider } from "@react-oauth/google"; import { @@ -38,7 +39,7 @@ export default (user: User | undefined) => }> - {/* } /> */} + } /> } /> From d08a930bb5ac815130dd55ff6ed763253b7eb084 Mon Sep 17 00:00:00 2001 From: Daeram Chung Date: Mon, 23 Oct 2023 15:35:54 +0900 Subject: [PATCH 14/18] =?UTF-8?q?#4=20feat:=20Naver=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fe/index.html | 4 +++ fe/src/components/auth/NaverSignInButton.tsx | 27 +++++++++++++++++++ fe/src/pages/SignInPage.tsx | 3 ++- .../pages/SignUpPage/subPages/MainSubPage.tsx | 3 ++- 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 fe/src/components/auth/NaverSignInButton.tsx diff --git a/fe/index.html b/fe/index.html index 16e61da..42494fa 100644 --- a/fe/index.html +++ b/fe/index.html @@ -4,6 +4,10 @@ FineAnts + +
diff --git a/fe/src/components/auth/NaverSignInButton.tsx b/fe/src/components/auth/NaverSignInButton.tsx new file mode 100644 index 0000000..5e0e16e --- /dev/null +++ b/fe/src/components/auth/NaverSignInButton.tsx @@ -0,0 +1,27 @@ +import { useEffect } from "react"; +import styled from "styled-components"; + +export default function NaverSignInButton() { + const naverLogin = new window.naver.LoginWithNaverId({ + clientId: import.meta.env.VITE_NAVER_CLIENT_ID, + callbackUrl: `${import.meta.env.VITE_CLIENT_URL}/signin?provider=naver`, + isPopup: true, + loginButton: { + color: "green", + type: 3, + height: 40, + }, + }); + + useEffect(() => { + naverLogin.init(); + }, []); + + return ( + + Naver 로그인 + + ); +} + +const StyledNaverSignInButton = styled.div``; diff --git a/fe/src/pages/SignInPage.tsx b/fe/src/pages/SignInPage.tsx index 6d51bfb..0d47791 100644 --- a/fe/src/pages/SignInPage.tsx +++ b/fe/src/pages/SignInPage.tsx @@ -2,6 +2,7 @@ import useOAuthSignInMutation from "@api/auth/queries/useOAuthSignInMutation"; import useSignInMutation from "@api/auth/queries/useSignInMutation"; import GoogleSignInButton from "@components/auth/GoogleSignInButton"; import KakaoSignInButton from "@components/auth/KakaoSignInButton"; +import NaverSignInButton from "@components/auth/NaverSignInButton"; import useText from "@hooks/useText"; import { validateEmail } from "@utils/textValidators"; import { CLIENT_URL } from "config"; @@ -98,7 +99,7 @@ export default function SignInPage() { - {/* */} +