React를 이용하여 캠핑 앱을 만들어보려고 한다.
- 로그인/회원가입 -> supabase활용
- 캠핑데이터 고캠핑 API활용
- 캠핑 위치 정보 -> 카카오 지도 활용
- React앱 생성
- npm i @supabase/supabase-js
- 환경변수 설정:
.env파일에 url과 Anon 키를 넣어준다.
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
먼저 로그인 시 user테이블에 auth의 정보들이 들어가게 하고 싶었다.
트리거 설정할 때마다 헤맨다;;
여튼 아래와같이 설정해주고 이메일 로그인 시 성공적으로 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();
- 카카오디벨로퍼, 구글 클라우드에 callback URL 설정 해주기
- 필요한 키들을 받아오고 supabase에 입력
- 카카오, 구글, 이메일 로그인 성공
- 이메일 형식인지 확인
- 이미 가입된 이메일인지 확인
- 비밀번호는 최소 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);
}
유효성 검사와 로그인 결과를 토스트 형태로 알림을 표시하도록 구현했다.
[원인]
카카오, 구글 로그인에는 Display name 이 들어와서 user테이블에 name에 닉네임? 설정한 이름이 잘 들어오는데
이메일, 비밀번호만 입력받아 회원가입 시키니까 유저의 닉네임 및 이름이 확인이 안된다.
[해결]
- 닉네임(이름) 인풋 생성
- 회원입 버튼 클릭 시 닉네임을 같이 넣어준다.
트리거를 확인한 후 회원가입 시 users테이블에 넣어준다.
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
name,
},
},
});
[원인]:
supabase에서 인증메일을 보내는 기본 횟수가 아주 적기때문
[해결]:
Authentication → Providers → Auth Providers → Email → Confirm email 끄기
정상적으로 회원가입이 잘 되는것을 확인했다.
[원인]
- ToastContainer 를 import 해줬어야했는데 안함
- import "react-toastify/dist/ReactToastify.css"; import 해줬어야했는데 안함.
그냥 토스트 라이브러리 설치 후 사용하면 될 줄 알았는데, ToastContainer를 import 해주고 css도 import 해줘야 하더라..
[해결]
공식문서의 사용법 확인하고
해당 css 파일을 import
최상단 컴포넌트에 ToastContainer 선언하여 하위 컴포넌트에서도 토스트 알림을 사용할 수 있게 설정
[원인]
회원가입 로직과 로그인 로직이 거의 비슷한데
로그인 생각을 안하고 회원가입만 구현해놔서
비슷한 로직의 파일이 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");
}
};
다음에는 미들웨어 처리를 한 후 정리해보겠다.