diff --git a/frontend/src/api/domain/auth.ts b/frontend/src/api/domain/auth.ts index b7b827f62..1018cb928 100644 --- a/frontend/src/api/domain/auth.ts +++ b/frontend/src/api/domain/auth.ts @@ -9,6 +9,8 @@ const authApi = { path: '/login', body: { email, password }, }), + + logout: async () => apiClient.post({ path: '/logout' }), }; export default authApi; diff --git a/frontend/src/components/common/Spinner/index.tsx b/frontend/src/components/common/Spinner/index.tsx index e6a626d51..2808b8af1 100644 --- a/frontend/src/components/common/Spinner/index.tsx +++ b/frontend/src/components/common/Spinner/index.tsx @@ -1,9 +1,12 @@ import type { StyleProps } from './style'; import S from './style'; -export default function Spinner({ width }: StyleProps) { +export default function Spinner({ width, color = 'white' }: StyleProps) { return ( - + diff --git a/frontend/src/components/common/Spinner/style.ts b/frontend/src/components/common/Spinner/style.ts index 291f29767..c36cf7a52 100644 --- a/frontend/src/components/common/Spinner/style.ts +++ b/frontend/src/components/common/Spinner/style.ts @@ -3,6 +3,7 @@ import styled from '@emotion/styled'; export interface StyleProps { width?: number; + color?: 'white' | 'primary'; } const bounceDelay = keyframes` @@ -17,6 +18,8 @@ const bounceDelay = keyframes` const Spinner = styled.div` --design-width: ${({ width }) => (width ? `${width}px` : '100%')}; --design-height: ${({ width }) => (width ? `${width * 0.23}px` : '1.8rem')}; + --design-color: ${({ theme, color }) => + color === 'primary' ? theme.colors.brand.primary : theme.baseColors.grayscale[50]}; display: flex; justify-content: center; @@ -28,7 +31,7 @@ const Bounce = styled.div` width: var(--design-height); aspect-ratio: 1/1; - background-color: ${({ theme }) => theme.baseColors.grayscale[50]}; + background-color: var(--design-color); border-radius: 100%; animation: ${bounceDelay} 1.4s infinite ease-in-out both; diff --git a/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx b/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx index ff85804e1..86eea341d 100644 --- a/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx +++ b/frontend/src/components/dashboard/DashboardSidebar/DashboardSidebar.stories.tsx @@ -34,4 +34,12 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + args: { + options: [ + { text: '우아한테크코스 6기 프론트엔드', isSelected: true, postId: 1 }, + { text: '우아한테크코스 6기 백엔드', isSelected: false, postId: 2 }, + { text: '우아한테크코스 6기 안드로이드', isSelected: false, postId: 3 }, + ], + }, +}; diff --git a/frontend/src/components/dashboard/DashboardSidebar/LogoutButton/index.tsx b/frontend/src/components/dashboard/DashboardSidebar/LogoutButton/index.tsx new file mode 100644 index 000000000..e28613bb2 --- /dev/null +++ b/frontend/src/components/dashboard/DashboardSidebar/LogoutButton/index.tsx @@ -0,0 +1,20 @@ +import useSignOut from '@hooks/useSignOut'; +import Spinner from '@components/common/Spinner'; +import S from './style'; + +export default function LogoutButton() { + const { mutate: logout, isPending } = useSignOut(); + + return ( + logout()}> + {isPending ? ( + + ) : ( + '로그아웃' + )} + + ); +} diff --git a/frontend/src/components/dashboard/DashboardSidebar/LogoutButton/style.ts b/frontend/src/components/dashboard/DashboardSidebar/LogoutButton/style.ts new file mode 100644 index 000000000..9bb6f42c6 --- /dev/null +++ b/frontend/src/components/dashboard/DashboardSidebar/LogoutButton/style.ts @@ -0,0 +1,17 @@ +import styled from '@emotion/styled'; + +const LogoutButton = styled.button` + ${({ theme }) => theme.typography.common.default}; + color: ${({ theme }) => theme.baseColors.grayscale[600]}; + + position: absolute; + bottom: 2.4rem; + left: 50%; + transform: translateX(-50%); +`; + +const S = { + LogoutButton, +}; + +export default S; diff --git a/frontend/src/components/dashboard/DashboardSidebar/index.tsx b/frontend/src/components/dashboard/DashboardSidebar/index.tsx index ca63040bb..0c86a3349 100644 --- a/frontend/src/components/dashboard/DashboardSidebar/index.tsx +++ b/frontend/src/components/dashboard/DashboardSidebar/index.tsx @@ -1,6 +1,7 @@ import Logo from '@assets/images/logo.svg'; import Accordion from '@components/common/Accordion'; import { Link, useParams } from 'react-router-dom'; +import LogoutButton from './LogoutButton'; import S from './style'; interface Option { @@ -37,6 +38,8 @@ export default function DashboardSidebar({ options }: DashboardSidebarProps) { ))} + + ); } diff --git a/frontend/src/components/dashboard/DashboardSidebar/style.ts b/frontend/src/components/dashboard/DashboardSidebar/style.ts index 831e6ca97..01b9605f0 100644 --- a/frontend/src/components/dashboard/DashboardSidebar/style.ts +++ b/frontend/src/components/dashboard/DashboardSidebar/style.ts @@ -1,6 +1,8 @@ import styled from '@emotion/styled'; const Container = styled.div` + position: relative; + width: 20%; min-width: 25rem; max-width: 30rem; diff --git a/frontend/src/hooks/useSignOut/index.ts b/frontend/src/hooks/useSignOut/index.ts new file mode 100644 index 000000000..8ccb37e46 --- /dev/null +++ b/frontend/src/hooks/useSignOut/index.ts @@ -0,0 +1,14 @@ +import authApi from '@api/domain/auth'; +import { useMutation } from '@tanstack/react-query'; +import { useNavigate } from 'react-router-dom'; + +export default function useSignOut() { + const navigate = useNavigate(); + + return useMutation({ + mutationFn: () => authApi.logout(), + onSuccess: () => { + navigate('/sign-in'); + }, + }); +} diff --git a/frontend/src/mocks/handlers/authHandlers.ts b/frontend/src/mocks/handlers/authHandlers.ts index e8f217410..25ebb197d 100644 --- a/frontend/src/mocks/handlers/authHandlers.ts +++ b/frontend/src/mocks/handlers/authHandlers.ts @@ -1,6 +1,7 @@ /* eslint-disable no-promise-executor-return */ import { AUTH } from '@api/endPoint'; import { http } from 'msw'; +import { Success } from './response'; interface LoginFormData { email: string; @@ -32,6 +33,11 @@ const authHandlers = [ }, }); }), + + http.post(`${AUTH}/logout`, async () => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return Success(); + }), ]; export default authHandlers;