-
Notifications
You must be signed in to change notification settings - Fork 2
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
Feat/#5: 카카오 로그인 구현 #8
Merged
Merged
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
e778c6a
feat: next-auth 라이브러리 설치
minjeongss 20b127f
feat: auth 모든 서비스 적용을 위한 sessionWrapper 구현
minjeongss 367feea
feat: layout에 sessionProvider 적용
minjeongss c85e97f
feat: auth에서 kakao provider 설정
minjeongss 8c11d9a
feat: login 페이지에 카카오 로그인 적용
minjeongss 7170005
feat: login 페이지에서 auth 거치지 않고 카카오 로그인 이동 구현
minjeongss 5b1bb4e
feat: 로그인 후, home으로 이동하도록 구현
minjeongss 829a657
feat: jwt, session 설정 구현
minjeongss 28591df
feat: 백엔드 발급 토큰 저장 구현
minjeongss File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,174 +1,24 @@ | ||
'use client'; | ||
|
||
import { useState } from 'react'; | ||
import { useRouter } from 'next/navigation'; | ||
import toast from 'react-hot-toast'; | ||
import axiosInstance from '@/lib/axios'; | ||
import { | ||
LoginWrapper, | ||
ButtonSignupWrapper, | ||
LogoContainer, | ||
} from '@/app/(auth)/login/Login.styles'; | ||
import Logo from '@/styles/Icon/Logo.svg'; | ||
// import MainLayout from '@/styles/Icon/mainLayout.svg'; // 용량이 너무 크다. . . | ||
import Image from 'next/image'; | ||
import Input from '@common/Input/Input'; | ||
import AutoLoginCheckbox from '@layout/Login/AutoLogin'; | ||
import Button from '@common/Button/Button'; | ||
import SignupTabs from '@layout/Login/SignUpTabs'; | ||
import useUserStore from '@/stores/useUserStore'; | ||
import ValidationMessage from '@/components/Common/ValidationMessage/ValidationMessage'; | ||
import { signIn, signOut, useSession } from 'next-auth/react'; | ||
import { LoginWrapper } from './Login.styles'; | ||
|
||
interface LoginError { | ||
email: string; | ||
password: string; | ||
} | ||
|
||
const Login = () => { | ||
const router = useRouter(); | ||
const { setNickname, setEmail } = useUserStore(); | ||
|
||
const [isHiddenPassword, setIsHiddenPassword] = useState(true); | ||
const [emailInput, setEmailInput] = useState(''); | ||
const [password, setPassword] = useState(''); | ||
const [errors, setErrors] = useState<LoginError>({ email: '', password: '' }); | ||
const [isLoading, setIsLoading] = useState(false); | ||
const [isFormValid, setIsFormValid] = useState(false); | ||
|
||
const handlePasswordVisibility = () => { | ||
setIsHiddenPassword((prev) => !prev); | ||
}; | ||
|
||
const validateEmail = (email: string): boolean => { | ||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
return emailRegex.test(email); | ||
}; | ||
|
||
const validateForm = (): boolean => { | ||
let isValid = true; | ||
const newErrors = { email: '', password: '' }; | ||
|
||
if (emailInput) { | ||
if (!validateEmail(emailInput)) { | ||
newErrors.email = '이메일 형식이 올바르지 않습니다 (예: [email protected])'; | ||
isValid = false; | ||
} | ||
} | ||
|
||
if (!emailInput || !password) { | ||
isValid = false; | ||
} | ||
|
||
setErrors(newErrors); | ||
setIsFormValid(isValid); | ||
return isValid; | ||
}; | ||
|
||
const handleLogin = async (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
|
||
if (!validateForm()) { | ||
return; | ||
} | ||
|
||
setIsLoading(true); | ||
try { | ||
const response = await axiosInstance.post('/api/login', { | ||
email: emailInput, | ||
password, | ||
}); | ||
|
||
const nickname = response.data; | ||
|
||
setEmail(emailInput); | ||
setNickname(nickname); | ||
|
||
localStorage.setItem('userId', emailInput); | ||
toast.success(`안녕하세요 ${nickname}님 환영합니다!`); | ||
router.push('/home'); | ||
} catch (error) { | ||
console.error('Login error:', error); | ||
setErrors({ | ||
email: '', | ||
password: '이메일 또는 비밀번호가 올바르지 않습니다.', | ||
}); | ||
setIsFormValid(false); | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}; | ||
|
||
const handleEmailChange = (value: string) => { | ||
setEmailInput(value); | ||
if (errors.email) { | ||
setErrors({ ...errors, email: '' }); | ||
} | ||
|
||
if (value && !validateEmail(value)) { | ||
setErrors((prev) => ({ | ||
...prev, | ||
email: '이메일 형식이 올바르지 않습니다 (예: [email protected])', | ||
})); | ||
setIsFormValid(false); | ||
} else { | ||
setIsFormValid(!!value && !!password); | ||
} | ||
}; | ||
|
||
const handlePasswordChange = (value: string) => { | ||
setPassword(value); | ||
setErrors({ ...errors, password: '' }); | ||
setIsFormValid(!!value && !!emailInput && validateEmail(emailInput)); | ||
}; | ||
const LoginPage = () => { | ||
const { data: session } = useSession(); | ||
console.log(session?.accessToken); | ||
|
||
return ( | ||
<LoginWrapper> | ||
<LogoContainer> | ||
<Image src={Logo} alt="logo" priority /> | ||
{/* <Image src={MainLayout} alt="mainLayout" /> */} | ||
</LogoContainer> | ||
<form onSubmit={handleLogin} autoComplete="off"> | ||
<Input | ||
title="이메일" | ||
placeholder="이메일을 입력해주세요" | ||
onChange={handleEmailChange} | ||
autoComplete="new-email" | ||
validation={ | ||
errors.email ? ( | ||
<ValidationMessage message={errors.email} type="error" /> | ||
) : null | ||
} | ||
/> | ||
<Input | ||
title="비밀번호" | ||
placeholder="비밀번호를 입력해주세요" | ||
isButton={true} | ||
isHiddenPassword={isHiddenPassword} | ||
handleClick={handlePasswordVisibility} | ||
onChange={handlePasswordChange} | ||
type={isHiddenPassword ? 'password' : 'text'} | ||
autoComplete="new-password" | ||
validation={ | ||
errors.password ? ( | ||
<ValidationMessage message={errors.password} type="error" /> | ||
) : null | ||
} | ||
/> | ||
<AutoLoginCheckbox /> | ||
<ButtonSignupWrapper> | ||
<Button | ||
buttonType={isFormValid ? 'purple' : 'gray'} | ||
buttonSize="large" | ||
buttonHeight="default" | ||
styleType="coloredBackground" | ||
label="로그인" | ||
disabled={isLoading} | ||
/> | ||
<SignupTabs /> | ||
</ButtonSignupWrapper> | ||
</form> | ||
{session ? ( | ||
<> | ||
<p>{session.user?.email}님 환영합니다</p> | ||
<button onClick={() => signOut()}>logout</button> | ||
</> | ||
) : ( | ||
<button onClick={() => signIn('kakao')}>login</button> | ||
)} | ||
</LoginWrapper> | ||
); | ||
}; | ||
|
||
export default Login; | ||
export default LoginPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import NextAuth, { NextAuthOptions } from 'next-auth'; | ||
import KakaoProvider from 'next-auth/providers/kakao'; | ||
|
||
const authOptions: NextAuthOptions = { | ||
providers: [ | ||
KakaoProvider({ | ||
clientId: process.env.KAKAO_CLIENT_ID as string, | ||
clientSecret: process.env.KAKAO_CLIENT_SECRET as string, | ||
}), | ||
], | ||
session: { | ||
strategy: 'jwt', //JWT 기반 인증 | ||
maxAge: 24 * 60 * 60, // 24시간 | ||
}, | ||
secret: process.env.NEXTAUTH_SECRET, //JWT 암호화 키 설정 | ||
callbacks: { | ||
async signIn({ user }) { | ||
return !!user.email; | ||
}, | ||
async jwt({ token, account }) { | ||
if (account) { | ||
token.accessToken = account.access_token; | ||
token.refreshToken = account.refresh_token; | ||
} | ||
return token; | ||
}, | ||
async session({ session, token }) { | ||
session.accessToken = token.accessToken as string; | ||
return session; | ||
}, | ||
}, | ||
}; | ||
Comment on lines
+4
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
auth에 대한 구조를 위와 같이 구성했습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 구조 확인했어요 ! 정리해주신덕에 조금은 수월하게 작업하지 않을까 싶네요 ! |
||
|
||
const handler = NextAuth(authOptions); | ||
|
||
export { authOptions, handler as GET, handler as POST }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
'use client'; | ||
import { SessionProvider } from 'next-auth/react'; | ||
|
||
const SessionWrapper = ({ children }: { children: React.ReactNode }) => { | ||
return <SessionProvider>{children}</SessionProvider>; | ||
}; | ||
|
||
export default SessionWrapper; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import 'next-auth'; | ||
|
||
declare module 'next-auth' { | ||
interface Session { | ||
accessToken?: string; | ||
} | ||
|
||
interface JWT { | ||
accessToken?: string; | ||
refreshToken?: string; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useSession을 통해
accessToken
에 접근 가능합니다.