Skip to content

Commit

Permalink
Merge pull request #1049 from woowacourse-teams/FE/test
Browse files Browse the repository at this point in the history
[FE] 🔖 운영 서버 배포
  • Loading branch information
greetings1012 authored Jan 14, 2025
2 parents 2e95ff8 + 9d4214e commit 33ae548
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 93 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/review_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: review_request

on:
pull_request:
branches: [ "FE/dev", "FE/test", "production" ]
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Extract receiver list
id: extract_receiver_list
uses: actions/[email protected]
with:
script: |
const excludeMember = context.payload.pull_request.user.login;
const members = JSON.parse(${{secrets.DISCORD_FE}});
const requester = members.find(entry => entry.github === excludeMember);
let outputString = '';
for (const member of members) {
if ((member.github !== excludeMember)) {
outputString += `<@${member.discord}> `;
}
}
console.log(`::set-output name=names::${outputString.trim()}`);
console.log(`::set-output name=requester::${requester.name}`);
- name: Discord Message
uses: discord-actions/message@v2
with:
webhookUrl: https://discord.com/api/webhooks/1326507349841809520/WD7X1LxAckgK1z2_2qQ1qvSGx3719XwkXocV_oNz8pmVMgJvDO2znJAWmVhSToM_wjBo
message: "👑${{steps.extract_receiver_list.outputs.requester}}👑 PR이 도착했습니다. \n↓↓↓\n\n⚡⚡⚡⚡⚡⚡⚡⚡⚡\n*⚡[${{steps.extract_receiver_list.outputs.requester}}](${{github.event.pull_request.html_url}}) 혼구멍 내러가기~>⚡* \n⚡⚡⚡⚡⚡⚡⚡⚡⚡\n\n _혼내줄 사람들: ${{steps.extract_receiver_list.outputs.names}}_ \n"
11 changes: 10 additions & 1 deletion frontend/src/apis/pairRoom.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Category } from '@/components/PairRoom/ReferenceCard/ReferenceCard.type';

import fetcher from '@/apis/fetcher';
import { Reference } from '@/apis/referenceLink';
import { Todo } from '@/apis/todo';

import { ERROR_MESSAGES } from '@/constants/message';

Expand All @@ -10,8 +14,13 @@ export interface GetPairRoomResponse {
id: number;
driver: string;
navigator: string;
missionUrl: string;
status: PairRoomStatus;
duration: number;
remainingTime: number;
missionUrl: string;
todos: Todo[];
references: Reference[];
categories: Category[];
}

