React로 캠핑 웹 만들기1 - 로그인 구현

이지영·2025년 1월 28일

📝 프로젝트 개요

React를 이용하여 캠핑 앱을 만들어보려고 한다.

주요기능:

  1. 로그인/회원가입 -> supabase활용
  2. 캠핑데이터 고캠핑 API활용
  3. 캠핑 위치 정보 -> 카카오 지도 활용

🚀 시작하기

Supabase 프로젝트 셋업

  1. React앱 생성
  2. npm i @supabase/supabase-js
  3. 환경변수 설정:
    .env파일에 url과 Anon 키를 넣어준다.
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

🔐 로그인 구현

먼저 로그인 시 user테이블에 auth의 정보들이 들어가게 하고 싶었다.

sql 트리거 필요

트리거 설정할 때마다 헤맨다;;
여튼 아래와같이 설정해주고 이메일 로그인 시 성공적으로 auth와 user테이블에 정보가 잘 저장되는 것을 확인했다.

-- users 테이블 생성
create table public.users (
  id uuid references auth.users on delete cascade not null primary key,
  email varchar,
  name varchar,
  avatar_url varchar,
  provider varchar,
  created_at timestamp with time zone default timezone('utc'::text, now()) not null,
  updated_at timestamp with time zone default timezone('utc'::text, now()) not null
);

-- RLS 정책 설정
alter table public.users enable row level security;

create policy "Users are viewable by everyone."
  on users for select
  using ( true );

create policy "Users can insert their own record."
  on users for insert
  with check ( auth.uid() = id );

create policy "Users can update own record."
  on users for update
  using ( auth.uid() = id );

-- 트리거 함수 생성
create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.users (id, email, name, avatar_url, provider)
  values (
    new.id,
    new.email,
    case 
      when new.raw_user_meta_data->>'full_name' is not null then new.raw_user_meta_data->>'full_name'
      else new.raw_user_meta_data->>'name'
    end,
    case 
      when new.raw_user_meta_data->>'avatar_url' is not null then new.raw_user_meta_data->>'avatar_url'
      else new.raw_user_meta_data->>'picture'
    end,
    new.raw_user_meta_data->>'provider'
  );
  return new;
end;
$$ language plpgsql security definer;

-- 트리거 생성
create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();

🔑 이메일, 카카오, 구글 로그인 구현

  1. 카카오디벨로퍼, 구글 클라우드에 callback URL 설정 해주기
  2. 필요한 키들을 받아오고 supabase에 입력
  3. 카카오, 구글, 이메일 로그인 성공

구글 클라우드 설정 참고 링크

✅ 이메일 로그인 유효성 검사

  1. 이메일 형식인지 확인
  2. 이미 가입된 이메일인지 확인
  3. 비밀번호는 최소 6자 이상
  const validateForm = async (email, password, mode) => {
    const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!emailPattern.test(email)) {
      toast.error(ERROR_MESSAGES.INVALID_EMAIL);
      return false;
    }

    if (password.length < 6) {
      toast.error(ERROR_MESSAGES.SHORT_PASSWORD);
      return false;
    }
	// 로그인일때는 처리 x 회원가입 시 작동
    if (mode === "signUp") {
      const { data } = await supabase
        .from("users")
        .select("email")
        .eq("email", email);
	  // 이미 가입된 아이디인지 확인
      if (data?.length > 0) {
        toast.error(ERROR_MESSAGES.EMAIL_EXISTS);
        return false;
      }
    }

    return true;
  };

회원가입 완료 시 로그인 페이지로 useNavigate를 사용하여 이동시켜주었다.

추가로 toast라는 라이브러리를 활용하여 에러 메시지를 보여주고 싶었다.
   1. 해당 라이브러리 설치
      npm i react-toastify
   2. 최상위 컴포넌트에서 ToastContainer로 감싸기
   3. 최상위 컴포넌트에서 import "react-toastify/dist/ReactToastify.css" 임포트
   4. 사용할 컴포넌트에서 import { toast } from "react-toastify"; 임포트
   5. 성공 및 에러 메시지를 toast 메시지로 변경

	// 토스트를 사용할 부분에 ToastContainer추가
	<ToastContainer
        position="bottom-center"
        limit={1}
        closeButton={false}
        autoClose={2000}
        hideProgressBar
      />

	// 수정전
	setSuccessMessage("회원가입이 완료되었습니다.");
	setTimeout(() => {
		nav("/login");
	}, 2000);

	// 수정후 
	if (error) {
        toast.error(ERROR_MESSAGES.EMAIL_EXISTS);
        return false;
      }
      toast.success(ERROR_MESSAGES.SIGN_UP_SUCCESS);
      return true;
    } catch (error) {
      toast.error(error.message);
      return false;
    } finally {
      setIsLoading(false);
    }

유효성 검사와 로그인 결과를 토스트 형태로 알림을 표시하도록 구현했다.

🚨 트러블 슈팅

트러블 1. 이메일 로그인, 소셜 로그인 가입 정보 불일치

[원인]

카카오, 구글 로그인에는 Display name 이 들어와서 user테이블에 name에 닉네임? 설정한 이름이 잘 들어오는데
이메일, 비밀번호만 입력받아 회원가입 시키니까 유저의 닉네임 및 이름이 확인이 안된다.

[해결]

  1. 닉네임(이름) 인풋 생성
  2. 회원입 버튼 클릭 시 닉네임을 같이 넣어준다.
    트리거를 확인한 후 회원가입 시 users테이블에 넣어준다.
const { data, error } = await supabase.auth.signUp({
  email,
  password,
  options: {
    data: {
      name,
    },
  },
});

트러블 2. email rate limit exceeded 에러

[원인]:

supabase에서 인증메일을 보내는 기본 횟수가 아주 적기때문

[해결]:

Authentication → Providers → Auth Providers → Email → Confirm email 끄기

정상적으로 회원가입이 잘 되는것을 확인했다.

트러블 3. 토스트 알림창 안뜸

[원인]

  1. ToastContainer 를 import 해줬어야했는데 안함
  2. import "react-toastify/dist/ReactToastify.css"; import 해줬어야했는데 안함.

그냥 토스트 라이브러리 설치 후 사용하면 될 줄 알았는데, ToastContainer를 import 해주고 css도 import 해줘야 하더라..

[해결]

공식문서의 사용법 확인하고
해당 css 파일을 import
최상단 컴포넌트에 ToastContainer 선언하여 하위 컴포넌트에서도 토스트 알림을 사용할 수 있게 설정

트러블 4. 회원가입과 로그인 로직 비슷함

[원인]

회원가입 로직과 로그인 로직이 거의 비슷한데
로그인 생각을 안하고 회원가입만 구현해놔서
비슷한 로직의 파일이 2개가 있어야되는 상황이였다.

[해결]

mode를 props로 넘겨주어 한 jsx파일로 재사용 가능하게 수정했다.
로그인에는 mode="signIn"
회원가입에는 mode="signUp"

const handleSubmit = async (e) => {
    e.preventDefault();

    const success = await (mode === "signIn"
      ? signIn(formData.email, formData.password)
      : signUp(formData.email, formData.password, formData.nickname));

    if (success) {
      nav(mode === "signIn" ? "/" : "/signin");
    }
  };

다음에는 미들웨어 처리를 한 후 정리해보겠다.

0개의 댓글