일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- Github Actions
- next.js
- 사탕게임
- react
- kakao blind recruitment
- Vanilla JavaScript
- suffixicon
- 백준
- typescript
- 프로그래머스
- locale data
- storybook
- interaction test
- ZOD
- Flutter
- React.memo
- custom hook
- javascript
- Props Drilling
- useMemo
- visual test
- useEffect
- 피보나치 함수
- React-hook-form
- next-auth
- 리팩토링
- 이메일 인증
- context api
- TextFormField
- Python
- Today
- Total
Dev Diary
[Question Cloud] 회원가입 만들기 (2) 본문
이전에 만든 회원가입 정보 입력 폼에 이어 실제로 회원가입 버튼 클릭 시 기입한 이메일로 인증 메일을 전송하고, 메일 안에 있는 인증 링크를 클릭하면 가입이 완료되는 플로우를 완성해보았다.
register 폴더 안에 container 라는 폴더를 생성 후 회원가입 플로우에 필요한 화면을 추가해주었다.
- registerOption: 카카오, 네이버, 구글, 이메일 가입 중 어떤 방식으로 가입할지 버튼을 통해 선택하는 화면
- register: 회원가입에 필요한 닉네임, 이메일, 전화번호 등을 기입하는 화면
- registerVerify: 이메일로 인증 메일을 전송하고, 인증 완료를 대기하는 화면
- registerResult: 회원가입 완료 시 redirect 되는 화면
직관적인 파일명을 짓는 것은 항상 어렵다. 결과적으로 저렇게 지었지만 뭔가 부족한 느낌이 든다...
1. 각 화면에 필요한 API 정의하기
각각 register, registerVerify, registerResult 화면에 사용할 API이다.
import { httpClient } from "@/providers";
import { useMutation } from "@tanstack/react-query";
import { RegisterApiResponse, RegisterFormValues } from "./types";
// 회원가입 요청 API
function useRegisterApi() {
return useMutation({
mutationFn: (data: RegisterFormValues) =>
httpClient<RegisterApiResponse>({
method: "POST",
url: "/user",
data: data,
}),
});
}
// 인증 이메일 재전송 API
function useResendEmailApi() {
return useMutation({
mutationFn: (resendToken: string) =>
httpClient<{ success: boolean }>({
method: "GET",
url: `/user/resend-verification-mail?resendToken=${resendToken}`,
}),
});
}
// 메일 인증을 통해 리다이렉트 후 가입을 완료하기 위한 API
function useEmailVerifyApi() {
return useMutation({
mutationFn: (token: string) =>
httpClient<{ success: boolean }>({
method: "GET",
url: `/user/verify?token=${token}`,
}),
});
}
export { useRegisterApi, useResendEmailApi, useEmailVerifyApi };
2. 서버에 실제 회원가입 데이터 전송 시 필요한 데이터 추가하기
회원가입 데이터 전송 시 서버에 아래와 같은 Request body를 포함해야한다. 하지만, 기존에 z.infer를 사용해서 email, password, phone, name은 타입을 자동으로 추론하도록 했었는데 해당 데이터 타입 안에는 socialRegisterToke와 accountType과 같은 부분은 사용자가 실제로 기입하지 않는 부분이기 때문에 없었다.
그래서 어쩔수 없이(?) 실제 서버 요청에 필요한 데이터 관련 타입은 새로 정의해줬다.
interface RegisterFormValues {
name: string;
email: string;
phone: string;
password: string;
passwordConfirm?: string;
}
// 실제 API 요청에 사용할 데이터 타입
interface RegisterApiRequest extends RegisterFormValues {
socialRegisterToken?: string;
accountType: string;
}
3. 서버에 전송할 accountType 값 판단하기
accountType은 registerOption 화면에서 어떤 버튼을 클릭했는지에 따라 url을 통해 /email, /kakao, /naver, /google 등을 넘겨주도록 하여 이후 useParams()를 이용해 가져올 수 있도록 했다. (라우터는 /register/[provider]/... 이런식으로 설정되어있다.)
그리고 다음 페이지 이동시 사용할 resendToken 값과 email 값을 query string으로 넘겨주었다.
// useRegister.ts
"use client";
import { useEffect, useState } from "react";
import { useRouter, useParams } from "next/navigation";
import { RegisterApiRequest, RegisterFormValues, useRegisterApi } from "../../api";
import { useDialogContext } from "@/providers";
const useRegister = () => {
const { push } = useRouter();
// 여기서 accountType을 가져온다.
const { provider } = useParams();
const { dialogOpen } = useDialogContext();
const [email, setEmail] = useState("");
const { mutate: register, data: registerData, error: registerError, isPending: isRegisterPending } = useRegisterApi();
const handleRegister = (data: RegisterFormValues) => {
const dataToSend: RegisterApiRequest = {
name: data.name,
email: data.email,
password: data.password,
phone: data.phone,
accountType: (provider as string).toUpperCase(),
};
setEmail(data.email);
register(dataToSend);
};
useEffect(() => {
if (registerData && email) {
push(`/register/${provider}/verify?resendToken=${registerData.resendToken}&email=${email}`);
}
}, [registerData, email, push, provider]);
useEffect(() => {
if (registerError) {
dialogOpen();
}
}, [dialogOpen, registerError]);
return {
handleRegister,
registerError,
isRegisterPending,
};
};
export { useRegister };
4. 이메일 인증을 대기하는 registerVerify 화면 구현하기
useSearchParams()를 이용해 query string에서 resendToken, email 값을 가져와 사용할 수 있도록 했고, 이메일 재전송 API 요청 후, 성공적으로 수행되었다면 "이메일이 재전송 되었습니다"라는 Dialog를, 실패했다면 Error Dialog를 띄워줄 수 있도록 했다.
// registerVerify.tsx
"use client";
import React from "react";
import { Button } from "@/components/_shared/ui";
import { useRegisterVerify } from "./useRegisterVerify";
import { RegisterAlarmDialog } from "../../components";
const RegisterVerify = () => {
const { handleResendEmail, email, resendEmailData, resendEmailError, isResendEmailPending } = useRegisterVerify();
return (
<div className="flex flex-col items-center space-y-[32px] w-[620px] m-auto">
<div className="heading1">회원가입</div>
<div className="body2 text-center">
입력하신 이메일로 인증 관련 메일을 전송하였습니다.
<br />
아래의 메일에 전송된 링크를 클릭하면 회원가입이 완료됩니다.
</div>
<div className="heading2 text-navy">{email}</div>
<div className="w-full text-center space-y-[20px]">
<div className="caption text-gray_03">이메일을 받지 못하셨나요?</div>
<Button variant="navy" size="large" onClick={handleResendEmail} disabled={isResendEmailPending}>
{isResendEmailPending ? "이메일 전송중" : "이메일 재전송"}
</Button>
</div>
{resendEmailData && <RegisterAlarmDialog message={"이메일이 재전송 되었습니다"} />}
{resendEmailError && <RegisterAlarmDialog message={resendEmailError?.message} />}
</div>
);
};
export { RegisterVerify };
// useRegisterVerify.ts
import { useEffect } from "react";
import { useResendEmailApi } from "../../api";
import { useSearchParams } from "next/navigation";
import { useDialogContext } from "@/providers";
const useRegisterVerify = () => {
const searchParams = useSearchParams();
const resendToken = searchParams.get("resendToken");
const email = searchParams.get("email");
const { dialogOpen } = useDialogContext();
const {
mutate: resendEmail,
data: resendEmailData,
error: resendEmailError,
isPending: isResendEmailPending,
} = useResendEmailApi();
const handleResendEmail = () => {
if (resendToken) resendEmail(resendToken);
};
useEffect(() => {
if (resendEmailData) {
dialogOpen();
}
}, [dialogOpen, resendEmailData]);
useEffect(() => {
if (resendEmailError) {
dialogOpen();
}
}, [dialogOpen, resendEmailError]);
return {
handleResendEmail,
resendEmailData,
resendEmailError,
isResendEmailPending,
email,
};
};
export { useRegisterVerify };
5. 인증 메일 url과 맞추어 redirect 화면을 구성하고 회원가입 완료 UI 화면 구현하기
/email-verification이란 라우팅(registerResult)을 만들고, 인증 메일에서 링크 클릭시 해당 화면으로 리다이렉트 시켜주며 서버로 parameter에 존재하는 token을 useSearchParams()를 이용해 파싱후 서버로 전송하여 회원가입을 마무리 할 수 있도록 했다.
// registerResult.tsx
"use client";
import { Button, CheckIcon } from "@/components/_shared/ui";
import React from "react";
import { useRegisterResult } from "./useRegisterResult";
const RegisterResult = () => {
const { handleGoLoginPage } = useRegisterResult();
return (
<div className="flex flex-col items-center space-y-[32px] w-[620px] m-auto">
<CheckIcon color="green" size="80" />
<div className="heading1">회원가입이 완료되었습니다.</div>
<Button variant="navy" size="large" onClick={handleGoLoginPage}>
로그인하러 가기
</Button>
</div>
);
};
export { RegisterResult };
// useRegisterResult.ts
"use client";
import { useCallback, useEffect } from "react";
import { useEmailVerifyApi } from "../../api";
import { useSearchParams, useRouter } from "next/navigation";
const useRegisterResult = () => {
const { push } = useRouter();
const searchParams = useSearchParams();
const token = searchParams.get("token");
const { mutate: verifyEmail } = useEmailVerifyApi();
const handleVerifyEmail = useCallback(() => {
if (token) verifyEmail(token);
}, [token, verifyEmail]);
useEffect(() => {
handleVerifyEmail();
}, [handleVerifyEmail]);
const handleGoLoginPage = () => {
push("/login");
};
return { handleGoLoginPage };
};
export { useRegisterResult };
최종 플로우
결과적으로 이메일 인증 링크 클릭 전에는 아래와 같이 user_status가 Pending~으로 되어있었는데, 이메일 인증 링크를 클릭하여 회원가입을 마무리 하게 되면 Active로 변해있는 것을 확인할 수 있다.
다음 게시글에서는 소셜 회원가입을 구현해보겠다. 다만, 네이버와 구글은 개발모드에서 로그인 할 수 있는 계정이 제한되어 있기 때문에 일단 카카오만 구현해보도록 하겠다.
추가로 알게 된 점
- useParams(): URL 파라미터 정보를 받는데 사용
- useSearchParams(): query string을 받는데 사용
'Projects' 카테고리의 다른 글
바닐라 자바스크립트 프로젝트 초기 환경 설정 (2) | 2024.11.11 |
---|---|
[Question Cloud] 회원가입 만들기 (3) (1) | 2024.10.10 |
[Question Cloud] 회원가입 만들기 (1) (2) | 2024.10.06 |
[Question Cloud] Chromatic을 활용한 Storybook 배포 (0) | 2024.09.03 |
[Question Cloud] Storybook 과 Testing 설정하기 (0) | 2024.08.23 |