-
Notifications
You must be signed in to change notification settings - Fork 7
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
[SP0] 모집 안내 플로팅 배너 구현 #161
Changes from 6 commits
b4563b9
f123596
c352368
91ce3e7
21deb64
165547d
a583982
562434b
5e745b2
1f923f9
38aa903
079213c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { FC, useState } from 'react'; | ||
import useInterval from '@src/hooks/useInterval'; | ||
import { convertMsIntoDate } from '@src/utils/convertMsIntoDate'; | ||
|
||
interface TimerProps { | ||
targetDate: Date; | ||
prefix?: string; | ||
suffix?: string; | ||
endMessage: string; | ||
} | ||
|
||
type TimeObj = ReturnType<typeof convertMsIntoDate>; | ||
|
||
const Timer: FC<TimerProps> = (props) => { | ||
const [timeDiff, setTimeDiff] = useState<TimeObj | 'prepare' | 'timeEnd'>('prepare'); | ||
|
||
useInterval(() => { | ||
const diff = props.targetDate.getTime() - Date.now(); | ||
if (diff >= 0) { | ||
setTimeDiff(convertMsIntoDate(diff)); | ||
} else { | ||
setTimeDiff('timeEnd'); | ||
} | ||
}, 100); | ||
|
||
if (timeDiff === 'prepare') { | ||
return <> </>; | ||
} | ||
if (timeDiff === 'timeEnd') { | ||
return <>{props.endMessage}</>; | ||
} | ||
|
||
return ( | ||
<> | ||
{props.prefix} | ||
{timeDiff.days}일 {padZero(timeDiff.hours)}:{padZero(timeDiff.minutes)}: | ||
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. timeDiff객체가 이 스트링을 다 뽑아주면 어떨까용!? 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. convertMsIntoDate 가 뽑아준 timeDiff를 갖고 이 스트링을 뽑아주는 함수를 만들어 쓴다면 좋을 것 같아요!! |
||
{padZero(timeDiff.seconds)} | ||
{props.suffix} | ||
</> | ||
Comment on lines
+26
to
+39
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. suggestion; 이렇게 작성하면 timeDiff에 대한 return 값을 더 쉽게 파악할 수 있을 것 같아요! if (timeDiff === 'prepare') {
return <> </>;
}else if (timeDiff === 'timeEnd') {
return <>{props.endMessage}</>;
}else{
return (
<>
{props.prefix}
{timeDiff.days}일 {padZero(timeDiff.hours)}:{padZero(timeDiff.minutes)}:
{padZero(timeDiff.seconds)}
{props.suffix}
</>
)
} |
||
); | ||
}; | ||
|
||
export default Timer; | ||
|
||
const padZero = (num: number) => { | ||
return `${num}`.padStart(2, '0'); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './Timer'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { useEffect, useRef } from 'react'; | ||
|
||
type IntervalId = ReturnType<typeof setInterval>; | ||
|
||
export default function useInterval(callback: () => void, delay: number) { | ||
const intervalId = useRef<IntervalId | null>(null); | ||
|
||
const clear = () => { | ||
if (intervalId.current !== null) { | ||
clearInterval(intervalId.current); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
intervalId.current = setInterval(callback, delay); | ||
return () => { | ||
clear(); | ||
}; | ||
}, [callback, delay]); | ||
|
||
return { | ||
clear, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const convertMsIntoDate = (milliseconds: number) => { | ||
const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24)); | ||
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. 1000 은 1초, 1000 * 60은 1분, 1000 * 60 * 60은 1시간 .. 등등 다 의미가 있는 값들이어서, 상수로 정의해서 써보면 어떨까요!? const SECOND = 1000;
const MINUTE = SECOND * 60;
const HOUR = MINUTE * 60;
const DAY = HOUR * 24; 요런 식으로요!! |
||
const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); | ||
const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60)); | ||
const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); | ||
return { days, hours, minutes, seconds }; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import styled from '@emotion/styled'; | ||
import Link from 'next/link'; | ||
import { useRouter } from 'next/router'; | ||
import Timer from '@src/components/common/Timer'; | ||
import { useIsMobile } from '@src/hooks/useDevice'; | ||
import SoptSymbol from '@src/views/MainPage/assets/sopt-symbol.svg'; | ||
import dayjs from 'dayjs'; | ||
|
||
const TARGET_DATE = dayjs('2023-09-08T15:00:00.000Z').toDate(); | ||
|
||
export function RecruitFloatingBanner() { | ||
const isMobile = useIsMobile(); | ||
const router = useRouter(); | ||
|
||
return ( | ||
<Wrapper> | ||
<Banner isMobile={isMobile} onClick={() => router.push('/recruit')}> | ||
<Countdown> | ||
<Symbol src={SoptSymbol} alt="솝트 심볼" /> | ||
{isMobile ? ( | ||
'33기 YB 지원하기' | ||
) : ( | ||
<Timer | ||
targetDate={TARGET_DATE} | ||
prefix="33기 YB 모집 마감까지 " | ||
endMessage="33기 YB 모집이 마감되었습니다" | ||
/> | ||
)} | ||
</Countdown> | ||
<ApplyButton> | ||
<Link href="/recruit">{isMobile ? '→' : '지원하기→'}</Link> | ||
</ApplyButton> | ||
</Banner> | ||
</Wrapper> | ||
); | ||
} | ||
|
||
const Wrapper = styled.div` | ||
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. comment; 3기부터는 시멘틱 태그를 최대한 사용하면 좋을 것 같아요! |
||
position: fixed; | ||
left: 50%; | ||
bottom: 120px; | ||
transform: translate(-50%, -50%); | ||
z-index: 9999; | ||
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. suggestion; z-index 단위( ex. 10단위로 증가 )도 정하면 좋을 것 같아요! |
||
|
||
@media (max-width: 1299px) { | ||
bottom: 80px; | ||
} | ||
`; | ||
|
||
const Symbol = styled.img` | ||
width: 52px; | ||
height: 26px; | ||
|
||
@media (max-width: 1299px) and (min-width: 766px) { | ||
width: 36px; | ||
height: 18px; | ||
} | ||
|
||
@media (max-width: 766px) { | ||
width: 30px; | ||
height: 15px; | ||
} | ||
`; | ||
|
||
const Banner = styled.div<{ isMobile: boolean }>` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
|
||
width: 753px; | ||
height: 88px; | ||
padding: 31px 40px 31px 48px; | ||
|
||
border-radius: 20px; | ||
border: 1px solid rgba(114, 222, 173, 0.6); | ||
background: rgba(33, 50, 42, 0.7); | ||
|
||
box-shadow: 0px 4px 20px 0px rgba(32, 39, 38, 0.15); | ||
backdrop-filter: blur(50px); | ||
|
||
@media (max-width: 1299px) and (min-width: 766px) { | ||
width: 585px; | ||
padding: 31px 32px; | ||
} | ||
|
||
@media (max-width: 766px) { | ||
width: 312px; | ||
height: 56px; | ||
padding: 20px 24px; | ||
|
||
border-radius: 12px; | ||
} | ||
|
||
cursor: ${({ isMobile }) => (isMobile ? 'pointer' : 'auto')}; | ||
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. 그리고 모바일인지 판단해서 처리하는 건
을 보고 모바일인지 데스크탑인지 판단하는 거라고 저도 처음에 생각했으나, |
||
`; | ||
|
||
const Countdown = styled.div` | ||
display: flex; | ||
gap: 16px; | ||
|
||
color: #fcfcfc; | ||
font-size: 26px; | ||
font-weight: 700; | ||
line-height: 100%; /* 26px */ | ||
letter-spacing: -0.52px; | ||
|
||
@media (max-width: 1299px) and (min-width: 766px) { | ||
font-size: 18px; | ||
letter-spacing: -0.36px; | ||
} | ||
|
||
@media (max-width: 766px) { | ||
gap: 8px; | ||
|
||
font-size: 16px; | ||
letter-spacing: -0.32px; | ||
} | ||
`; | ||
|
||
const ApplyButton = styled.div` | ||
color: #1deda2; | ||
font-size: 26px; | ||
font-weight: 700; | ||
line-height: 100%; /* 26px */ | ||
letter-spacing: -0.52px; | ||
|
||
cursor: pointer; | ||
|
||
@media (max-width: 1299px) and (min-width: 766px) { | ||
font-size: 18px; | ||
letter-spacing: -0.36px; | ||
} | ||
|
||
@media (max-width: 766px) { | ||
font-size: 16px; | ||
letter-spacing: -0.32px; | ||
} | ||
`; |
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.
우왕 FC로 하신 이유는 무엇인가용!?