로그인, 회원가입 화면
page > login
const [formError, setFormError] = useState<boolean>(false); // 로그인, 회원가입 에러
const [tabLogin, setTabLogin] = useState<boolean>(true); // 로그인, 회원가입 탭
const router = useRouter();
const { isLogin } = useAuth();
useEffect(() => {
// 로그인된 상태에서 /login 들어오면 홈으로
if (isLogin) {
void router.replace("/");
} else {
// active 클래스 1초 뒤 삭제
const timer = setTimeout(() => {
setFormError(false);
}, 1000);
return () => {
clearTimeout(timer);
};
}
}, [formError, isLogin]);
(isLogin 하단 context API 코드 참고)
- 로그인, 회원가입에서 setFormError가 넘어오면 클래스 active가 추가된다.
스타일을 위해서 1초뒤 삭제했다.
- 로그인을 한 상태에서 로그인페이지에 다시 올 수 도 있으니 그때는 / 홈으로 이
로그인 컴포넌트
componentes > formLogin.tsx
const emailInput = useRef<HTMLInputElement>(null);
const pwdInput = useRef<HTMLInputElement>(null);
const router = useRouter();
async function login(email: string, password: string) {
try {
const userLogin = await signInWithEmailAndPassword(auth, email, password);
setFormError(false);
router.back();
return userLogin;
} catch (error) {
console.log(error);
setFormError(true);
return null;
}
}
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const email = emailInput.current?.value ?? "";
const password = pwdInput.current?.value ?? "";
await login(email, password);
};
- 로그인을 하면 이전 화면으로 이동
- onSubmit 여기서도 email, password는 ref로 input 값을 받아왔다.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup
- 원래 onSubmit에 router.back()을 했는데 오류가 나왔었다.
찾아보니 컴포넌트가 이미 언마운트되었는데도 상태를 업데이트하려고 하면 나온다고 한다.
그래서 페이지 이동을 login에서 진행되게 바꿔주었다.
로그아웃
export default function LayoutHeader({ className }: { className?: string }) {
const { isLogin } = useAuth();
const { currentModal, openModal, closeModal } = useModal();
// 로그아웃 클릭 이벤트
const onClickLogout = async () => {
try {
await signOut(auth);
openModal("logout");
} catch (error) {
console.log(error);
}
};
헤더에 있는 로그아웃 버튼
- 버튼을 클릭하면 로그아웃이 되고 모달을 띄운다.
💡로그아웃 header.tsx
더보기
export default function LayoutHeader({ className }: { className?: string }) {
const isMobile = useIsMobile();
const [menuOpen, setMenuOpen] = useState<boolean>(false);
const { isLogin } = useAuth();
const { currentModal, openModal, closeModal } = useModal();
// 로그아웃 클릭 이벤트
const onClickLogout = async () => {
try {
await signOut(auth);
openModal("logout");
} catch (error) {
console.log(error);
}
};
// 모바일 메뉴 토글 클릭이벤트
const onClickMenu = () => {
setMenuOpen((prev) => !prev);
};
return (
<>
<Header className={className}>
<div className="header_wrap">
<Link href="/" passHref>
<Logo>
<span className="sr_only">요즘캠핑</span>
</Logo>
</Link>
{!isMobile ? (
<Menu>
<li>
<Link href="/campingReview" passHref>
요즘 캠핑 후기
</Link>
</li>
<li>
<Link href={!isLogin ? "/login" : "/myCamping"} passHref>
내 캠핑장
</Link>
</li>
<li className="login">
{!isLogin ? (
<Link href="/login" passHref>
로그인
</Link>
) : (
<button onClick={onClickLogout}>로그아웃</button>
)}
</li>
</Menu>
) : (
<MobileHeader>
<li className="login">
{!isLogin ? (
<Link href="/login" passHref>
로그인
</Link>
) : (
<button onClick={onClickLogout}>로그아웃</button>
)}
</li>
<li>
<MobileMenuBtn onClick={onClickMenu}>
<IoMenu />
</MobileMenuBtn>
</li>
</MobileHeader>
)}
{/* 모바일메뉴 */}
{isMobile && (
<MobileMenuModal menuOpen={menuOpen} onClick={onClickMenu} />
)}
{/* 후기, 내 캠핑장 alert */}
{currentModal === "logout" && (
<Modal
currentModal={currentModal}
hide={closeModal}
message="로그아웃 되었습니다!"
/>
)}
</div>
</Header>
</>
);
}
💡전체코드 formLogin.tsx
더보기
export default function FormLogin({
formError,
setFormError,
}: {
formError: boolean;
setFormError: (value: boolean) => void;
}) {
const emailInput = useRef<HTMLInputElement>(null);
const pwdInput = useRef<HTMLInputElement>(null);
const router = useRouter();
async function login(email: string, password: string) {
try {
const userLogin = await signInWithEmailAndPassword(auth, email, password);
setFormError(false);
router.back();
return userLogin;
} catch (error) {
console.log(error);
setFormError(true);
return null;
}
}
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const email = emailInput.current?.value ?? "";
const password = pwdInput.current?.value ?? "";
await login(email, password);
};
return (
<Form onSubmit={onSubmit}>
<p>Email</p>
<input
type="email"
ref={emailInput}
name="email"
placeholder="test@email.com"
/>
<p className="pwd">Password</p>
<input
type="password"
ref={pwdInput}
name="password"
placeholder="test1234"
/>
{formError && <p className="error">이메일, 비밀번호를 확인해주세요.</p>}
<input className="btn" type="submit" value="Login" />
</Form>
);
}
💡 전체 코드 useAuth
- 나는 우선 유저정보는 필요없고 로그인 상태만 필요해서 login이 됐는지 안됐는지만 확인하는 용도로 context를 만들었다.
더보기
import { auth } from "@/firebase/firebase";
import { onAuthStateChanged } from "firebase/auth";
import { createContext, useContext, useEffect, useState } from "react";
interface AuthContextType {
isLogin: boolean;
}
export const AuthContext = createContext<AuthContextType>({
isLogin: false,
});
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [isLogin, setIsLogin] = useState<boolean>(false);
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setIsLogin(true);
} else {
setIsLogin(false);
}
});
}, [auth]);
return (
<AuthContext.Provider value={{ isLogin }}>{children}</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("context가 없을 경우");
}
return context;
};
💡 전체 코드 login
더보기
export default function index() {
const [formError, setFormError] = useState<boolean>(false);
const [tabLogin, setTabLogin] = useState<boolean>(true);
const router = useRouter();
const { isLogin } = useAuth();
useEffect(() => {
// 로그인된 상태에서 /login 들어오면 홈으로
if (isLogin) {
void router.replace("/");
} else {
// active 클래스 1초 뒤 삭제
const timer = setTimeout(() => {
setFormError(false);
}, 1000);
return () => {
clearTimeout(timer);
};
}
}, [formError, isLogin]);
if (isLogin) {
return null;
}
return (
<Wrap>
<BoxWrap>
<LoginBox className={formError ? "active" : ""}>
<div className="left">
<h2>
Welcome
<br />
Daily Camping!
</h2>
<img src="/image/login_camping.png" alt="캠핑 이미지" />
</div>
<div className="right">
<Link href="/" passHref>
<a className="link_home">
홈 화면으로 돌아가기
<IoIosArrowForward />
</a>
</Link>
{/* 로그인/ 회원가입 탭 */}
{tabLogin ? (
<div className="form">
<h2>Login</h2>
<p className="info">아래 테스트 이메일로 로그인 가능합니다:)</p>
<FormLogin />
</div>
) : (
<div className="form">
<h2>Sign Up</h2>
<p className="info">회원가입 입니다:)</p>
<FormJoin setFormError={setFormError} />
</div>
)}
<Button
onClick={() => {
setTabLogin((prev) => !prev);
}}
className="tab"
>
{tabLogin ? "Sign Up" : "Login"}
<IoIosArrowForward />
</Button>
</div>
</LoginBox>
</BoxWrap>
</Wrap>
);
}
참고 블로그
'언어 > Next.js' 카테고리의 다른 글
[Next 고캠핑] 파이어베이스 좋아요 기능 추가 (1) | 2024.08.24 |
---|---|
[Next 고캠핑] 로컬 데이터 파이어베이스로 변경하기 (0) | 2024.08.21 |
[Next 고캠핑] 파이어베이스 중복 오류, 회원가입 (0) | 2024.08.12 |
[Next 고캠핑] 뒤로가기 클릭시 모달창 닫기 (0) | 2024.08.12 |
[Next 고캠핑] 모달 만들기 (1) | 2024.07.31 |