Skip to content

Commit

Permalink
Merge pull request #290 from Jaymyong66/feat/signup_login
Browse files Browse the repository at this point in the history
로그아웃 기능, auth 상태를 전역으로 관리
  • Loading branch information
Hain-tain authored Aug 6, 2024
2 parents d6398ff + d92719f commit 0ce4fe8
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 29 deletions.
31 changes: 28 additions & 3 deletions frontend/src/api/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const API_URL = process.env.REACT_APP_API_URL;

export const SIGNUP_API_URL = `${API_URL}/signup`;
export const LOGIN_API_URL = `${API_URL}/login`;
export const LOGOUT_API_URL = `${API_URL}/logout`;
export const LOGIN_STATE_API_URL = `${API_URL}/login/check`;
export const CHECK_USERNAME_API_URL = `${API_URL}/check-username`;
export const CHECK_EMAIL_API_URL = `${API_URL}/check-email`;
Expand All @@ -26,10 +27,24 @@ export const postLogin = async (loginInfo: LoginRequest) => {
return response;
};

export const postLogout = async () => {
const response = await customFetch({
method: 'POST',
url: `${LOGOUT_API_URL}`,
});

return response;
};

export const getLoginState = async () => {
const url = `${LOGIN_STATE_API_URL}`;

const response = await customFetch({ url });
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (response.status === 401) {
throw new Error('로그인을 해주세요.');
Expand All @@ -46,7 +61,12 @@ export const checkEmail = async (email: string) => {
const params = new URLSearchParams({ email });
const url = `${CHECK_EMAIL_API_URL}?${params}`;

const response = await customFetch({ url });
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (response.status === 409) {
throw new Error('중복된 이메일입니다.');
Expand All @@ -63,7 +83,12 @@ export const checkUsername = async (username: string) => {
const params = new URLSearchParams({ username });
const url = `${CHECK_USERNAME_API_URL}?${params}`;

const response = await customFetch({ url });
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
});

if (response.status === 409) {
throw new Error('중복된 닉네임입니다.');
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export {
LOGIN_API_URL,
LOGIN_STATE_API_URL,
SIGNUP_API_URL,
LOGOUT_API_URL,
} from './authentication';
49 changes: 31 additions & 18 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Link } from 'react-router-dom';

import { logoIcon, newTemplateIcon, userMenuIcon } from '@/assets/images';
import { Flex, Heading, Text } from '@/components';
import { Button, Flex, Heading, Text } from '@/components';
import { useCheckLoginState } from '@/hooks/authentication';
import { theme } from '@/style/theme';
import { useAuth } from '@/hooks/authentication/useAuth';
import { useLogoutMutation } from '@/queries/authentication/useLogoutMutation';
import { theme } from '../../style/theme';
import * as S from './Header.style';

const Header = ({ headerRef }: { headerRef: React.RefObject<HTMLDivElement> }) => {
const { isLogin } = useAuth();

useCheckLoginState();

return (
Expand All @@ -19,8 +23,12 @@ const Header = ({ headerRef }: { headerRef: React.RefObject<HTMLDivElement> }) =
</Flex>

<Flex align='center' gap='2rem'>
<NewTemplateButton />
<UserMenuButton />
<Link to={'/templates/upload'}>
<Button variant='outlined' size='medium' weight='bold' hoverStyle='none'>
<img src={newTemplateIcon} alt='' />새 템플릿
</Button>
</Link>
{isLogin ? <UserMenuButton /> : <LoginButton />}
</Flex>
</S.HeaderContentContainer>
</S.HeaderContainer>
Expand All @@ -46,21 +54,26 @@ const NavOption = ({ route, name }: { route: string; name: string }) => (
</Link>
);

const NewTemplateButton = () => (
<Link to={'/templates/upload'}>
<S.NewTemplateButton>
<img src={newTemplateIcon} alt='' />
<Text.Small weight='bold' color={theme.color.light.primary_800}>
새 템플릿
</Text.Small>
</S.NewTemplateButton>
</Link>
);
const UserMenuButton = () => {
const { mutateAsync } = useLogoutMutation();

const UserMenuButton = () => (
<S.UserMenuButton>
<img src={userMenuIcon} alt='사용자 메뉴' />
</S.UserMenuButton>
const handleLogoutButton = async () => {
await mutateAsync();
};

return (
<S.UserMenuButton onClick={handleLogoutButton}>
<img src={userMenuIcon} alt='사용자 메뉴' />
</S.UserMenuButton>
);
};

const LoginButton = () => (
<Link to='/login'>
<Button variant='text' size='medium' weight='bold' hoverStyle='none'>
로그인
</Button>
</Link>
);

export default Header;
18 changes: 18 additions & 0 deletions frontend/src/contexts/authContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createContext, useState, ReactNode } from 'react';

interface AuthContextProps {
isLogin: boolean;
handleLoginState: (state: boolean) => void;
}

export const AuthContext = createContext<AuthContextProps | undefined>(undefined);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
const [isLogin, setIsLogin] = useState(false);

const handleLoginState = (state: boolean) => {
setIsLogin(state);
};

return <AuthContext.Provider value={{ isLogin, handleLoginState }}>{children}</AuthContext.Provider>;
};
13 changes: 13 additions & 0 deletions frontend/src/hooks/authentication/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from 'react';

import { AuthContext } from '@/contexts/authContext';

export const useAuth = () => {
const context = useContext(AuthContext);

if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}

return context;
};
11 changes: 9 additions & 2 deletions frontend/src/hooks/authentication/useCheckLoginState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';

import { useLoginStateQuery } from '@/queries/authentication/useLoginStateQuery';
import { useAuth } from './useAuth';

export const useCheckLoginState = () => {
const { error, isError } = useLoginStateQuery();
const { error, isError, isSuccess } = useLoginStateQuery();
const navigate = useNavigate();
const { handleLoginState } = useAuth();

const handleLoginNavigate = useCallback(() => {
navigate('/login');
Expand All @@ -15,6 +17,11 @@ export const useCheckLoginState = () => {
if (isError) {
alert(error.message);
handleLoginNavigate();
handleLoginState(false);
}
}, [error, isError, handleLoginNavigate]);

if (isSuccess) {
handleLoginState(true);
}
}, [error, isError, isSuccess, handleLoginNavigate, handleLoginState]);
};
15 changes: 9 additions & 6 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RouterProvider } from 'react-router-dom';

import { HeaderProvider } from './context/HeaderContext';
import { ToastProvider } from './context/ToastContext';
import { AuthProvider } from './contexts/authContext';
import router from './routes/router';
import GlobalStyles from './style/GlobalStyles';
import { theme } from './style/theme';
Expand Down Expand Up @@ -44,12 +45,14 @@ enableMocking().then(() => {
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<ToastProvider>
<HeaderProvider>
<GlobalStyles />
<RouterProvider router={router} />
</HeaderProvider>
</ToastProvider>
<AuthProvider>
<ToastProvider>
<HeaderProvider>
<GlobalStyles />
<RouterProvider router={router} />
</HeaderProvider>
</ToastProvider>
</AuthProvider>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>,
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CHECK_USERNAME_API_URL,
LOGIN_API_URL,
LOGIN_STATE_API_URL,
LOGOUT_API_URL,
SIGNUP_API_URL,
} from '@/api';
import mockCategoryList from './categoryList.json';
Expand Down Expand Up @@ -62,6 +63,14 @@ const authenticationHandler = [
status: 200,
}),
),
http.post(
`${LOGOUT_API_URL}`,
() =>
new HttpResponse(null, {
status: 204,
statusText: 'AUTHORIZED',
}),
),
http.get(
`${CHECK_EMAIL_API_URL}`,
() =>
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/queries/authentication/useLogoutMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useMutation } from '@tanstack/react-query';

import { postLogout } from '@/api/authentication';
import { useAuth } from '@/hooks/authentication/useAuth';

export const useLogoutMutation = () => {
const { handleLoginState } = useAuth();

return useMutation({
mutationFn: () => postLogout(),
onSuccess: () => {
handleLoginState(false);
},
onError: () => {
console.log('mutation');
},
});
};

0 comments on commit 0ce4fe8

Please sign in to comment.