-
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] 메인 페이지 개선 #168
[SP0] 메인 페이지 개선 #168
Changes from all commits
b4563b9
f123596
c352368
91ce3e7
9e86c10
4bbbb73
4a06fee
087a479
501b4e6
21deb64
165547d
85291a4
a583982
496e89a
38a12ff
4af4061
7adf850
c4ce07f
132a9c2
562434b
5e745b2
1f923f9
76d3e63
c956961
c6bd55f
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)}: | ||
{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)); | ||
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,6 @@ | ||
export { default as imgAppjam } from './appjam.png'; | ||
export { default as imgManagementMediaTeam } from './management-media-team.png'; | ||
export { default as imgSeminar } from './seminar.png'; | ||
export { default as imgSoptTerm } from './sopt-term.png'; | ||
export { default as imgSopkathon } from './sopkathon.png'; | ||
export { default as imgStudyNetworking } from './study-networking.png'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,102 @@ | ||
import Image from 'next/image'; | ||
import { useIsDesktop, useIsMobile, useIsTablet } from '@src/hooks/useDevice'; | ||
import { useTabs } from '@src/hooks/useTabs'; | ||
import appJamImage from '@src/views/MainPage/assets/sopt-activity/appjam.png'; | ||
import managementMediaTeamImage from '@src/views/MainPage/assets/sopt-activity/management-media-team.png'; | ||
import seminarImage from '@src/views/MainPage/assets/sopt-activity/seminar.png'; | ||
import sopkathonImage from '@src/views/MainPage/assets/sopt-activity/sopkathon.png'; | ||
import soptTermImage from '@src/views/MainPage/assets/sopt-activity/sopt-term.png'; | ||
import studyAndNetworkingImage from '@src/views/MainPage/assets/sopt-activity/study-networking.png'; | ||
import { | ||
imgAppjam, | ||
imgManagementMediaTeam, | ||
imgSeminar, | ||
imgSopkathon, | ||
imgSoptTerm, | ||
imgStudyNetworking, | ||
} from '@src/views/MainPage/assets/sopt-activity'; | ||
import cc from 'classcat'; | ||
import styles from './activity-description.module.scss'; | ||
|
||
const activityList = [ | ||
{ | ||
type: '정기세미나', | ||
imgSrc: seminarImage, | ||
content: | ||
'활동 기간 동안 총 8회의 파트별 세미나를 통해 각자 자신의 파트에서 실력을 다져요. 각 파트장의 강연, 파트원간의 지식 공유, 외부 연사 초청 등 다양한 유형의 세미나가 진행돼요.', | ||
imgSrc: imgSeminar, | ||
content: [ | ||
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. 요렇게 구조 짠 것 너무너무 좋아요! |
||
{ data: '활동 기간 동안 총 ', highlight: false }, | ||
{ data: '8회의 파트별 세미나', highlight: true }, | ||
{ | ||
data: '를 통해 각자 자신의 파트에서 실력을 다져요. 각 파트장의 강연, 파트원간의 지식 공유, 외부 연사 초청 등 다양한 유형의 세미나가 진행돼요.', | ||
highlight: false, | ||
}, | ||
], | ||
}, | ||
{ | ||
type: '솝커톤', | ||
imgSrc: sopkathonImage, | ||
content: | ||
'무박 2일간 기획, 디자인, 개발 파트가 팀을 이뤄 최소 단위의 서비스를 제작하는 SOPT 내 단기 프로젝트예요. 앱잼에 앞서 팀 단위의 협업 과정을 빠르게 경험하며 IT 프로젝트에 대한 협업 감각을 익힐 수 있어요.', | ||
nickName: 'SOPKATHON', | ||
imgSrc: imgSopkathon, | ||
content: [ | ||
{ data: '무박 2일', highlight: true }, | ||
{ | ||
data: '간 기획, 디자인, 개발 파트가 팀을 이뤄 최소 단위의 서비스를 제작하는 SOPT 내 ', | ||
highlight: false, | ||
}, | ||
{ data: '단기 프로젝트', highlight: true }, | ||
{ | ||
data: '예요. 앱잼에 앞서 팀 단위의 협업 과정을 빠르게 경험하며 IT 프로젝트에 대한 협업 감각을 익힐 수 있어요.', | ||
highlight: false, | ||
}, | ||
], | ||
}, | ||
{ | ||
type: '앱잼', | ||
imgSrc: appJamImage, | ||
content: | ||
'3~5주간 기획, 디자인, 개발 파트가 팀을 이뤄 하나의 웹 또는 앱 서비스를 제작하는 SOPT 내 장기 프로젝트예요. IT 창업을 위한 협업 과정을 경험하고, 최종 데모데이에서 각 파트 현직자들과 결과물을 공유하고 피드백을 받아요.', | ||
nickName: 'APPJAM', | ||
imgSrc: imgAppjam, | ||
content: [ | ||
{ data: '3~5주간 ', highlight: true }, | ||
{ | ||
data: '기획, 디자인, 개발 파트가 팀을 이뤄 하나의 웹 또는 앱 서비스를 제작하는 SOPT 내 ', | ||
highlight: false, | ||
}, | ||
{ data: '장기 프로젝트', highlight: true }, | ||
{ data: '예요. IT 창업을 위한 협업 과정을 경험하고, 최종 ', highlight: false }, | ||
{ data: '데모데이', highlight: true }, | ||
{ data: '에서 각 파트 현직자들과 결과물을 공유하고 피드백을 받아요.', highlight: false }, | ||
], | ||
}, | ||
{ | ||
type: '솝텀', | ||
imgSrc: soptTermImage, | ||
content: | ||
'SOPT를 한 기수 이상 수료한 회원끼리 모여 자유로운 주제로 IT 프로젝트를 진행해요. SOPT에서 쌓은 실력을 기반으로 보다 자율적인 프로젝트를 진행할 수 있어요.', | ||
nickName: 'SOPT-TERM', | ||
type: '스터디&네트워킹', | ||
imgSrc: imgStudyNetworking, | ||
content: [ | ||
{ data: '각 파트의 실력을 심도있게 다질 수 있는 ', highlight: false }, | ||
{ data: '스터디', highlight: true }, | ||
{ data: '와 다양한 파트원들과 친목을 쌓을 수 있는 ', highlight: false }, | ||
{ data: '네트워킹', highlight: true }, | ||
{ | ||
data: '이 열려요. 자율적으로 참여하며 SOPT 활동을 더욱 유익하게 만들어 나갈 수 있어요.', | ||
highlight: false, | ||
}, | ||
], | ||
}, | ||
{ | ||
type: '스터디&네트워킹', | ||
imgSrc: studyAndNetworkingImage, | ||
content: | ||
'각 파트의 실력을 심도있게 다질 수 있는 스터디와 다양한 파트원들과 친목을 쌓을 수 있는 네트워킹이 열려요. 자율적으로 참여하며 SOPT 활동을 더욱 유익하게 만들어 나갈 수 있어요.', | ||
type: '솝텀', | ||
imgSrc: imgSoptTerm, | ||
content: [ | ||
{ data: 'SOPT를 ', highlight: false }, | ||
{ data: '한 기수 이상 수료한 회원', highlight: true }, | ||
{ | ||
data: '끼리 모여 자유로운 주제로 IT 프로젝트를 진행해요. SOPT에서 쌓은 실력을 기반으로 보다 ', | ||
highlight: false, | ||
}, | ||
{ data: '자율적인 프로젝트', highlight: true }, | ||
{ data: '를 진행할 수 있어요.', highlight: false }, | ||
], | ||
}, | ||
{ | ||
type: '운영팀&미디어팀', | ||
imgSrc: managementMediaTeamImage, | ||
content: | ||
'운영팀에서는 SOPT 회원들의 원활한 네트워킹을 위한 다양한 행사를 기획해요. 미디어팀에서는 SOPT의 활동과 관련된 콘텐츠를 제작하여 SOPT를 대내외적으로 알려요.', | ||
imgSrc: imgManagementMediaTeam, | ||
content: [ | ||
{ data: '운영팀', highlight: true }, | ||
{ data: '에서는 SOPT 회원들의 원활한 ', highlight: false }, | ||
{ data: '네트워킹', highlight: true }, | ||
{ data: '을 위한 다양한 행사를 기획해요. ', highlight: false }, | ||
{ data: '미디어팀', highlight: true }, | ||
{ data: '에서는 SOPT의 활동과 관련된 ', highlight: false }, | ||
{ data: '콘텐츠를 제작', highlight: true }, | ||
{ data: '하여 SOPT를 대내외적으로 알려요.', highlight: false }, | ||
], | ||
}, | ||
]; | ||
|
||
|
@@ -81,14 +129,16 @@ function MobileActivityDescription() { | |
src={currentTab.imgSrc} | ||
alt="card" | ||
fill | ||
sizes="(max-width: 766px) 328px 585px" | ||
sizes="(max-width: 766px) 309px, 585px" | ||
/> | ||
</div> | ||
<div className={styles.imageDesc}> | ||
<span className={styles.type}>{currentTab.type}</span> | ||
{currentTab.nickName && <span className={styles.nickName}>{currentTab.nickName}</span>} | ||
<div className={styles.cardContent}> | ||
<p> | ||
{currentTab.content.map(({ data, highlight }) => | ||
highlight ? <span>{data}</span> : data, | ||
Comment on lines
+137
to
+138
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; bold 처리된 부분을 highlight에 따라 처리한 것 인상깊네요! |
||
)} | ||
</p> | ||
</div> | ||
<p className={styles.cardContent}>{currentTab.content}</p> | ||
</article> | ||
</div> | ||
); | ||
|
@@ -97,7 +147,7 @@ function MobileActivityDescription() { | |
function DesktopActivityDescription() { | ||
return ( | ||
<div className={styles.content}> | ||
{activityList.map(({ type, imgSrc, nickName, content }) => { | ||
{activityList.map(({ type, imgSrc, content }) => { | ||
return ( | ||
<article className={styles.card} key={type}> | ||
<div className={styles.imageWrapper}> | ||
|
@@ -110,11 +160,14 @@ function DesktopActivityDescription() { | |
540px" | ||
/> | ||
</div> | ||
<div className={cc([styles.imageDesc, nickName && styles.nickNameInclude])}> | ||
<div className={styles.imageDesc}> | ||
<span className={styles.type}>{type}</span> | ||
{nickName && <span className={styles.nickName}>{nickName}</span>} | ||
</div> | ||
<p className={styles.cardContent}>{content}</p> | ||
<div className={styles.cardContent}> | ||
<p> | ||
{content.map(({ data, highlight }) => (highlight ? <span>{data}</span> : data))} | ||
</p> | ||
</div> | ||
</article> | ||
); | ||
})} | ||
|
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.
요것 괜찮다면 ./constant.ts 만들어서 그곳에 옮겨도 좋을 것 같아요 ..!!