Skip to content
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

oAuth, 로그인 유무 로직 구현 #86

Merged
merged 23 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5eb91fc
✨ Feature(#67): useMemo 사용으로 반복 계산 방지 및 렌더링 최적화
bluetree7878 Dec 4, 2024
110c188
Merge branch 'develop' of https://github.com/modern-agile-team/8term-…
bluetree7878 Dec 5, 2024
2af8139
✨ Feature(#67): 로그인/비로그인 확인 useAuth 훅 생성
bluetree7878 Dec 5, 2024
be2c078
✨ Feature(#67): 로그인 시 fake jwt 생성
bluetree7878 Dec 5, 2024
aec6afb
⚙ Setting(#67): CI-CD Action Test - 이미지 삭제
bluetree7878 Dec 5, 2024
7bf4031
📝 Modify(#67): 실제 Service에는 fake jwt 안 올라가게 수정
bluetree7878 Dec 5, 2024
3b05363
📝 Modify(#67): 반환되는 이미지 있는지 확인 후 <none> 태그 이미지가 있을 경우 삭제
bluetree7878 Dec 5, 2024
09e9640
✨ Feature(#67): 토큰 유무 프로필 Modal 변경 및 Interceptor 수정
bluetree7878 Dec 8, 2024
8cb010a
Merge branch 'develop' of https://github.com/modern-agile-team/8term-…
bluetree7878 Dec 8, 2024
360f161
Merge branch 'develop' of https://github.com/modern-agile-team/8term-…
bluetree7878 Dec 9, 2024
c14dc6a
✨ Feature(#useDropdown hook 생성):
bluetree7878 Dec 9, 2024
01aa72a
✨ Feature(#67): useDropdown hook 생성
bluetree7878 Dec 9, 2024
292c387
Merge branch 'feature/#67/OAuth_2_0' of https://github.com/modern-agi…
bluetree7878 Dec 9, 2024
c591ff6
🔨 Refactor(#67): DropdownMenu -> ProfileDropdownMenu로 네이밍 수정
bluetree7878 Dec 9, 2024
4f00d6e
📝 Modify(#67): handleLogin fakeToken 주석
bluetree7878 Dec 9, 2024
3f5865e
📝 Modify(#67): useAuth -> isLoggedIn으로 훅에서 유틸 함수로 변경
bluetree7878 Dec 9, 2024
36d9fb4
🔨 Refactor(#67): interceptor 테스트 콘솔 제거 및 파일명 오탈자 수정
bluetree7878 Dec 9, 2024
5bd9bbd
📝 Modify(#67): usePopover hook 콜백 memoization 및 외부 클릭 감지 기능
bluetree7878 Dec 11, 2024
53d65a8
📝 Modify(#67): usePopover hook 상태 변경 시 호출되는 callback함수 옵셔널 추가 및 Logou…
bluetree7878 Dec 11, 2024
d37014c
🔨 Refactor(#67): isFirstRender -> isFirstMount 네이밍 변경
bluetree7878 Dec 11, 2024
45d71c6
📝 Modify(#67): handleLogin fake Token 주석
bluetree7878 Dec 11, 2024
317b7b5
🔨 Refactor(#67): useIsFirstMount -> useIsMounted hook 사용
bluetree7878 Dec 12, 2024
ad4d371
📝 Modify(#67): handleLogin fake Token 주석
bluetree7878 Dec 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,8 @@ jobs:
# Docker Compose로 서비스 배포
- name: Docker run
run: |
if [ -n "$(docker images -f "dangling=true" -q)" ]; then
docker rmi $(docker images -f "dangling=true" -q)
fi
docker compose -f /home/ubuntu/docker-compose.yml down
docker compose -f /home/ubuntu/docker-compose.yml up -d --pull always
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
href="https://cdn.jsdelivr.net/gh/fonts-archive/Maplestory/subsets/Maplestory-dynamic-subset.css"
type="text/css"
/>
<link rel="icon" href="public/favicon.svg" />
<link rel="icon" href="/favicon.svg" />
<title>CokoEdu</title>
</head>
<body>
Expand Down
23 changes: 0 additions & 23 deletions src/apis/axios/intercepter.ts

This file was deleted.

26 changes: 26 additions & 0 deletions src/apis/axios/interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import api from './instance';
import { getCookie } from '@utils/cookies';

// 요청 인터셉터
api.interceptors.request.use(config => {
// 쿠키에서 accessToken 가져오기
const accessToken: string | undefined = getCookie('accessToken');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}

return config;
});

// 응답 인터셉터
api.interceptors.response.use(
response => {
// HTTP 상태 코드가 200번대인 경우
console.log(response);
return response;
},
error => {
// HTTP 상태 코드가 에러인 경우
return Promise.reject(error);
}
);
71 changes: 60 additions & 11 deletions src/common/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
import { useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { getImageUrl } from '@utils/getImageUrl';
import HeaderItem from '../ui/HeaderItem';
import { HeaderBox } from './style';
import * as S from '../ui/style';
import HeaderItem from '../ui/HeaderItem';
import Login from '@features/login/ui/Login';
import { ProfileWrapper, ProfileIcon, HeaderIcon } from '../ui/style';
import handleLogout from '@features/login/service/handleLogout';
import isLoggedIn from '@utils/isLoggedIn';
import useModal from '@hooks/useModal';
import useUserStore from '@/store/useUserStore';
import useUserStore from '@store/useUserStore';
import usePopover from '@hooks/usePopover';

export default function Header() {
const points: number = 2999999999;
const lifePoints: number = 5;

const navigate = useNavigate();
const { isShow, openModal, closeModal, Modal } = useModal();
const { user } = useUserStore();

const profileRef = useRef<HTMLDivElement>(null);
const { isOpen, togglePopover, popoverRef } = usePopover({
excludeRefs: [profileRef],
});

const handleProfileClick = () => {
if (isLoggedIn()) {
togglePopover(); // 팝오버 열기/닫기
} else {
openModal(); // 로그인 모달 열기
}
};

return (
<HeaderBox>
{user && (
Expand All @@ -27,14 +48,42 @@ export default function Header() {
/>
</>
)}
<ProfileWrapper onClick={openModal}>
<ProfileIcon src={getImageUrl('테두리.svg')} alt="프로필 테두리" />
<HeaderIcon src={getImageUrl('코코-프로필.svg')} alt="코코 프로필" />
</ProfileWrapper>
{/* Modal 컴포넌트 */}
<Modal isShow={isShow}>
<Login openModal={openModal} closeModal={closeModal} />
</Modal>
<S.ProfileWrapper ref={profileRef} onClick={handleProfileClick}>
<S.ProfileIcon src={getImageUrl('테두리.svg')} alt="프로필 테두리" />
<S.HeaderIcon src={getImageUrl('코코-프로필.svg')} alt="코코 프로필" />
{isLoggedIn() && isOpen && (
<S.ProfilePopover ref={popoverRef} onClick={e => e.stopPropagation()}>
<S.UserNameText>유저이름</S.UserNameText>
<S.UserJoinDate>2024.11.19</S.UserJoinDate>
<S.UserInfoButton
$backgroundColor="#00FAFF"
$boxShadow="0 2px #00E1EC"
onClick={() => navigate('/profile')}
>
프로필
</S.UserInfoButton>
<S.UserInfoButton
$backgroundColor="#3DFF4A"
$boxShadow="0 2px #00EB6A"
onClick={() => navigate('/setting')}
>
설정
</S.UserInfoButton>
<S.UserInfoButton
$backgroundColor="#FF3F3D"
$boxShadow="0 2px #EB0000"
onClick={handleLogout}
>
로그아웃
</S.UserInfoButton>
</S.ProfilePopover>
)}
</S.ProfileWrapper>
{!isLoggedIn() && (
<Modal isShow={isShow}>
<Login openModal={openModal} closeModal={closeModal} />
</Modal>
)}
</HeaderBox>
);
}
2 changes: 2 additions & 0 deletions src/common/layout/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const HeaderBox = styled.header`
height: 42px;
position: fixed;
padding-right: 20px;
z-index: 1;
`;

export const LogoBoxWrapper = styled.div`
Expand All @@ -42,4 +43,5 @@ export const OverRay = styled.div`
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.2);
z-index: 100;
`;
92 changes: 73 additions & 19 deletions src/common/ui/style.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import styled from 'styled-components';
import styled, { keyframes } from 'styled-components';
import { Link } from 'react-router-dom';

interface SectionButtonProps {
$backgroundImage: string;
interface UserInfoButtonProps {
$backgroundColor: string;
$boxShadow: string;
}

interface MenuButtonProps {
$activeStyle: boolean;
}

interface IconWrapperProps {
$color: string;
}

export const SectionButton = styled.button<SectionButtonProps>`
export const SectionButton = styled.button<{ $backgroundImage: string }>`
width: 100px;
height: 75px;
margin-top: 75px;
Expand All @@ -31,7 +24,7 @@ export const MenuButtonWrapper = styled.nav`
display: inline-block;
`;

export const MenuButton = styled.button<MenuButtonProps>`
export const MenuButton = styled.button<{ $activeStyle: boolean }>`
width: 193px;
height: 42px;
font-size: 15px;
Expand All @@ -58,7 +51,7 @@ export const MenuIcon = styled.img`
height: 26px;
`;

export const IconWrapper = styled.div<IconWrapperProps>`
export const IconWrapper = styled.div<{ $color: string }>`
display: flex;
align-items: center;
margin-right: 16px;
Expand All @@ -69,11 +62,6 @@ export const IconWrapper = styled.div<IconWrapperProps>`
color: ${({ $color }) => $color};
`;

export const ProfileWrapper = styled.div`
position: relative;
cursor: pointer;
`;

export const ProfileIcon = styled.img`
position: absolute;
width: 30px;
Expand All @@ -90,3 +78,69 @@ export const LogoImg = styled.img`
width: 147px;
height: 117px;
`;

export const ProfileWrapper = styled.div`
position: relative;
cursor: pointer;
`;

// Popover 열릴 때 애니메이션
const slideIn = keyframes`
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;

export const ProfilePopover = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: absolute;
margin-top: 3px;
right: 0;
cursor: default;
width: 200px;
height: 200px;
background-color: #fff;
border-radius: 15px;
border: 3px solid #ffb53d;
animation: ${slideIn} 0.3s ease-out;
`;

export const UserNameText = styled.p`
color: #000;
font-size: 18px;
text-align: center;
font-weight: 700;
`;

export const UserJoinDate = styled.p`
font-weight: 300;
color: #cbcbcb;
font-size: 12px;
`;

export const UserInfoButton = styled.button<UserInfoButtonProps>`
width: 80%;
height: 30px;
margin-top: 12px;
border: none;
background-color: ${({ $backgroundColor }) => $backgroundColor};
box-shadow: ${({ $boxShadow }) => $boxShadow};
text-align: center;
font-size: 17px;
font-weight: 700;
color: #ffffff;
border-radius: 6px;
text-shadow: -1px 0 #000, 0 1px #000, 1px 0 #000, 0 -1px #000;
&:hover {
transform: scale(1.05);
transition: transform 0.2s ease;
}
`;
23 changes: 23 additions & 0 deletions src/features/login/service/handleLogin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// import { setCookie } from '@utils/cookies';

const BASE_URL = import.meta.env.VITE_BASE_URL;

const handleLogin = (provider: 'google' | 'kakao' | 'github') => {
const redirectUrl = `${BASE_URL}/auth/${provider}`;
window.location.href = redirectUrl;

// 테스트용 accessToken 및 refreshToken 생성 및 쿠키에 저장 (실제 Service에 배포할 때는 주석 달거나 삭제)
// const fakeAccessToken = 'test.access.token';
// const fakeRefreshToken = 'test.refresh.token';

// setCookie('accessToken', fakeAccessToken, { path: '/', maxAge: 3600 }); // 1시간 유효
// setCookie('refreshToken', fakeRefreshToken, {
// path: '/',
// maxAge: 3600 * 24 * 30,
// }); // 30일 유효

// alert('AccessToken 생성 완료: ' + fakeAccessToken);
// alert('RefreshToken 생성 완료: ' + fakeRefreshToken);
};

export default handleLogin;
9 changes: 9 additions & 0 deletions src/features/login/service/handleLogout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { removeCookie } from '@utils/cookies';

const handleLogout = () => {
removeCookie('accessToken');
removeCookie('refreshToken');
window.location.href = '/';
};

export default handleLogout;
8 changes: 0 additions & 8 deletions src/features/login/service/handleSocialLogin.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/features/login/styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import styled, { keyframes } from 'styled-components';
import { getImageUrl } from './../../utils/getImageUrl';

interface SocialLoginLinkProps {
$color: string;
Expand Down
8 changes: 4 additions & 4 deletions src/features/login/ui/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
DashLineHr,
} from '../styles';
import { LogoImg } from '@common/ui/style';
import handleSocialLogin from '../service/handleSocialLogin';
import handleLogin from '../service/handleLogin';

interface LoginProps {
openModal: () => void;
Expand All @@ -28,23 +28,23 @@ export default function Login({ closeModal }: LoginProps) {
<SocialLoginButton
$color="#000000"
$backgroundColor="#ffffff"
onClick={() => handleSocialLogin('google')}
onClick={() => handleLogin('google')}
>
<img src={getImageUrl('구글.svg')} alt="구글 로그인" />
Google 로그인
</SocialLoginButton>
<SocialLoginButton
$color="#000000"
$backgroundColor="#FEE500"
onClick={() => handleSocialLogin('kakao')}
onClick={() => handleLogin('kakao')}
>
<img src={getImageUrl('카카오.svg')} alt="카카오 로그인" />
Kakao 로그인
</SocialLoginButton>
<SocialLoginButton
$color="#ffffff"
$backgroundColor="#000000"
onClick={() => handleSocialLogin('github')}
onClick={() => handleLogin('github')}
>
<img src={getImageUrl('깃허브.svg')} alt="깃허브 로그인" />
GitHub 로그인
Expand Down
Loading