-
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 all 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 |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import type { RestaurantData } from './api.types'; | ||
|
||
type RestaurantImages = RestaurantData['images']; | ||
|
||
export type RestaurantImage = RestaurantImages[number]; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type Oauth = 'google' | 'kakao' | 'naver'; |
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,28 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import ImageCarousel from './ImageCarousel'; | ||
|
||
const meta: Meta<typeof ImageCarousel> = { | ||
title: 'ImageCarousel', | ||
component: ImageCarousel, | ||
}; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof ImageCarousel>; | ||
|
||
export const Default: Story = { | ||
args: { | ||
images: [ | ||
{ id: 1, name: 'https://picsum.photos/315/300', author: '@d0dam', sns: 'youtube' }, | ||
{ id: 2, name: 'https://picsum.photos/315/300', author: '@d0dam', sns: 'youtube' }, | ||
{ id: 3, name: 'https://picsum.photos/315/300', author: '@d0dam', sns: 'youtube' }, | ||
{ id: 4, name: 'https://picsum.photos/315/300', author: '@d0dam', sns: 'youtube' }, | ||
], | ||
}, | ||
}; | ||
|
||
export const OneImage: Story = { | ||
args: { | ||
images: [{ id: 1, name: 'https://picsum.photos/315/300', author: '@d0dam', sns: 'youtube' }], | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { useState } from 'react'; | ||
import styled, { css } from 'styled-components'; | ||
import { RestaurantImage } from '~/@types/image.type'; | ||
import LeftBracket from '~/assets/icons/left-bracket.svg'; | ||
import RightBracket from '~/assets/icons/right-bracket.svg'; | ||
import { BORDER_RADIUS } from '~/styles/common'; | ||
import WaterMarkImage from '../WaterMarkImage'; | ||
|
||
interface ImageCarouselProps { | ||
images: RestaurantImage[]; | ||
type: 'list' | 'map'; | ||
} | ||
|
||
function ImageCarousel({ images, type }: ImageCarouselProps) { | ||
const [currentIndex, setCurrentIndex] = useState<number>(0); | ||
|
||
const goToPrevious = () => { | ||
setCurrentIndex(prevIndex => prevIndex - 1); | ||
}; | ||
|
||
const goToNext = () => { | ||
setCurrentIndex(prevIndex => prevIndex + 1); | ||
}; | ||
|
||
return ( | ||
<StyledCarouselContainer type={type}> | ||
<StyledCarouselSlide currentIndex={currentIndex}> | ||
{images.map(({ id, name, author }) => ( | ||
<WaterMarkImage key={id} imageUrl={name} waterMark={author} /> | ||
))} | ||
</StyledCarouselSlide> | ||
{currentIndex !== 0 && ( | ||
<StyledLeftButton type="button" onClick={goToPrevious}> | ||
<LeftBracket width={10} height={10} /> | ||
</StyledLeftButton> | ||
)} | ||
{currentIndex !== images.length - 1 && ( | ||
<StyledRightButton type="button" onClick={goToNext}> | ||
<RightBracket width={10} height={10} /> | ||
</StyledRightButton> | ||
)} | ||
{images.length > 1 && ( | ||
<StyledDots currentIndex={currentIndex}> | ||
{Array.from({ length: images.length }, () => ( | ||
<StyledDot /> | ||
))} | ||
</StyledDots> | ||
)} | ||
</StyledCarouselContainer> | ||
); | ||
} | ||
|
||
export default ImageCarousel; | ||
|
||
const StyledCarouselContainer = styled.div<{ type: 'list' | 'map' }>` | ||
position: relative; | ||
width: 100%; | ||
overflow: hidden; | ||
border-radius: ${({ type }) => | ||
type === 'list' ? `${BORDER_RADIUS.md}` : `${BORDER_RADIUS.md} ${BORDER_RADIUS.md} 0 0`}; | ||
button { | ||
visibility: hidden; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
position: absolute; | ||
top: 50%; | ||
width: 32px; | ||
height: 32px; | ||
border: none; | ||
border-radius: 50%; | ||
background-color: var(--white); | ||
cursor: pointer; | ||
opacity: 0; | ||
transition: transform 0.15s ease-in-out, opacity 0.2s ease-in-out; | ||
transform: translateY(-50%); | ||
box-shadow: var(--shadow); | ||
outline: none; | ||
&:hover { | ||
transform: translateY(-50%) scale(1.04); | ||
} | ||
} | ||
&:hover { | ||
button { | ||
visibility: visible; | ||
opacity: 0.85; | ||
&:hover { | ||
opacity: 1; | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const StyledLeftButton = styled.button` | ||
left: 12px; | ||
`; | ||
|
||
const StyledRightButton = styled.button` | ||
right: 12px; | ||
`; | ||
|
||
const StyledCarouselSlide = styled.div<{ currentIndex: number }>` | ||
display: flex; | ||
width: 100%; | ||
transition: transform 0.3s ease-in-out; | ||
transform: ${({ currentIndex }) => `translateX(-${currentIndex * 100}%)`}; | ||
flex-wrap: nowrap; | ||
aspect-ratio: 1.05 / 1; | ||
`; | ||
|
||
const StyledDots = styled.div<{ currentIndex: number }>` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
gap: 0 0.5rem; | ||
position: absolute; | ||
bottom: 12px; | ||
width: 100%; | ||
${({ currentIndex }) => css` | ||
& > span:nth-child(${currentIndex + 1}) { | ||
opacity: 1; | ||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out; | ||
transform: scale(1.1); | ||
} | ||
`} | ||
`; | ||
|
||
const StyledDot = styled.span` | ||
width: 6px; | ||
height: 6px; | ||
border-radius: 50%; | ||
background-color: var(--white); | ||
opacity: 0.2; | ||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import ImageCarousel from './ImageCarousel'; | ||
|
||
export default ImageCarousel; |
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: {}, | ||
}; |
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.
👍