export const getPairRoom = async (accessCode: string): Promise<GetPairRoomResponse> => {
Expand Down
20 changes: 3 additions & 17 deletions frontend/src/apis/timer.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
import fetcher from '@/apis/fetcher';

const API_URL = process.env.REACT_APP_API_URL;
const SOCKET_URL = process.env.REACT_SOCKET_API_URL;

export const getSSEConnection = (accessCode: string) => {
return new EventSource(`${API_URL}/${accessCode}/connect`);
};

export interface GetTimerResponse {
id: number;
duration: number;
remainingTime: number;
}

export const getTimer = async (accessCode: string): Promise<GetTimerResponse> => {
const response = await fetcher.get({
url: `${API_URL}/${accessCode}/timer`,
errorMessage: '',
});

return response.json();
export const getConnection = (accessCode: string) => {
return new WebSocket(`${SOCKET_URL}/ws-connect?accesscode=${accessCode}`);
};

interface UpdateDurationRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export default meta;
type Story = StoryObj<typeof ReferenceCard>;

export const Default: Story = {
render: () => <ReferenceCard accessCode="1234" isOpen={true} toggleIsOpen={() => {}} />,
render: () => (
<ReferenceCard accessCode="1234" isOpen={true} toggleIsOpen={() => {}} references={[]} categories={[]} />
),
};
11 changes: 7 additions & 4 deletions frontend/src/components/PairRoom/ReferenceCard/ReferenceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import CategoryManagementModal from '@/components/PairRoom/CategoryManagementMod
import { PairRoomCard } from '@/components/PairRoom/PairRoomCard';
import Footer from '@/components/PairRoom/ReferenceCard/Footer/Footer';
import Header from '@/components/PairRoom/ReferenceCard/Header/Header';
import { Category } from '@/components/PairRoom/ReferenceCard/ReferenceCard.type';
import ReferenceList from '@/components/PairRoom/ReferenceCard/ReferenceList/ReferenceList';

import { Reference } from '@/apis/referenceLink';

import useModal from '@/hooks/_common/useModal';

import useCategoriesQuery, { DEFAULT_CATEGORY_ID, DEFAULT_CATEGORY_VALUE } from '@/queries/PairRoom/useCategoriesQuery';
import useReferencesQuery from '@/queries/PairRoom/useReferencesQuery';

import { findValueById } from '@/utils/findOption';

Expand All @@ -19,15 +21,16 @@ interface ReferenceCardProps {
accessCode: string;
isOpen: boolean;
toggleIsOpen: () => void;
references: Reference[];
categories: Category[];
}

const ReferenceCard = ({ accessCode, isOpen, toggleIsOpen }: ReferenceCardProps) => {
const ReferenceCard = ({ accessCode, isOpen, toggleIsOpen, references, categories }: ReferenceCardProps) => {
const [selectedCategoryId, setSelectedCategoryId] = useState(DEFAULT_CATEGORY_ID);

const { isModalOpen, openModal, closeModal } = useModal();

const { categories, isCategoryExist } = useCategoriesQuery(accessCode);
const { references } = useReferencesQuery(selectedCategoryId, accessCode);
const { isCategoryExist } = useCategoriesQuery(accessCode);

const selectedCategoryName = findValueById(categories, selectedCategoryId) || DEFAULT_CATEGORY_VALUE;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { useParams } from 'react-router-dom';

import TodoItem from '@/components/PairRoom/TodoListCard/TodoItem/TodoItem';

import { Todo } from '@/apis/todo';

import useDragAndDrop from '@/hooks/PairRoom/useDragAndDrop';

import useTodosMutation from '@/queries/PairRoom/useTodosMutation';
import useTodosQuery from '@/queries/PairRoom/useTodosQuery';

import * as S from './TodoList.styles';

const TodoList = () => {
const { accessCode } = useParams();
interface TodoListProps {
todos: Todo[];
}

const { todos } = useTodosQuery(accessCode || '');
const TodoList = ({ todos }: TodoListProps) => {
const { updateOrderMutation } = useTodosMutation();

const handleUpdateOrder = (todoId: number, order: number) => updateOrderMutation({ todoId, order });
const handleUpdateOrder = (todoId: number, order: number) => {
updateOrderMutation({ todoId, order });
};

const { dragOverItem, handleDragStart, handleDragEnter, handleDrop } = useDragAndDrop(todos, handleUpdateOrder);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { PairRoomCard } from '@/components/PairRoom/PairRoomCard';
import Header from '@/components/PairRoom/TodoListCard/Header/Header';
import TodoList from '@/components/PairRoom/TodoListCard/TodoList/TodoList';

import { Todo } from '@/apis/todo';

import useInput from '@/hooks/_common/useInput';

import useTodosMutation from '@/queries/PairRoom/useTodosMutation';
Expand All @@ -17,9 +19,10 @@ import * as S from './TodoListCard.styles';
interface TodoListCardProps {
isOpen: boolean;
toggleIsOpen: () => void;
todos: Todo[];
}

const TodoListCard = ({ isOpen, toggleIsOpen }: TodoListCardProps) => {
const TodoListCard = ({ isOpen, toggleIsOpen, todos }: TodoListCardProps) => {
const { accessCode } = useParams();

const { value, handleChange, resetValue } = useInput();
Expand All @@ -35,7 +38,7 @@ const TodoListCard = ({ isOpen, toggleIsOpen }: TodoListCardProps) => {
<PairRoomCard>
<Header isOpen={isOpen} toggleIsOpen={toggleIsOpen} />
<S.Body $isOpen={isOpen}>
<TodoList />
<TodoList todos={todos} />
<S.Footer>
<S.Form onSubmit={handleSubmit}>
<Input
Expand Down
117 changes: 68 additions & 49 deletions frontend/src/hooks/PairRoom/useTimer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,32 @@ import { AlarmSound } from '@/assets';

import useToastStore from '@/stores/toastStore';

import { getSSEConnection, startTimer, stopTimer } from '@/apis/timer';
import { getConnection, startTimer, stopTimer } from '@/apis/timer';

import useNotification from '@/hooks/PairRoom/useNotification';

import { QUERY_KEYS } from '@/constants/queryKeys';

const STATUS_SSE_KEY = 'timer';
const TIME_SSE_KEY = 'remaining-time';
const TIMEOUT_LIMIT = 100;
const EVENT_NAMES = {
TIMER: 'timer',
REMAINING_TIME: 'remaining-time',
};

const MESSAGES = {
COMPLETE: 'complete',
START: 'start',
RUNNING: 'running',
PAUSE: 'pause',
UPDATE: 'update',
};

const useTimer = (accessCode: string, defaultTime: number, defaultTimeleft: number, onTimerStop: () => void) => {
const navigate = useNavigate();

const queryClient = useQueryClient();

const alarmAudio = useRef(new Audio(AlarmSound));
const timeoutCount = useRef(0);
// const timeoutCount = useRef(0);

const [timeLeft, setTimeLeft] = useState(defaultTimeleft);
const [isActive, setIsActive] = useState(false);
Expand All @@ -49,72 +58,82 @@ const useTimer = (accessCode: string, defaultTime: number, defaultTimeleft: numb
addToast({ status: 'INFO', message: '드라이버 / 내비게이터 역할을 바꿔 주세요!' });
};

useEffect(() => {
const sse = getSSEConnection(accessCode);
const handleEvent = (eventName: string, eventData: string) => {
switch (eventName) {
case EVENT_NAMES.TIMER:
handleTimerEvent(eventData);
break;
case EVENT_NAMES.REMAINING_TIME:
handleRemainingTimeEvent(eventData);
break;
default:
console.warn(`Unhandled event: ${eventName}`);
}
};

const handleStatus = (event: MessageEvent) => {
if (event.data === 'complete') {
const handleTimerEvent = (eventData: string) => {
switch (eventData) {
case MESSAGES.COMPLETE:
navigate(`/room/${accessCode}/retrospectForm`, { state: { valid: true } });
addToast({ status: 'WARNING', message: '페어룸이 종료되었습니다.' });
return;
}

if (event.data === 'start') {
break;
case MESSAGES.START:
case MESSAGES.RUNNING:
setIsActive(true);
addToast({ status: 'SUCCESS', message: '타이머가 시작되었습니다.' });
return;
}

if (event.data === 'running') {
setIsActive(true);
addToast({ status: 'SUCCESS', message: '타이머가 진행 중입니다.' });
return;
}

if (event.data === 'pause') {
break;
case MESSAGES.PAUSE:
setIsActive(false);
addToast({ status: 'WARNING', message: '타이머가 일시 정지되었습니다.' });
return;
}

if (event.data === 'update') {
break;
case MESSAGES.UPDATE:
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GET_PAIR_ROOM_TIMER] });
addToast({ status: 'WARNING', message: '타이머 시간이 변경되었습니다.' });
return;
}
};
break;
default:
console.warn(`Unhandled timer event data: ${eventData}`);
}
};

const handleRemainingTimeEvent = (eventData: string) => {
if (eventData === '0') {
handleStop();
alarmAudio.current.play();
fireNotification('타이머가 끝났어요!', '드라이버 / 내비게이터 역할을 바꿔 주세요!', {
requireInteraction: true,
});
} else {
setTimeLeft(Number(eventData));
}
};

const handleTimeLeft = (event: MessageEvent) => {
if (event.data === '0') {
handleStop();
alarmAudio.current.play();
fireNotification('타이머가 끝났어요!', '드라이버 / 내비게이터 역할을 바꿔 주세요!', {
requireInteraction: true,
});
} else {
setTimeLeft(event.data);
}
useEffect(() => {
const socket = getConnection(accessCode);

const handleMessage = (event: MessageEvent) => {
console.log('Received event:', event.data);
const parsedData = JSON.parse(event.data);
handleEvent(parsedData.event, parsedData.data);
};

const handleBeforeUnload = (event: BeforeUnloadEvent) => {
event.preventDefault();
};

sse.addEventListener(TIME_SSE_KEY, handleTimeLeft);
sse.addEventListener(STATUS_SSE_KEY, handleStatus);
socket.onopen = () => {
console.log('WebSocket connection opened');
socket.addEventListener('message', handleMessage as EventListener);
};

sse.onerror = () => {
timeoutCount.current += 1;
if (timeoutCount.current >= TIMEOUT_LIMIT) navigate('/error');
socket.onerror = (error) => {
console.error('WebSocket connection error:', error);
};

window.addEventListener('beforeunload', handleBeforeUnload);

return () => {
sse.removeEventListener(STATUS_SSE_KEY, handleStatus);
sse.removeEventListener(TIME_SSE_KEY, handleTimeLeft);
sse.close();

socket.removeEventListener('message', handleMessage as EventListener);
socket.close();
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, []);
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/pages/PairRoom/PairRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const PairRoom = () => {
duration,
remainingTime,
isFetching,
todos,
references,
categories,
} = usePairRoomQuery(accessCode || '');

const { updatePairRoleMutation } = usePairRoomMutation();
Expand Down Expand Up @@ -65,8 +68,14 @@ const PairRoom = () => {
/>
</S.Container>
<S.Container>
<TodoListCard isOpen={!isCardOpen} toggleIsOpen={() => setIsCardOpen(false)} />
<ReferenceCard accessCode={accessCode || ''} isOpen={isCardOpen} toggleIsOpen={() => setIsCardOpen(true)} />
<TodoListCard isOpen={!isCardOpen} toggleIsOpen={() => setIsCardOpen(false)} todos={todos} />
<ReferenceCard
accessCode={accessCode || ''}
isOpen={isCardOpen}
toggleIsOpen={() => setIsCardOpen(true)}
references={references}
categories={categories}
/>
</S.Container>
<GuideModal isOpen={isModalOpen} close={closeModal} accessCode={accessCode || ''} />
</S.Layout>
Expand Down
Loading

0 comments on commit 33ae548

Please sign in to comment.