-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
merge: [FE] 프론트엔드 배포 (#41)
- Loading branch information
Showing
12 changed files
with
281 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,3 +22,6 @@ dist-ssr | |
*.njsproj | ||
*.sln | ||
*.sw? | ||
|
||
.env.production | ||
.env.development |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ReactNode } from 'react'; | ||
|
||
type ButtonProps = { | ||
children: ReactNode; | ||
onClick: () => void; | ||
fontSize: string; | ||
}; | ||
|
||
export default function Button({ children, onClick, fontSize }: ButtonProps) { | ||
return ( | ||
<button | ||
type="button" | ||
className="px-[1.6vw] py-[14px] bg-mainColor text-white rounded-[15px] font-Pretendard" | ||
onClick={onClick} | ||
style={{ fontSize }} | ||
> | ||
{children} | ||
</button> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { useState } from 'react'; | ||
|
||
export default function useInput(initialValue: string) { | ||
const [inputValue, setInputValue] = useState(initialValue); | ||
|
||
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
const { | ||
target: { value }, | ||
} = event; | ||
setInputValue(value); | ||
}; | ||
|
||
return { inputValue, onChange }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { useState, useEffect, useRef } from 'react'; | ||
import { io } from 'socket.io-client/debug'; | ||
import { SOCKET_EMIT_EVENT, SOCKET_RECEIVE_EVENT } from '@/constants/socketEvents'; | ||
|
||
const useRoom = (roomId: string) => { | ||
const [streamList, setStreamList] = useState<{ id: string; stream: MediaStream }[]>([]); | ||
const videoRef = useRef<HTMLVideoElement>(null); | ||
const localStream = useRef<MediaStream | null>(null); | ||
|
||
const RTCConnections: Record<string, RTCPeerConnection> = {}; | ||
|
||
const socket = io(`${import.meta.env.VITE_SOCKET_URL}`); | ||
|
||
useEffect(() => { | ||
navigator.mediaDevices | ||
.getUserMedia({ | ||
video: true, | ||
}) | ||
.then((video) => { | ||
if (videoRef.current) videoRef.current.srcObject = video; | ||
|
||
localStream.current = video; | ||
|
||
socket.connect(); | ||
socket.emit(SOCKET_EMIT_EVENT.JOIN_ROOM, { | ||
room: roomId, | ||
}); | ||
}); | ||
}, []); | ||
|
||
const createPeerConnection = (socketId: string): RTCPeerConnection => { | ||
const RTCConnection = new RTCPeerConnection({ | ||
iceServers: [{ urls: 'stun:stun.1.google.com:19302' }], | ||
}); | ||
|
||
if (localStream.current) { | ||
localStream.current.getTracks().forEach((track) => { | ||
RTCConnection.addTrack(track, localStream.current as MediaStream); | ||
}); | ||
} | ||
|
||
RTCConnection.addEventListener('icecandidate', (e) => { | ||
if (e.candidate != null) | ||
socket.emit('candidate', { | ||
candidate: e.candidate, | ||
candidateSendId: socket.id, | ||
candidateReceiveId: socketId, | ||
}); | ||
}); | ||
|
||
RTCConnection.addEventListener('track', (e) => { | ||
setStreamList((prev) => [...prev, { id: socketId, stream: e.streams[0] }]); | ||
}); | ||
|
||
return RTCConnection; | ||
}; | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.ALL_USERS, async (data: { users: Array<{ id: string }> }) => { | ||
data.users.forEach((user) => { | ||
RTCConnections[user.id] = createPeerConnection(user.id); | ||
}); | ||
|
||
Object.entries(RTCConnections).forEach(async ([key, value]) => { | ||
const offer = await value.createOffer({ | ||
offerToReceiveAudio: true, | ||
offerToReceiveVideo: true, | ||
}); | ||
|
||
await value.setLocalDescription(new RTCSessionDescription(offer)); | ||
|
||
socket.emit(SOCKET_EMIT_EVENT.OFFER, { | ||
sdp: offer, | ||
offerSendId: socket.id, | ||
offerReceiveId: key, | ||
}); | ||
}); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.OFFER, async (data: { sdp: RTCSessionDescription; offerSendId: string }) => { | ||
RTCConnections[data.offerSendId] = createPeerConnection(data.offerSendId); | ||
|
||
await RTCConnections[data.offerSendId].setRemoteDescription(new RTCSessionDescription(data.sdp)); | ||
|
||
const answer = await RTCConnections[data.offerSendId].createAnswer({ | ||
offerToReceiveAudio: true, | ||
offerToReceiveVideo: true, | ||
}); | ||
|
||
await RTCConnections[data.offerSendId].setLocalDescription(new RTCSessionDescription(answer)); | ||
|
||
socket.emit(SOCKET_EMIT_EVENT.ANSWER, { | ||
sdp: answer, | ||
answerSendId: socket.id, | ||
answerReceiveId: data.offerSendId, | ||
}); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.ANSWER, (data: { sdp: RTCSessionDescription; answerSendId: string }) => { | ||
RTCConnections[data.answerSendId].setRemoteDescription(new RTCSessionDescription(data.sdp)); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.CANDIDATE, (data: { candidate: RTCIceCandidateInit; candidateSendId: string }) => { | ||
RTCConnections[data.candidateSendId].addIceCandidate(new RTCIceCandidate(data.candidate)); | ||
}); | ||
|
||
socket.on(SOCKET_RECEIVE_EVENT.USER_EXIT, (data: { id: string }) => { | ||
RTCConnections[data.id].close(); | ||
delete RTCConnections[data.id]; | ||
|
||
setStreamList((prev) => prev.filter((stream) => stream.id !== data.id)); | ||
}); | ||
|
||
return { videoRef, socket, RTCConnections, streamList }; | ||
}; | ||
|
||
export default useRoom; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,59 @@ | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import { useNavigate } from 'react-router-dom'; | ||
import Button from '@/components/Button'; | ||
import useInput from '@/hooks/useInput'; | ||
|
||
export default function Home() { | ||
return <div>Home</div>; | ||
const navigate = useNavigate(); | ||
const { inputValue, onChange } = useInput(''); | ||
|
||
const handleMakeRoomClick = () => { | ||
const roomId = uuidv4(); | ||
navigate(`/${roomId}`); | ||
}; | ||
|
||
const handleJoinRoomClick = () => { | ||
if (inputValue) navigate(`/${inputValue}`); | ||
}; | ||
|
||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (event.key === 'Enter') handleJoinRoomClick(); | ||
}; | ||
|
||
return ( | ||
<div className="h-screen w-screen bg-backgroundColor flex justify-center items-center py-[10vw] "> | ||
<div className="flex flex-col basis-2/4 pc:gap-[130px] mobile:gap-[50px] justify-center items-center "> | ||
<div className="flex flex-col justify-center"> | ||
<div className="text-[8.8vw] font-bold font-Pretendard ">AlgoITNi</div> | ||
<div className="text-[1.4vw] font-Pretendard">AlgoITNi를 통해 동료, 친구와 함께 알고리즘을 학습해봐요!</div> | ||
</div> | ||
<div className="flex items-center justify-center w-[37vw] gap-3"> | ||
<Button onClick={handleMakeRoomClick} fontSize="1.5vw"> | ||
방생성 | ||
</Button> | ||
<input | ||
placeholder="" | ||
className="rounded-2xl font-Pretendard px-[1.6vw] py-[14px] text-[1.5vw] w-3/5" | ||
value={inputValue} | ||
onChange={onChange} | ||
onKeyDown={handleKeyDown} | ||
/> | ||
|
||
<button | ||
type="button" | ||
className="text-[1.5vw] font-bold text-mainColor font-Pretendard" | ||
onClick={handleJoinRoomClick} | ||
style={{ | ||
opacity: inputValue ? '1' : '0.4', | ||
}} | ||
> | ||
참여 | ||
</button> | ||
</div> | ||
</div> | ||
<div className="basis-2/4 max-w-[600px] max-h-[600px] mobile:hidden"> | ||
<img src="/main.png" alt="main" /> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,29 @@ | ||
import * as Home from '@pages/Home'; | ||
import { fireEvent, render, screen } from '@testing-library/react'; | ||
import Home from '@pages/Home'; | ||
|
||
describe('Home 호출 테스트', () => { | ||
it('Home이 제대로 실행된다.', () => { | ||
jest.spyOn(Home, 'default'); | ||
Home.default(); | ||
expect(Home.default).toHaveBeenCalled(); | ||
jest.mock('uuid', () => { | ||
return { v4: jest.fn(() => 'test') }; | ||
}); | ||
|
||
const mockNavigate = jest.fn(); | ||
|
||
jest.mock('react-router-dom', () => ({ | ||
...jest.requireActual('react-router-dom'), | ||
useNavigate: () => mockNavigate, | ||
})); | ||
|
||
describe('Home 테스트', () => { | ||
it('방생성 클릭시, uuid를 url로 갖는 새로운 방을 생성하고, 해당 방 url로 이동한다.', () => { | ||
render(<Home />); | ||
fireEvent.click(screen.getByText('방생성')); | ||
expect(mockNavigate).toHaveBeenCalledWith('/test'); | ||
}); | ||
|
||
it('참가 클릭시, input입력창에 입력한 url을 가진 방으로 이동한다.', () => { | ||
render(<Home />); | ||
const input = screen.getByPlaceholderText(''); | ||
fireEvent.change(input, { target: { value: 'inputValue' } }); | ||
fireEvent.click(screen.getByText('참여')); | ||
expect(mockNavigate).toHaveBeenCalledWith('/inputValue'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,8 @@ | |
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
html, | ||
body { | ||
width: 100%; | ||
height: 100%; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters