Skip to content

Commit

Permalink
feat(TS-29): 제한 시간 기능 추가 (#99)
Browse files Browse the repository at this point in the history
* feat: 제한 시간 기능 추가

- 빈 입력이나 기본 값(35초)을 수정하지 않을 경우 35초로 설정됩니다.
- 10초 미만인 입력 값은 10초로 조정되게 했습니다.
- 제한 시간 안내 문구를 추가했습니다.
- courseRegisterSlice에 제한 시간을 추가했습니다.
- 로그아웃시 courseRegisterSlice 데이터를 초기화하도록 했습니다.

* feat: 카운트다운 추가

- 분:초로 표시됩니다.
- 최대 시간을 1시간으로 설정했습니다.
- 5초 이하일 때에는 빨간색으로 출력 되도록 했습니다.
  • Loading branch information
jeewonMoon authored Jan 11, 2025
1 parent e57d6fa commit 9ba4e1a
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 17 deletions.
127 changes: 120 additions & 7 deletions src/components/CourseRegister/StartButton.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,155 @@
import {useEffect, useState} from 'react';
import styled from 'styled-components';
import {useDispatch} from 'react-redux';
import {deleteAllRegistrations} from '@apis/api/course.ts';
import {setEndCount, setTime} from '@/store/modules/courseRegisteredSlice';
import {FilterWrap} from '@/styles/FilterLayout';
import {useAppSelector} from '@/store/hooks';

interface StartBtnProps {
onClick: () => void;
}

function StartButton({onClick}: StartBtnProps) {
const dispatch = useDispatch();
const time = useAppSelector(state => state.courseRegistered.time);
const [timeout, setTimeout] = useState<number | string>(time);
const [timeLeft, setTimeLeft] = useState(35);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
if (!isRunning || timeLeft <= 0) return;

const countdown = setInterval(() => {
setTimeLeft(prev => prev - 1);
}, 1000);

return () => clearInterval(countdown);
}, [isRunning, timeLeft]);

useEffect(() => {
if (timeLeft === 0) {
setIsRunning(false);
setTimeLeft(time);
dispatch(setEndCount(true));
console.log('제한 시간 초과');
}
}, [timeLeft, dispatch]);

const handleClick = async () => {
if (!confirm('수강신청 연습 시작하시겠습니까?')) return;

//카운트다운 중에 재시작
if (isRunning) {
setTimeLeft(time);
}

setIsRunning(true);
await deleteAllRegistrations();
onClick();
};

const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const timeInput = parseInt(e.target.value);

if (e.target.value) {
setTimeout(timeInput);
setTimeLeft(timeInput);
dispatch(setTime(timeInput));
} else {
setTimeout('');
setTimeLeft(35);
dispatch(setTime(35));
}
};

const formatTime = (time: number) => {
return time.toString().padStart(2, '0');
};

return (
<Container>
<p>시작 버튼을 누르면 수강 신청이 시작됩니다. 다시 연습하고 싶다면 한번 더 버튼을 눌러주세요.</p>
<p>시작 후, 35초가 지나면 모든 과목이 수강여석 없음으로 변경됩니다.</p>
<p>
시작 버튼을 누르면 수강 신청이 시작됩니다. 다시 연습하고 싶다면 한번 더
버튼을 눌러주세요.
</p>
<p>현재 상태를 확인하고 싶다면, 재조회 버튼을 눌러주세요.</p>
<ButtonWrap onClick={handleClick}>시작 / 초기화</ButtonWrap>
<ul>
<p>제한 시간 안내</p>
<li>기본 설정: 35초</li>
<li>최소 시간: 10초 (10초 이하 입력 시 자동으로 10초로 조정)</li>
<li>
최대 시간: 1시간(3600초) (1시간 이상 입력 시 자동으로 1시간으로 조정)
</li>
<li>
설정한 제한 시간이 지나면 모든 과목의 수강여석이 없음으로 변경됩니다.
</li>
<h5>※ 초단위로 입력해주세요!</h5>
</ul>
<TimerWrap $isAlmostDone={timeLeft <= 5}>
<span>{formatTime(Math.floor(timeLeft / 60))}:</span>
<span>{formatTime(timeLeft % 60)}</span>
</TimerWrap>
<InputWrap>
<span>제한 시간</span>
<InputBox type='number' value={timeout} onChange={handleInput} />
<ButtonWrap onClick={handleClick}>시작 / 초기화</ButtonWrap>
</InputWrap>
</Container>
);
}

