Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[권주현] Week19 #493

Merged
32 changes: 16 additions & 16 deletions api.ts
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api 요청 경로 변경 코드입니다.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CODEIT_BASE_URL } from "@/constants";
import { FormValues } from "./common/Auth/Form";
import {
FolderData,
LinkData,
Comment on lines 3 to 4
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kuum97

타입 또는 인터페이스를 선언할 때, 해당 타입들이 특정 데이터의 타입임을 표현하고 있는데요.
굳이 ~~Data 라는 식으로 타입 네이밍을 하는게 과연 좋을 지 한번 고민해볼 수 있으면 좋겠어요.

Expand All @@ -9,6 +8,7 @@ import {
Response,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kuum97

NextJS에서 이미 서버 리스폰스 객체를 위한 Response라는 예약어가 사용되고 있을텐데요,
page router를 사용할때는 충돌이 없었나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예약어는 NextResponse로 설정되어 있어 충돌은 없지만 Response라는 단어가 너무 포괄적인 단어라 수정해야 할 듯 합니다.

UserData,
} from "./types/api";
import { FormValues } from "./types/form";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kuum97

앞으로 form value들이 굉장히 다양하게 존재하게 될 텐데요.
지금 사용되는 formvalues 타입의 경우, 계정 정보 활용을 위한 폼 데이터를 관리하는 요소로 보여요.
그렇다면 form value보다는 더 좋은, 그리고 직관적인 이름이 있을텐데요.
어떻게 변경하면 좋을 지 한번 고민해볼까요?


interface TokenProp {
token: string;
Expand Down Expand Up @@ -134,52 +134,52 @@ export async function getLinksByFolderId({

// POST

export async function postEmailCheck(email: string): Promise<void | string> {
const response = await fetch(`${CODEIT_BASE_URL}/check-email`, {
export async function postEmailCheck(email: string): Promise<boolean | string> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kuum97

크게 프로젝트 아키텍쳐를 레이어로 나누어보면, 네트워크 요청을 전담하는 controller layer와 비즈니스 로직을 담당하는 service layer, 그리고 결과물을 사용자에게 보여주고 ui interaction을 처리하는 ui layer로 구분해볼 수 있어요.

여기서 postEmailCheck 과 같은 함수들이 우리가 말하는 비즈니스 로직을 담고 있는 서비스 레이어로 평가되는데요.
비즈니스 로직은 ui 레이어에서 최대한 필요한 데이터만 받아와 활용할 수 있도록 해주는 목적을 지니고 있습니다.

지금 작성된 코드를 살펴보면, ui 레이어에서 바로 필요한 데이터를 받아와 활용하기 어려워보여요.
특히, 이 함수의 반환값이 object일수도 있고, string일수도 있고, error 가 던져질 수도 있기 때문인데요.
이를 어떻게 개선 시킬 수 있을지 생각해보면 어떨까요?

const response = await fetch(`${CODEIT_BASE_URL}/users/check-email`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
});
const result = await response.json();

if (response.status === 409) {
const data = await response.json();
return data.error.message;
return result.message;
}

if (!response.ok) {
throw new Error("잘못된 요청입니다.");
}

return;
return result;
}

interface postData {
data: {
accessToken: string;
refreshToken: string;
};
data:
| {
accessToken: string;
refreshToken: string;
}
| { message: string };
}
Comment on lines 158 to 165
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kuum97
마찬가지로 postData라는 인터페이스의 이름은 직관적이지 않아요. 어떤 응답에 대한 타입을 설정한건지를 명시를 해주면 어떨까요?

또한, 서버에서 반환되는 응답이 data type이거나 오류인경우 message: string으로 반환된다면, 이를 공통적으로 활용할 수 있는 ServerResponse와 같은 인터페이스와 제네릭을 조립하는 형태로 타입을 사용하는게 더 좋을 것 같은데 어떻게 생각하시나요?


export async function postSignup({
email,
password,
}: FormValues): Promise<postData> {
const response = await fetch(`${CODEIT_BASE_URL}/sign-up`, {
}: FormValues): Promise<postData | void> {
const response = await fetch(`${CODEIT_BASE_URL}/auth/sign-up`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
});
const data = await response.json();
const result = await response.json();

if (!response.ok) {
return data.error.message;
return result.message;
}

return data;
}

export async function postSignin({
Expand Down
17 changes: 1 addition & 16 deletions common/Auth/Form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
import { SubmitHandler, UseFormHandleSubmit } from "react-hook-form";
import { AuthFormProps } from "@/types/form";
import styles from "./index.module.css";
import { ReactNode } from "react";
import { TProps } from "../Header";

export interface FormValues {
email: string;
password: string;
passwordConfirm?: string;
}

export interface AuthFormProps {
children?: ReactNode;
onSubmit: SubmitHandler<FormValues>;
handleSubmit: UseFormHandleSubmit<FormValues, undefined>;
purpose: TProps;
}

function AuthForm({
children,
Expand Down
12 changes: 6 additions & 6 deletions common/Auth/Input/index.tsx
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기본 html 태그 타입을 상속해주었습니다.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { HTMLInputTypeAttribute, forwardRef, useState } from "react";
import React, { InputHTMLAttributes, forwardRef, useState } from "react";
import { FaEyeSlash, FaEye } from "react-icons/fa";
import styles from "./index.module.css";
import {
Expand All @@ -9,16 +9,15 @@ import {
} from "react-hook-form";
import classNames from "classnames";

interface AuthInputProps {
interface AuthInputProps extends InputHTMLAttributes<HTMLInputElement> {
id: string;
label: string;
type: HTMLInputTypeAttribute;
placeholder: string;
register: FieldValues;
error?: FieldError | Merge<FieldError, FieldErrorsImpl>;
}

function AuthInput(
{ label, type, placeholder, register, error }: AuthInputProps,
{ id, label, type, placeholder, register, error }: AuthInputProps,
ref: React.ForwardedRef<HTMLInputElement>
) {
const [isVisible, setIsVisible] = useState(false);
Expand All @@ -32,10 +31,11 @@ function AuthInput(

return (
<div className={styles.container}>
<label className={styles.inputLabel} htmlFor={type}>
<label className={styles.inputLabel} htmlFor={id}>
{label}
</label>
<input
id={id}
type={type}
placeholder={placeholder}
className={classNames(styles.inputWrapper, {
Expand Down
3 changes: 2 additions & 1 deletion constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//API

export const KAKAO_KEY = "a841341da0099a5d1291638b48030745";
export const CODEIT_BASE_URL = "https://bootcamp-api.codeit.kr/api";
export const CODEIT_BASE_URL =
"https://bootcamp-api.codeit.kr/api/linkbrary/v1";
export const SAMPLE_USER_ID = 1;
export const SOCIALLINKS = [
{ href: "https://www.kakaocorp.com/page", src: "/images/kakaotalk.png" },
Expand Down
10 changes: 3 additions & 7 deletions hooks/auth/useSignin.ts
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSignup과 마찬가지로 쿼리 적용과 커스텀훅으로 묶어 구현해주었는데 인증 후 리다이렉트는 미들웨어를 구현해야 하는데 아직 미구현 상태라서 이 부분은 미완성 상태입니다.

Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { postSignin } from "@/api";
import { FormValues } from "@/common/Auth/Form";
import { AuthHookProp, FormValues } from "@/types/form";
import { useRouter } from "next/router";
import { SubmitHandler, UseFormSetError } from "react-hook-form";
import { SubmitHandler } from "react-hook-form";

interface UseSigninProp {
setError: UseFormSetError<FormValues>;
}

export function useSignin({ setError }: UseSigninProp) {
export function useSignin({ setError }: AuthHookProp) {
const router = useRouter();
const localAccessToken = localStorage.getItem("accessToken");

Expand Down
46 changes: 46 additions & 0 deletions hooks/auth/useSignup.ts
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이메일 중복체크와 회원가입 요청 및 라우팅을 커스텀훅으로 묶어주었습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { postEmailCheck, postSignup } from "@/api";
import { AuthHookProp, FormValues } from "@/types/form";
import { useRouter } from "next/router";
import { SubmitHandler } from "react-hook-form";

function useSignup({ setError }: AuthHookProp) {
const router = useRouter();

const handleEmailCheck = async (e: React.FocusEvent<HTMLInputElement>) => {
const email = e.target.value;
try {
if (email.length === 0) return;

const result = await postEmailCheck(email);

if (typeof result === "string") {
setError(
"email",
{ type: "value", message: result },
{ shouldFocus: true }
);
}

return;
} catch (error) {
console.error(error);
}
};

const handleSignup: SubmitHandler<FormValues> = async ({
email,
password,
}) => {
try {
await postSignup({ email, password });

router.replace("/signin");
} catch (error) {
console.error(error);
}
};

return { handleEmailCheck, handleSignup };
}

export default useSignup;
48 changes: 8 additions & 40 deletions pages/signup/index.page.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,24 @@
import { SubmitHandler, useForm } from "react-hook-form";
import { postEmailCheck, postSignup } from "@/api";
import { useForm } from "react-hook-form";
import { EMAIL_PATTERN, PW_PATTERN } from "@/constants";
import AuthHeader from "@/common/Auth/Header";
import AuthForm, { FormValues } from "@/common/Auth/Form";
import AuthForm from "@/common/Auth/Form";
import AuthInput from "@/common/Auth/Input";
import SocialAuthBox from "@/components/SocialAuthBox";
import styles from "@/styles/Auth.module.css";
import React from "react";
import { useRouter } from "next/router";
import useSignup from "@/hooks/auth/useSignup";
import { FormValues } from "@/types/form";

function Signup() {
const router = useRouter();
const {
register,
handleSubmit,
formState: { errors },
watch,
setError,
} = useForm<FormValues>({ mode: "onBlur" });

const password = watch("password");

const handleEmailCheck = async (e: React.FocusEvent<HTMLInputElement>) => {
const email = e.target.value;
try {
const result = await postEmailCheck(email);
console.log("result", result);

if (result) {
setError(
"email",
{ type: "onBlur", message: result },
{ shouldFocus: true }
);
}

return;
} catch (error) {
console.error(error);
}
};

const handleSignup: SubmitHandler<FormValues> = async ({
email,
password,
}) => {
try {
const result = await postSignup({ email, password });

localStorage.setItem("accessToken", result.data.accessToken);
router.push("/folder");
} catch (error) {
console.error(error);
}
};
const { handleEmailCheck, handleSignup } = useSignup({ setError });

return (
<div className={styles.container}>
Expand All @@ -65,6 +30,7 @@ function Signup() {
onSubmit={handleSignup}
>
<AuthInput
id="email"
label="이메일"
type="text"
placeholder="이메일을 입력해 주세요"
Expand All @@ -79,6 +45,7 @@ function Signup() {
error={errors.email}
/>
<AuthInput
id="password"
label="비밀번호"
type="password"
placeholder="영문, 숫자를 조합해 8자 이상 입력해 주세요"
Expand All @@ -92,6 +59,7 @@ function Signup() {
error={errors.password}
/>
<AuthInput
id="password-confirm"
label="비밀번호 확인"
type="password"
placeholder="비밀번호와 일치하는 값을 입력해 주세요"
Expand Down
24 changes: 24 additions & 0 deletions types/form/index.ts
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유저 인증 폼의 타입을 따로 파일을 만들어 분리해주었습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { TProps } from "@/common/Auth/Header";
import { ReactNode } from "react";
import {
SubmitHandler,
UseFormHandleSubmit,
UseFormSetError,
} from "react-hook-form";

export interface AuthHookProp {
setError: UseFormSetError<FormValues>;
}

export interface FormValues {
email: string;
password: string;
passwordConfirm?: string;
}

export interface AuthFormProps {
children?: ReactNode;
onSubmit: SubmitHandler<FormValues>;
handleSubmit: UseFormHandleSubmit<FormValues, undefined>;
purpose: TProps;
}