-
Notifications
You must be signed in to change notification settings - Fork 6
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: 로그인 기능 구현 #203
The head ref may contain hidden characters: "193-feat-\uCE74\uCE74\uC624-\uB85C\uADF8\uC778-\uAE30\uB2A5-\uAD6C\uD604"
feat: 로그인 기능 구현 #203
Changes from 16 commits
c0eb17e
c259975
cc8fc40
c4190d9
97445a3
3e34ca9
38b01c3
c387492
59187a6
eee7e13
941cc82
46ebf24
929cc2e
5f60d9e
9213391
e6ee4c4
f1a6b2d
b704ab9
1b7a93d
286583d
4746b1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,18 @@ | ||
import MainPage from './pages/MainPage'; | ||
import { BrowserRouter, Routes, Route } from 'react-router-dom'; | ||
import OauthRedirectPage from '~/pages/OauthRedirectPage'; | ||
import MainPage from '~/pages/MainPage'; | ||
|
||
function App() { | ||
return <MainPage />; | ||
return ( | ||
<BrowserRouter> | ||
<Routes> | ||
<Route path="/" element={<MainPage />} /> | ||
<Route path="/oauth/redirect/kakao" element={<OauthRedirectPage type="kakao" />} /> | ||
<Route path="/oauth/redirect/naver" element={<OauthRedirectPage type="naver" />} /> | ||
<Route path="/oauth/redirect/google" element={<OauthRedirectPage type="google" />} /> | ||
</Routes> | ||
</BrowserRouter> | ||
); | ||
} | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,50 @@ | ||
import React from 'react'; | ||
import { styled } from 'styled-components'; | ||
import Logo from '~/assets/logo.png'; | ||
import { Modal, ModalContent } from '~/components/@common/Modal'; | ||
import InfoDropDown from '~/components/InfoDropDown'; | ||
import LoginModalContent from '~/components/LoginModalContent'; | ||
import useBooleanState from '~/hooks/useBooleanState'; | ||
|
||
const options = [ | ||
{ id: 1, value: '로그인' }, | ||
{ id: 2, value: '회원가입' }, | ||
]; | ||
|
||
function Header() { | ||
const { value: isModalOpen, setTrue: openModal, setFalse: closeModal } = useBooleanState(false); | ||
|
||
const handleInfoDropDown = (event: React.MouseEvent<HTMLElement>) => { | ||
const currentOption = event.currentTarget.dataset.name; | ||
|
||
if (currentOption === '로그인') openModal(); | ||
}; | ||
|
||
return ( | ||
<StyledHeader> | ||
<StyledLogo alt="셀럽잇 로고" src={Logo} /> | ||
</StyledHeader> | ||
<> | ||
<StyledHeader> | ||
<StyledLogo alt="셀럽잇 로고" src={Logo} /> | ||
<InfoDropDown options={options} externalOnClick={handleInfoDropDown} isOpen={isModalOpen} /> | ||
</StyledHeader> | ||
<Modal> | ||
<ModalContent isShow={isModalOpen} title="로그인 및 회원 가입" closeModal={closeModal}> | ||
<LoginModalContent /> | ||
</ModalContent> | ||
</Modal> | ||
</> | ||
); | ||
} | ||
|
||
export default Header; | ||
|
||
const StyledHeader = styled.header` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
|
||
position: sticky; | ||
top: 0; | ||
z-index: 10; | ||
z-index: 20; | ||
Comment on lines
-20
to
+47
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. z-index를 올려줘야만 하는 일이 있었나요? 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. 넹 ㅋㅋ Navbar의 sticky랑 Header의 sticky가 중복되서 로그인 dropdown이 가려지는 현상이 발생하더라구요 |
||
|
||
width: 100%; | ||
height: 80px; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import InfoButton from './InfoButton'; | ||
|
||
const meta: Meta<typeof InfoButton> = { | ||
title: 'InfoButton', | ||
component: InfoButton, | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof InfoButton>; | ||
|
||
export const Default: Story = { | ||
args: {}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import styled, { css } from 'styled-components'; | ||
|
||
import Menu from '~/assets/icons/etc/menu.svg'; | ||
import User from '~/assets/icons/etc/user.svg'; | ||
|
||
interface InfoButtonProps { | ||
isShow?: boolean; | ||
} | ||
|
||
function InfoButton({ isShow = false }: InfoButtonProps) { | ||
return ( | ||
<StyledInfoButton isShow={isShow}> | ||
<Menu /> | ||
<User /> | ||
</StyledInfoButton> | ||
Comment on lines
+12
to
+15
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. 우선 각각의 아이콘이 한 버튼으로 동작하게 해 놓은 걸까요? 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. 아뇨 디자인 시안에서 보면 아이콘에 관계없이 아이콘을 감싸는 버튼을 클릭 시 dropdown이 동작하게 되어있습니다. |
||
); | ||
} | ||
|
||
export default InfoButton; | ||
|
||
const StyledInfoButton = styled.button<InfoButtonProps>` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
|
||
width: 77px; | ||
|
||
padding: 0.5rem 0.5rem 0.5rem 1.2rem; | ||
|
||
border: 1px solid #ddd; | ||
border-radius: 21px; | ||
background: transparent; | ||
|
||
cursor: pointer; | ||
|
||
${({ isShow }) => | ||
isShow && | ||
css` | ||
box-shadow: 0 1px 2px rgb(0 0 0 / 15%); | ||
`} | ||
|
||
&:hover { | ||
box-shadow: 0 1px 2px rgb(0 0 0 / 15%); | ||
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. common에 등록되어있는 shaow 속성을 사용하면 좋겠어요! |
||
|
||
transition: box-shadow 0.2s ease-in-out; | ||
} | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import InfoButton from '~/components/@common/InfoButton/InfoButton'; | ||
|
||
export default InfoButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { BrowserRouter, Route, Routes } from 'react-router-dom'; | ||
import LoginButton from './LoginButton'; | ||
|
||
const meta: Meta<typeof LoginButton> = { | ||
title: 'Oauth/LoginButton', | ||
component: LoginButton, | ||
decorators: [ | ||
Story => ( | ||
<BrowserRouter> | ||
<Routes> | ||
<Route path="/*" element={<Story />} /> | ||
</Routes> | ||
</BrowserRouter> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof LoginButton>; | ||
|
||
export const Google: Story = { | ||
args: { type: 'google' }, | ||
}; | ||
|
||
export const KaKao: Story = { | ||
args: { type: 'kakao' }, | ||
}; | ||
|
||
export const Naver: Story = { | ||
args: { type: 'naver' }, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { Link } from 'react-router-dom'; | ||
import styled, { css } from 'styled-components'; | ||
import React from 'react'; | ||
import { OAUTH_BUTTON_MESSAGE, OAUTH_LINK } from '~/constants/api'; | ||
|
||
import KaKao from '~/assets/icons/oauth/kakao.svg'; | ||
import Naver from '~/assets/icons/oauth/naver.svg'; | ||
import Google from '~/assets/icons/oauth/google.svg'; | ||
|
||
interface LoginButtonProps { | ||
type: 'google' | 'kakao' | 'naver'; | ||
} | ||
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. 다음과 같은 타입은 oauth.types.ts 로 빼줘도 좋을 것 같네요~! 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. 좋아요!! type Domain = 'google' | 'kakao' | 'naver'; 으로 분리하면 좋을 거 같네요 |
||
|
||
const LoginIcon: Record<string, React.ReactNode> = { | ||
naver: <Naver />, | ||
kakao: <KaKao />, | ||
google: <Google />, | ||
}; | ||
|
||
function LoginButton({ type }: LoginButtonProps) { | ||
return ( | ||
<StyledLoginButtonWrapper type={type} to={OAUTH_LINK[type]} target="_blank"> | ||
<div>{LoginIcon[type]}</div> | ||
<StyledLoginButtonText>{OAUTH_BUTTON_MESSAGE[type]}</StyledLoginButtonText> | ||
</StyledLoginButtonWrapper> | ||
); | ||
} | ||
|
||
export default LoginButton; | ||
|
||
const StyledLoginButtonWrapper = styled(Link)<LoginButtonProps>` | ||
display: flex; | ||
|
||
width: 100%; | ||
height: fit-content; | ||
|
||
padding: 2.3rem 1.3rem; | ||
|
||
border-radius: 12px; | ||
|
||
font-size: 1.4rem; | ||
font-weight: 600; | ||
text-decoration: none; | ||
|
||
${({ type }) => | ||
type === 'naver' && | ||
css` | ||
background: #03c759; | ||
|
||
color: #fff; | ||
`} | ||
|
||
${({ type }) => | ||
type === 'kakao' && | ||
css` | ||
background: #fee500; | ||
`} | ||
|
||
${({ type }) => | ||
type === 'google' && | ||
css` | ||
border: 1px solid var(--gray-3); | ||
`} | ||
|
||
cursor: pointer; | ||
transition: box-shadow 0.2s cubic-bezier(0.2, 0, 0, 1), transform 0.1s cubic-bezier(0.2, 0, 0, 1); | ||
`; | ||
|
||
const StyledLoginButtonText = styled.span` | ||
margin: 0 auto; | ||
|
||
color: inherit; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import LoginButton from '~/components/@common/LoginButton/LoginButton'; | ||
|
||
export default LoginButton; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { createPortal } from 'react-dom'; | ||
|
||
interface ModalProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
function Modal({ children }: ModalProps) { | ||
return createPortal(children, document.querySelector('#modal')); | ||
} | ||
|
||
export default Modal; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import ModalContent from './ModalContent'; | ||
|
||
const meta: Meta<typeof ModalContent> = { | ||
title: 'ModalContent', | ||
component: ModalContent, | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof ModalContent>; | ||
|
||
export const LoginModal: Story = { | ||
args: { | ||
isShow: true, | ||
title: '로그인 또는 회원가입', | ||
closeModal: () => {}, | ||
children: '모달 내용', | ||
}, | ||
}; |
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.
👍