const Container = styled.div`
font-weight: normal;
> p {
font-size: 1.6rem;
margin-bottom: 1.5rem;
}
> ul {
font-size: 1.6rem;
margin-bottom: 1rem;
> p {
font-weight: normal;
font-size: 1.6rem;
margin-bottom: 15px;
font-weight: bold;
margin-bottom: 1rem;
}
> li {
list-style-type: square;
margin: 0 0 0.7rem 2rem;
}
> h5 {
color: ${props => props.theme.colors.primary};
font-weight: bold;
}
}
`;

const InputWrap = styled(FilterWrap)`
margin: 1.5rem 0;
`;

const InputBox = styled.input`
height: 2.4rem;
border: 1px solid ${props => props.theme.colors.neutral4};
padding-left: 0.5rem;
width: 21.5rem;
`;

const TimerWrap = styled.div<{$isAlmostDone: boolean}>`
${props => props.theme.texts.title};
margin: 1.5rem 0;
font-size: 2.5rem;
letter-spacing: 0.5rem;
color: ${props =>
props.$isAlmostDone ? props.theme.colors.primary : 'inherit'};
`;

const ButtonWrap = styled.button`
${props => props.theme.texts.content};
background-color: ${props => props.theme.colors.primary};
color: ${props => props.theme.colors.white};
width: 8rem;
height: 2.4rem;
margin-bottom: 10px;
margin-left: 1rem;
&:hover {
filter: grayscale(15%);
Expand Down
7 changes: 1 addition & 6 deletions src/components/CourseRegister/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function CourseRegister() {

useEffect(() => {
dispatch(setEndCount(false));
}, []);
}, [dispatch]);

const refreshAll = useCallback(async () => {
const registeredCourses = await getRegisterdList();
Expand Down Expand Up @@ -89,11 +89,6 @@ function CourseRegister() {
setIsRegistrationStarted(true);
setIsFirstSearch(true);
dispatch(setEndCount(false));

setTimeout(() => {
console.log('35초 지남');
dispatch(setEndCount(true));
}, 35000);
};

const handleAction = async (
Expand Down
2 changes: 2 additions & 0 deletions src/components/Header/TopMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {useAppSelector} from '@/store/hooks';
import {clearUserInfo} from '@/store/modules/userSlice';
import logout from '@assets/img/logout.png';
import Clock from './Clock';
import {resetCourseRegistered} from '@/store/modules/courseRegisteredSlice';

function TopMenu() {
const navigate = useNavigate();
Expand All @@ -15,6 +16,7 @@ function TopMenu() {

const handleLogout = () => {
dispatch(clearUserInfo());
dispatch(resetCourseRegistered());
delete baseAPI.defaults.headers.common['Authorization'];
Cookies.remove('accessToken');
navigate('/login');
Expand Down
31 changes: 27 additions & 4 deletions src/store/modules/courseRegisteredSlice.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
import {createSlice} from '@reduxjs/toolkit';

export interface CourseRegistered {
endCount: boolean
endCount: boolean;
time: number;
}

const courseRegistered = createSlice({
name: 'courseRegistered',
initialState: {
endCount: false,
time: 35,
},
reducers: {
setEndCount(state: CourseRegistered, {payload}: {payload: boolean}) {
state.endCount = payload;
},

setTime(state: CourseRegistered, {payload}: {payload: number}) {
if (payload <= 10) {
state.time = 10;
} else if (payload >= 3600) {
state.time = 3600;
} else {
state.time = payload;
}
},
clearCount(state: CourseRegistered) {
state.endCount = false;
},
cleatTime(state: CourseRegistered) {
state.time = 35;
},
resetCourseRegistered(state: CourseRegistered) {
state.endCount = false;
state.time = 35;
},
},
});

export const {setEndCount, clearCount} = courseRegistered.actions;
export const {
setEndCount,
setTime,
clearCount,
cleatTime,
resetCourseRegistered,
} = courseRegistered.actions;

export default courseRegistered.reducer;
export default courseRegistered.reducer;

0 comments on commit 9ba4e1a

Please sign in to comment.