<aside> 💻

feature/auth 브랜치 작업 내역

feature/auth 브랜치의 주요 목적은 인증/인가 기능 구현입니다.

구현할 주요 기능들:

  1. 회원가입 기능

    image.png

  2. 로그인 기능

    image.png

  3. 인증 상태 관리

  4. 프로필 수정 기능

    image.png

  5. 에러 처리

구현 상세 가이드

1. 회원가입 기능

회원가입 기능은 다음 단계로 구현되었습니다. 각 단계는 서로 연결되어 작동합니다.

1.1 API 타입 정의

타입스크립트의 타입 안정성을 위해 API 요청과 응답의 타입을 정의합니다.

// src/types/auth.ts
export interface RegisterRequest {
  id: string;
  password: string;
  nickname: string;
}

export interface RegisterResponse {
  success: boolean;
  message?: string;
}

1.2 API 함수 구현

axios를 사용하여 서버와 통신하는 함수를 구현합니다.

// src/services/api.ts
export const authAPI = {
  register: async (data: RegisterRequest): Promise<ApiResponse<RegisterResponse>> => {
    const response = await api.post("/register", data);
    return response.data;
  },
};

1.3 React Query Mutation 설정

서버와의 통신을 관리하기 위한 mutation hook을 생성합니다.

// src/hooks/queries/useAuthQuery.ts
export const useRegisterMutation = () => {
  return useMutation({
    mutationFn: (data: RegisterRequest) => authAPI.register(data),
    onError: (error) => {
      console.error('Registration failed:', error);
    },
  });
};

1.4 RegisterForm 컴포넌트 구현

사용자 입력을 받고 처리하는 폼 컴포넌트입니다.

// src/components/auth/RegisterForm.tsx
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useRegisterMutation } from "@/hooks/queries/useAuthQuery";
import Button from "@/components/common/Button";
import Input from "@/components/common/Input";

const RegisterForm = () => {
  const navigate = useNavigate();
  const { mutateAsync: registerMutation } = useRegisterMutation();
  const [formData, setFormData] = useState({
    id: "",
    password: "",
    nickname: "",
  });
  const [errors, setErrors] = useState({
    id: "",
    password: "",
    nickname: "",
  });

  const validateForm = () => {
    const newErrors = {
      id: "",
      password: "",
      nickname: "",
    };
    let isValid = true;

    if (!formData.id) {
      newErrors.id = "아이디를 입력해주세요";
      isValid = false;
    } else if (formData.id.length < 4) {
      newErrors.id = "아이디는 4자 이상이어야 합니다";
      isValid = false;
    }

    if (!formData.password) {
      newErrors.password = "비밀번호를 입력해주세요";
      isValid = false;
    } else if (formData.password.length < 6) {
      newErrors.password = "비밀번호는 6자 이상이어야 합니다";
      isValid = false;
    }

    if (!formData.nickname) {
      newErrors.nickname = "닉네임을 입력해주세요";
      isValid = false;
    }

    setErrors(newErrors);
    return isValid;
  };

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    
    if (!validateForm()) return;

    try {
      await registerMutation(formData);
      alert("회원가입이 완료되었습니다.");
      navigate("/login");
    } catch {
      alert("회원가입에 실패했습니다.");
    }
  };

  return (
    <form onSubmit={handleSubmit} className="flex flex-col gap-4">
      <Input
        label="아이디"
        name="id"
        value={formData.id}
        onChange={handleChange}
        error={errors.id}
        placeholder="아이디를 입력하세요"
      />
      <Input
        type="password"
        label="비밀번호"
        name="password"
        value={formData.password}
        onChange={handleChange}
        error={errors.password}
        placeholder="비밀번호를 입력하세요"
      />
      <Input
        label="닉네임"
        name="nickname"
        value={formData.nickname}
        onChange={handleChange}
        error={errors.nickname}
        placeholder="닉네임을 입력하세요"
      />
      <Button type="submit">회원가입</Button>
      <Button
        type="button"
        variant="secondary"
        onClick={() => navigate("/login")}
      >
        로그인으로 이동
      </Button>
    </form>
  );
};

export default RegisterForm;