Skip to content

Commit

Permalink
Merge pull request #17 from depromeet/feat/timer-logic
Browse files Browse the repository at this point in the history
타이머 기능 추가
  • Loading branch information
sumi-0011 authored Dec 2, 2023
2 parents d5e1611 + 17661b0 commit 738b3bd
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 46 deletions.
9 changes: 6 additions & 3 deletions src/app/timer/TimerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import { center } from '@/styled-system/patterns';

interface Props {
isActive: boolean;
time: [number, number];
time: [string, string];
category: string;
}
export default function TimerView({ category, time, isActive }: Props) {
return (
<div className={center()}>
<div className={center(innerCss)}>
<div className={css(categoryCss)}>{category}</div>
<div className={css(timerTextCss)}>
<div
className={css(timerTextCss, {
color: isActive ? 'transparent' : '#B0B8C1',
})}
>
<span>{time[0]}</span>
<span>:</span>
<span>{time[1]}</span>
Expand All @@ -39,6 +43,5 @@ const timerTextCss = {
animation: 'gradient 3s ease-in-out infinite',
backgroundSize: '150% 200%!',
'-webkit-background-clip': 'text!',
color: 'transparent',
background: 'linear-gradient(108deg, #FF8C8C -1.04%, #5D8AFF 101.48%)',
};
13 changes: 1 addition & 12 deletions src/app/timer/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import { type PropsWithChildren } from 'react';
import Header from '@/components/Layout/Header';
import { css } from '@/styled-system/css';

export default function Layout({ children }: PropsWithChildren) {
return (
<div className={containerCss}>
<Header title={'미션 타이머'} />
<main className={mainCss}>{children}</main>
</div>
);
return <div className={containerCss}>{children}</div>;
}

const containerCss = css({
background: 'linear-gradient(136deg, #FFF1F2 4.76%, #E9EFFF 89.58%)',
minHeight: '100vh',
});

const mainCss = css({
padding: '24px 16px',
});
84 changes: 71 additions & 13 deletions src/app/timer/page.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,72 @@
'use client';
import TimerView from '@/app/timer/TimerView';
import useStep from '@/app/timer/useStep';
import useTimer from '@/app/timer/useTimer';
import useTimerStatus from '@/app/timer/useTimerStatus';
import Header from '@/components/Layout/Header';
import { css } from '@styled-system/css';

const STEP_LABEL = {
ready: {
title: '준비 되셨나요?',
desc: '타이머를 눌러서 10분의 미션을 완성해 주세요!',
},
} as const;

export default function TimerPage() {
const { step } = useStep();
const { step, stepLabel, onNextStep } = useTimerStatus();
const { formattedTime } = useTimer(step);

const onFinish = () => {
onNextStep('stop');
alert('정말 끝내시겠습니까?');
};

return (
<div>
<h1 className={titleCss}>{STEP_LABEL[step].title}</h1>
<p className={descCss}>{STEP_LABEL[step].desc}</p>
<div
className={css(bgCss, {
background: step === 'stop' ? '#F2F4F6' : 'linear-gradient(136deg, #FFF1F2 4.76%, #E9EFFF 89.58%)',
})}
>
<Header title={'미션 타이머'} />
<div className={css(containerCss)}>
<h1 className={titleCss}>{stepLabel.title}</h1>
<p className={descCss}>{stepLabel.desc}</p>

<TimerView category="카테고리" time={[10, 0]} isActive={true} />
<TimerView category="카테고리" time={formattedTime} isActive={step !== 'stop'} />
<div className={css(buttonContainerCss)}>
{step === 'ready' && (
<button type="button" onClick={() => onNextStep('progress')}>
시작
</button>
)}
{step === 'progress' && (
<>
<button type="button" onClick={() => onNextStep('stop')}>
일시정지
</button>
<button type="button" onClick={onFinish}>
끝내기
</button>
</>
)}
{step === 'stop' && (
<>
<button type="button" onClick={() => onNextStep('progress')}>
다시 시작
</button>
<button type="button" onClick={onFinish}>
끝내기
</button>
</>
)}
</div>
</div>
</div>
);
}

const bgCss = {
minHeight: '100vh',
transition: '1s ease',
};

const containerCss = {
padding: '24px 16px',
};

const font24Css = {
fontSize: '24px',
fontFamily: 'Pretendard',
Expand All @@ -40,3 +84,17 @@ const font14Css = {

const titleCss = css(font24Css, { color: '#333D4B' });
const descCss = css(font14Css, { color: '#6B7684', marginBottom: '84px' });

const buttonContainerCss = {
margin: '28px auto',
display: 'flex',
justifyContent: 'center',
gap: '12px',

'& button': {
backgroundColor: 'white',
borderRadius: '30px',
padding: '16px 24px',
boxSizing: '0px 4px 20px 0px rgba(18, 23, 41, 0.10)',
},
};
12 changes: 0 additions & 12 deletions src/app/timer/useStep.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/app/timer/useTimer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useEffect, useState } from 'react';
import { type StepType } from '@/app/timer/useTimerStatus';

export default function useTimer(status: StepType, initSeconds = 600) {
const [second, setSecond] = useState(initSeconds); // 남은 시간 (단위: 초)

const formattedTime = formatMMSS(second);

useEffect(() => {
let timer: NodeJS.Timeout;

if (second <= 0) {
return;
}

if (status === 'progress') {
timer = setInterval(() => {
setSecond((prev) => prev - 1);
}, 1000);
}
return () => clearInterval(timer);
}, [second, status]);

return { formattedTime };
}

const formatMMSS = (second: number): [string, string] => {
const minutes = Math.floor(second / 60); // 분 계산
const seconds = second % 60; // 초 계산
const formattedMinutes = String(minutes).padStart(2, '0'); // 두 자리로 변환
const formattedSeconds = String(seconds).padStart(2, '0'); // 두 자리로 변환

return [formattedMinutes, formattedSeconds];
};
32 changes: 32 additions & 0 deletions src/app/timer/useTimerStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from 'react';

export type StepType = 'ready' | 'progress' | 'stop';

const TIMER_STATUS = {
ready: {
title: '준비 되셨나요?',
desc: '타이머를 눌러서 10분의 미션을 완성해 주세요!',
},
progress: {
title: '시작!',
desc: '10분 동안 최선을 다해주세요!',
},
stop: {
title: '잠시 멈췄어요',
desc: '준비가 되면 타이머를 다시 시작해주세요!',
},
} as const;

function useTimerStatus() {
const [step, setStep] = useState<StepType>('ready');

const stepLabel = TIMER_STATUS[step];

const onNextStep = (nextStep: StepType) => {
setStep(nextStep);
};

return { step, onNextStep, stepLabel };
}

export default useTimerStatus;
24 changes: 18 additions & 6 deletions src/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,30 @@ interface Props {

function Header({ title }: Props) {
return (
<div className={wrapperCss}>
<button type="button">
<Image src="/assets/icons/left-arrow-icon.svg" alt="Left Arrow Icon" width={20} height={20} />
</button>
<h2 className={headingCss}>{title}</h2>
</div>
<>
<header className={wrapperCss}>
<button type="button">
<Image src="/assets/icons/left-arrow-icon.svg" alt="Left Arrow Icon" width={20} height={20} />
</button>
<h2 className={headingCss}>{title}</h2>
</header>
<div className={css(headerBlankCss)} />
</>
);
}

const headerBlankCss = {
height: '42px;',
width: '100%',
};

const wrapperCss = flex({
padding: '10px 16px',
gap: '6px',
background: 'transparent',
position: 'fixed',
margin: '0 auto',
zIndex: 100,
});

const headingCss = css({
Expand Down

0 comments on commit 738b3bd

Please sign in to comment.