Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 50 additions & 32 deletions front-end/src/hooks/useBlockTimer.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,66 @@
import { useEffect, useState, useRef } from 'react';

const useBlockTimer = (
isSelected: boolean,
setIsSelected: React.Dispatch<React.SetStateAction<boolean>>,
blockDuration: number,
delay: number
) => {
const [isBlocked, setIsBlocked] = useState(false);
const [countdown, setCountdown] = useState(blockDuration / 1000); // 초기값 설정
const useBlockTimer = (type: string, blockDuration: number, delay: number) => {
// 로컬에 값이 있으면 사용 없으면 0으로
const storedCountdown = Number(localStorage.getItem(type)) || 0;
const initialCountdown = storedCountdown > 0 ? storedCountdown : 0;

// 값이 존재하는 경우 isBlock을 바로 true처리
const [isBlocked, setIsBlocked] = useState(initialCountdown > 0);
const [countdown, setCountdown] = useState(initialCountdown);

const blockTimerRef = useRef<number | null>(null);
const countdownIntervalRef = useRef<number | null>(null);
const blockTimerRef = useRef<number | null>(null);

// 로컬 스토리지에 값이 존재하는 경우 바로 카운트 다운 시작
useEffect(() => {
if (!isSelected) {
setIsBlocked(false);
setCountdown(blockDuration / 1000); // 초기값으로 리셋
return;
}

setTimeout(() => {
if (initialCountdown > 0) {
setIsBlocked(true);
setCountdown(blockDuration / 1000);

//시간 줄이는 기능
countdownIntervalRef.current = setInterval(() => {
setCountdown((prev) => (prev > 1 ? prev - 1 : 0));
}, 1000);

//끝나고 초기화
blockTimerRef.current = setTimeout(() => {
setIsSelected(false);
setIsBlocked(false);
setCountdown(blockDuration / 1000);
}, blockDuration);
}, delay);
startCountdown(initialCountdown);
}

return () => {
clearTimeout(blockTimerRef.current!);
clearInterval(countdownIntervalRef.current!);
};
}, [isSelected, blockDuration, delay]);
}, [type, blockDuration, delay]);

// 카운트 다운 함수
const startCountdown = (duration: number) => {
setCountdown(duration);
localStorage.setItem(type, duration.toString());

countdownIntervalRef.current = window.setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
clearInterval(countdownIntervalRef.current!);
localStorage.setItem(type, '0');
return 0;
}
const newCountdown = prev - 1;
localStorage.setItem(type, newCountdown.toString());
return newCountdown;
});
}, 1000);

blockTimerRef.current = window.setTimeout(() => {
setIsBlocked(false);
setCountdown(0);
localStorage.setItem(type, '0');
}, duration * 1000);
};

//외부에서 호출할 블록 시작 함수
const startBlock = () => {
if (isBlocked) return;

setTimeout(() => {
setIsBlocked(true);
startCountdown(blockDuration / 1000);
}, delay);
};

return { isBlocked, countdown };
return { isBlocked, countdown, startBlock };
};

export default useBlockTimer;
1 change: 1 addition & 0 deletions front-end/src/pages/student/course/react/StudentReact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const StudentReact = ({ setModalType, openModal }: StudentReactProps) => {
{CARD_List.map((item) => (
<ReactCard
key={item.type}
type={item.type}
Icon={item.icon}
onCardClick={() => handleCardClick(item.type)}
/>
Expand Down
22 changes: 16 additions & 6 deletions front-end/src/pages/student/course/react/components/ReactCard.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import S from './ReactCard.module.css';
import useBlockTimer from '@/hooks/useBlockTimer';
import { Reaction } from '@/core/model';

type ReactCardProps = {
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
onCardClick: () => Promise<boolean>;
type: Reaction;
};

const ReactCard = ({ Icon, onCardClick }: ReactCardProps) => {
const [isSelected, setIsSelected] = useState<boolean>(false);
const { isBlocked, countdown } = useBlockTimer(
isSelected,
setIsSelected,
const ReactCard = ({ type, Icon, onCardClick }: ReactCardProps) => {
const [isSelected, setIsSelected] = useState(false);
const { isBlocked, countdown, startBlock } = useBlockTimer(
`reactions_block_${type}`,
10000,
2000
);

useEffect(() => {
if (isBlocked) {
setIsSelected(true);
} else {
setIsSelected(false);
}
}, [isBlocked]);

const handleButtonClick = async () => {
const success = await onCardClick();
if (success) {
setIsSelected(true);
startBlock();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const StudentRequest = ({ setModalType, openModal }: StudentRequestProps) => {
<div className={S.cardContainer}>
{CARD_CONTENT.map((item) => (
<RequestCard
type={item.type}
key={item.type}
onCardClick={() => handleCardClick(item.type)}
title={item.title}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
import { RequestType } from '@/core/model';
import S from './RequestCard.module.css';
import useBlockTimer from '@/hooks/useBlockTimer';
import { useState } from 'react';
import { useEffect, useState } from 'react';

type RequestCardProps = {
onCardClick: () => Promise<boolean>;
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
title: string;
description: string;
type: RequestType;
};

const RequestCard = ({
onCardClick,
Icon,
title,
description,
type,
}: RequestCardProps) => {
const [isSelected, setIsSelected] = useState<boolean>(false);
const { isBlocked, countdown } = useBlockTimer(
isSelected,
setIsSelected,
const { isBlocked, countdown, startBlock } = useBlockTimer(
`requests_block_${type}`,
60000,
2000
);

useEffect(() => {
if (isBlocked) {
setIsSelected(true);
} else {
setIsSelected(false);
}
}, [isBlocked]);

const handleButtonClick = async () => {
const success = await onCardClick();
if (success) {
setIsSelected(true);
startBlock();
}
};

Expand Down
23 changes: 16 additions & 7 deletions front-end/src/repository/classroomRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ class ClassroomRepository {
}
);
await throwError(response);
const data = response.json();
return data;
}

async checkQuestionByStudent(questionId: string): Promise<void> {
Expand All @@ -52,8 +50,6 @@ class ClassroomRepository {
}
);
await throwError(response);
const data = await response.json();
return data;
}

async sendQuestion(question: string): Promise<Question> {
Expand All @@ -69,7 +65,11 @@ class ClassroomRepository {
});
await throwError(response);
const data = await response.json();
return data.data;
return {
id: data.data.id,
createdAt: data.data.createdAt,
content: data.data.content,
};
}

async sendRequest(request: string): Promise<void> {
Expand Down Expand Up @@ -104,9 +104,18 @@ class ClassroomRepository {
const response = await fetch('/api/students/questions', {
method: 'GET',
});
await throwError(response);

await throwError(response); // 오류 처리

const data = await response.json();
return data.data.questions;
const questions: Question[] = data.data.questions.map(
(question: Question) => ({
id: question.id,
createdAt: question.createdAt,
content: question.content,
})
);
return questions;
}
}

Expand Down