Skip to content

Feature(#77): 마이 페이지 API 연동 관련 기본 로직 작업 #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
281fdd2
refactor: 철자 수정
Jw705 Dec 12, 2023
44e5e1e
feat: 마이 페이지 API 연동 관련 기본 로직 작업 #77
LellowMellow Dec 12, 2023
b912de5
fix: 발표자 입장 시, 초기 화이트보드 데이터 받고 반영할 수 있도록 수정
platinouss Dec 12, 2023
eeba8f2
fix: redis에 string 타입으로 저장하도록 수정
platinouss Dec 12, 2023
32c67dd
feat: 참여자, 발표자 강의 제목 정상적으로 표시하도록 개선
Jw705 Dec 12, 2023
02dbb5d
Merge pull request #264 from platinouss/fix/231212-whiteboard-init
tmddus2 Dec 12, 2023
b61c349
Merge commit '02dbb5d0736518cf2844ff74122c3700ea8192d4' into feature/…
Jw705 Dec 12, 2023
d8fab4b
feat: 발표자가 강의를 나갔다가 들어오는 경우 이전 화이트보드 데이터를 가져온다 #215
Jw705 Dec 12, 2023
0ce28ac
fix: 새로 방을 개설했을 때만, 강의 시작시간과 화이트보드 초기 데이터 반영
platinouss Dec 12, 2023
fcec906
Merge pull request #265 from platinouss/fix/231212-re-connect-init
tmddus2 Dec 12, 2023
33c9766
Merge commit 'fcec906edc8199a10b3588f26007556dd0f7d00c' into feature/…
Jw705 Dec 12, 2023
de30d45
feat: 강의자 페이지 로그인 한 사용자만 가능하게 변경
Jw705 Dec 12, 2023
650e07f
Merge pull request #266 from Jw705/feature/231212-update-socket
Jw705 Dec 12, 2023
ff2c70b
fix: 참여자 페이지 게스트 토큰 임시 추가
Jw705 Dec 12, 2023
69c1488
Merge pull request #267 from Jw705/feature/231212-auth-lecture
Byeonjin Dec 12, 2023
37fcbbf
feat: 발표자 페이지 새로고침 시 해결되지 않은 질문 리스트 백업 기능 구현 #268
Byeonjin Dec 12, 2023
509a152
feat: 강의자가 새로고침 후 재접속 해도 음성이 잘 들리도록 개선 #215
Jw705 Dec 12, 2023
2ccec62
chore: 사용하지 않는 FE 디버깅 코드 제거
Jw705 Dec 12, 2023
e3a6860
Merge pull request #270 from Jw705/feature/231212-reconnect-audio
platinouss Dec 12, 2023
b3b2c0a
feat: 마이 페이지 API 연동 관련 기본 로직 작업 #77
LellowMellow Dec 12, 2023
a7c2fc6
fix: 메모지 영어, 한글 폰트가 다른 문제 해결, 폰트 크기 제어시 메모지 배경 크기 조절 #272
Byeonjin Dec 12, 2023
7326dd3
refactor: import 순서 그룹핑 #77
LellowMellow Dec 12, 2023
39be6d2
fix: 메모지와 다른 요소를 같이 선택할 경우 메모 편집 패널 표시 오류 #274
Byeonjin Dec 12, 2023
81fed72
feat: 참여자가 socket id 전송하도록 개선
Jw705 Dec 12, 2023
2bd061a
Merge pull request #275 from Jw705/feature/231212-socket-id
Jw705 Dec 12, 2023
dbe6d37
fix: 메모지 입력 시 백스페이스 문제 해결 #276
Byeonjin Dec 12, 2023
b2b4ac9
refact: 사용하지 않는 모듈 제거
Byeonjin Dec 12, 2023
e4427f6
Merge pull request #277 from Byeonjin/fix/231212-momo-bug-fix
Byeonjin Dec 12, 2023
5046423
refactor: 결과값 구조분해할당 적용 #77
LellowMellow Dec 12, 2023
ee5c8f3
Merge branch 'feature/231211-add-api-to-mypage' of https://github.com…
LellowMellow Dec 12, 2023
f58361e
feat: 마이 페이지 API 연동 관련 기본 로직 작업 #77
LellowMellow Dec 12, 2023
f4c006a
refactor: import 순서 그룹핑 #77
LellowMellow Dec 12, 2023
7f03e2a
refactor: 결과값 구조분해할당 적용 #77
LellowMellow Dec 12, 2023
937e3b1
feat: 마이 페이지 API 연동 관련 기본 로직 작업 #77
LellowMellow Dec 12, 2023
f5cbb08
fix: 비밀번호 regexp 수정 #77
LellowMellow Dec 12, 2023
02cbb2f
Merge branch 'feature/231211-add-api-to-mypage' of https://github.com…
LellowMellow Dec 12, 2023
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
1 change: 0 additions & 1 deletion frontend/public/vite.svg

This file was deleted.

13 changes: 8 additions & 5 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,22 @@ interface HeaderProps {
const Header = ({ type }: HeaderProps) => {
const [isProfileClicked, setIsProfileClicked] = useState(false);
const [isSettingClicked, setIsSettingClicked] = useState(false);
const [lectureTitle, setLectureTitle] = useState("강의 제목");
const [lectureCode, setLectureCode] = useState("000000");

return (
<header className="flex w-100 h-20 items-center justify-between px-6 py-4 bg-grayscale-white border-header box-border z-10 sticky top-0">
<div className="flex items-center gap-4 semibold-20">
{(type === "login" || type === "main") && <HeaderLogo type={type === "login" ? "login" : "normal"} />}
{(type === "login" || type === "main") && (
<HeaderLogo type={type === "login" ? "login" : "normal"} lectureTitle={lectureTitle} />
)}
{(type === "instructor" || type === "participant") && (
<>
<HeaderLogo type="lecture" />
<HeaderLogo type="lecture" lectureTitle={lectureTitle} />
<HeaderCodeCopyButton lectureCode={lectureCode} />
</>
)}
{type === "review" && <HeaderLogo type="lecture" />}
{type === "review" && <HeaderLogo type="lecture" lectureTitle={lectureTitle} />}
</div>

<div className="flex items-center gap-4 semibold-20">
Expand All @@ -40,7 +43,7 @@ const Header = ({ type }: HeaderProps) => {
)}
{type === "instructor" && (
<>
<HeaderInstructorControls setLectureCode={setLectureCode} />
<HeaderInstructorControls setLectureCode={setLectureCode} setLectureTitle={setLectureTitle} />
<HeaderSettingButton
isSettingClicked={isSettingClicked}
setIsSettingClicked={setIsSettingClicked}
Expand All @@ -51,7 +54,7 @@ const Header = ({ type }: HeaderProps) => {
)}
{type === "participant" && (
<>
<HeaderParticipantControls setLectureCode={setLectureCode} />
<HeaderParticipantControls setLectureCode={setLectureCode} setLectureTitle={setLectureTitle} />
<HeaderSettingButton
isSettingClicked={isSettingClicked}
setIsSettingClicked={setIsSettingClicked}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useNavigate, useLocation } from "react-router-dom";
import axios from "axios";

import VolumeMeter from "./VolumeMeter";
import useAuth from "@/hooks/useAuth";

import PlayIcon from "@/assets/svgs/play.svg?react";
import StopIcon from "@/assets/svgs/stop.svg?react";
import MicOnIcon from "@/assets/svgs/micOn.svg?react";
Expand All @@ -20,30 +22,33 @@ import calcNormalizedVolume from "@/utils/calcNormalizedVolume";
import selectedMicrophoneState from "@/stores/stateSelectedMicrophone";
import micVolumeGainState from "@/stores/stateMicVolumeGain";
import micVolumeState from "@/stores/stateMicVolume";
import cavasInstanceState from "@/pages/Test/components/stateCanvasInstance";
import canvasInstanceState from "@/pages/Test/components/stateCanvasInstance";
import instructorSocketState from "@//stores/stateInstructorSocketRef";
import questionListState from "@/pages/Test/components/stateQuestionList";

interface HeaderInstructorControlsProps {
setLectureCode: React.Dispatch<React.SetStateAction<string>>;
setLectureTitle: React.Dispatch<React.SetStateAction<string>>;
}

const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsProps) => {
const isLectureStartRef = useRef<boolean>(false);

const HeaderInstructorControls = ({ setLectureCode, setLectureTitle }: HeaderInstructorControlsProps) => {
const [isMicOn, setIsMicOn] = useState(true);
const [isStartModalOpen, setIsStartModalOpen] = useState(false);
const [isCloseModalOpen, setIsCloseModalOpen] = useState(false);
const [elapsedTime, setElapsedTime] = useState<number>(0);

const selectedMicrophone = useRecoilValue(selectedMicrophoneState);
const inputMicVolume = useRecoilValue(micVolumeGainState);
const fabricCanvasRef = useRecoilValue(cavasInstanceState);
const fabricCanvasRef = useRecoilValue(canvasInstanceState);
const setInputMicVolumeState = useSetRecoilState(micVolumeGainState);
const setMicVolumeState = useSetRecoilState(micVolumeState);
const setInstructorSocket = useSetRecoilState(instructorSocketState);
const setQuestionList = useSetRecoilState(questionListState);
const navigate = useNavigate();
const showToast = useToast();
const { checkAuth } = useAuth();

const isLectureStartRef = useRef<boolean>(false);
const timerIdRef = useRef<number | null>(null); // 경과 시간 표시 타이머 id
const onFrameIdRef = useRef<number | null>(null); // 마이크 볼륨 측정 타이머 id
const managerRef = useRef<Manager>();
Expand All @@ -56,8 +61,6 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
const prevInputMicVolumeRef = useRef<number>(0);

const roomid = new URLSearchParams(useLocation().search).get("roomid") || "999999";
const sampleAccessToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InBsYXRpbm91c3NAZ21haWwuY29tIiwiaWF0IjoxNzAxNjY0NTc4LCJleHAiOjE3MDI3MDEzNzh9.e2ikfmTsFCoVNxenHpAh__hLhoJnUPWSf-FmFSPo_RA";
const pc_config = {
iceServers: [
{
Expand All @@ -72,12 +75,12 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
};

useEffect(() => {
checkAuth();
axios
.get(`${import.meta.env.VITE_API_SERVER_URL}/lecture?code=${roomid}`)
.then((result) => {
const lecureTitle = result.data.title;
console.log(lecureTitle);
// 이후 작업 내일 예정
const lectureTitle = result.data.title;
setLectureTitle(lectureTitle);
})
.catch(() => {
// showToast({ message: "존재하지 않는 강의실입니다.", type: "alert" });
Expand All @@ -95,6 +98,8 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
}
}, [selectedMicrophone]);

let startTime = Date.now();

const startLecture = async () => {
if (!selectedMicrophone) {
showToast({ message: "음성 입력장치(마이크)를 먼저 선택해 주세요.", type: "alert" });
Expand Down Expand Up @@ -137,13 +142,14 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
managerRef.current = new Manager(import.meta.env.VITE_MEDIA_SERVER_URL);
socketRef.current = managerRef.current.socket("/create-room", {
auth: {
accessToken: sampleAccessToken,
accessToken: localStorage.getItem("token"),
refreshToken: "sample"
}
});
socketRef.current.on("connect_error", (err) => handleServerError(err));
socketRef.current.on(`serverAnswer`, (data) => handleServerAnswer(data));
socketRef.current.on(`serverCandidate`, (data) => handleServerCandidate(data));
socketRef.current.on(`reconnectPresenter`, (data) => handleReconnect(data));

// 1. 로컬 stream 생성 (발표자 브라우저에서 미디어 track 설정)
if (!selectedMicrophone) throw new Error("마이크를 먼저 선택해 주세요");
Expand Down Expand Up @@ -185,7 +191,9 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
offerToReceiveAudio: false,
offerToReceiveVideo: false
});
saveCanvasData(fabricCanvasRef!, canvasData, startTime);
socketRef.current.emit("presenterOffer", {
whiteboard: canvasData,
socketId: socketRef.current.id,
roomId: roomid,
SDP: SDP
Expand Down Expand Up @@ -245,7 +253,6 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
onFrameIdRef.current = window.requestAnimationFrame(onFrame);
};

let startTime = Date.now();
// 경과 시간을 표시하기 위한 부분입니다
const startTimer = () => {
const updateElapsedTime = () => {
Expand Down Expand Up @@ -291,12 +298,12 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr

let canvasData: ICanvasData = {
canvasJSON: "",
viewport: [1, 0, 0, 1, 0, 0],
viewport: [0, 0, 0, 0, 0, 0],
eventTime: 0,
width: 0,
height: 0
};
let replayFileArray: ICanvasData[] = [];
//let replayFileArray: ICanvasData[] = [];

const submitData = (data: ICanvasData) => {
if (!lectureSocketRef.current) return;
Expand All @@ -305,8 +312,11 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
roomId: roomid,
content: data
});
/*
화이트보드 다시보기 더미 데이터를 만들기 위한 코드입니다.
replayFileArray.push({ ...data });
console.log(replayFileArray);
*/
};

const handleServerAnswer = (data: any) => {
Expand All @@ -321,23 +331,31 @@ const HeaderInstructorControls = ({ setLectureCode }: HeaderInstructorControlsPr
};
const handleConnected = () => {
isLectureStartRef.current = true;
startTime = Date.now();
startTimer();
showToast({ message: "강의가 시작되었습니다.", type: "success" });
if (!managerRef.current) return;
lectureSocketRef.current = managerRef.current.socket("/lecture", {
auth: {
accessToken: sampleAccessToken,
accessToken: localStorage.getItem("token"),
refreshToken: "sample"
}
});
setInstructorSocket(lectureSocketRef.current);
submitData(canvasData);
};
const handleServerError = (err: any) => {
console.error(err.message);
showToast({ message: "서버 연결에 실패했습니다", type: "alert" });
};
const handleReconnect = (data: any) => {
fabricCanvasRef!.loadFromJSON(data.whiteboard.canvasJSON, () => {});
fabricCanvasRef!.setViewportTransform(data.whiteboard.viewport);
startTime = new Date(data.startTime).getTime();
const questionList = data.questions.map((question: any) => {
return { content: question[1][1], questionId: question[0] };
});
setQuestionList(questionList);
showToast({ message: "이전에 진행한 강의 내용을 불러왔습니다.", type: "default" });
};
const handlePopstate = () => {
stopLecture();
navigate("/");
Expand Down
8 changes: 2 additions & 6 deletions frontend/src/components/Header/components/HeaderLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { useNavigate } from "react-router-dom";
import logoSmall from "@/assets/imgs/logoSmall.png";

interface LogoButtonProps {
type: "login" | "normal" | "lecture";
}

const HeaderLogo = ({ type }: LogoButtonProps) => {
const HeaderLogo = ({ type, lectureTitle }: { type: "login" | "normal" | "lecture"; lectureTitle: string }) => {
const navigate = useNavigate();

const handleLogoClicked = () => {
Expand All @@ -19,7 +15,7 @@ const HeaderLogo = ({ type }: LogoButtonProps) => {
<img src={logoSmall} alt="로고" />
{type === "normal" && <h1 className="hidden sm:block">Boarlog</h1>}
</button>
{type === "lecture" && <h1 className="hidden sm:block truncate max-w-[15vw]">React Hooks에 대해 알아보자</h1>}
{type === "lecture" && <h1 className="hidden sm:block truncate max-w-[15vw]">{lectureTitle}</h1>}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { useState, useRef, useEffect } from "react";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { Socket, Manager } from "socket.io-client";
import { useNavigate, useLocation } from "react-router-dom";
import axios from "axios";

import VolumeMeter from "./VolumeMeter";
import useAuth from "@/hooks/useAuth";

import StopIcon from "@/assets/svgs/stop.svg?react";
import MicOnIcon from "@/assets/svgs/micOn.svg?react";
import MicOffIcon from "@/assets/svgs/micOff.svg?react";
Expand All @@ -16,25 +19,26 @@ import { convertMsTohhmm } from "@/utils/convertMsToTimeString";
import calcNormalizedVolume from "@/utils/calcNormalizedVolume";

import selectedSpeakerState from "@/stores/stateSelectedSpeaker";
import speakerVolmeState from "@/stores/stateSpeakerVolume";
import speakerVolumeState from "@/stores/stateSpeakerVolume";
import micVolumeState from "@/stores/stateMicVolume";
import participantCavasInstanceState from "@/stores/stateParticipantCanvasInstance";
import participantSocketRefState from "@/stores/stateParticipantSocketRef";

interface HeaderParticipantControlsProps {
setLectureCode: React.Dispatch<React.SetStateAction<string>>;
setLectureTitle: React.Dispatch<React.SetStateAction<string>>;
}

const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControlsProps) => {
const [isSpeakerOn, setisSpeakerOn] = useState(false);
const HeaderParticipantControls = ({ setLectureCode, setLectureTitle }: HeaderParticipantControlsProps) => {
const [isSpeakerOn, setIsSpeakerOn] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [elapsedTime, setElapsedTime] = useState<number>(0);
const [didMount, setDidMount] = useState(false);

const selectedSpeaker = useRecoilValue(selectedSpeakerState);
const speakerVolume = useRecoilValue(speakerVolmeState);
const speakerVolume = useRecoilValue(speakerVolumeState);
const fabricCanvasRef = useRecoilValue(participantCavasInstanceState);
const setSpeakerVolume = useSetRecoilState(speakerVolmeState);
const setSpeakerVolume = useSetRecoilState(speakerVolumeState);
const setMicVolumeState = useSetRecoilState(micVolumeState);
const setParticipantSocket = useSetRecoilState(participantSocketRefState);

Expand All @@ -52,10 +56,9 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls

const navigate = useNavigate();
const showToast = useToast();
const { checkAuth } = useAuth();

const roomid = new URLSearchParams(useLocation().search).get("roomid") || "999999";
const sampleAccessToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InBsYXRpbm91c3MwMkBnbWFpbC5jb20iLCJpYXQiOjE3MDE2ODUyMDYsImV4cCI6MTcwMjcyMjAwNn0.gNXyIPGyaBKX5KjBVB6USNWGEc3k9ZruCTglCGeLo3Y";
const pc_config = {
iceServers: [
{
Expand All @@ -65,6 +68,7 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
};

useEffect(() => {
checkAuth();
setDidMount(true);
const backToMain = () => {
leaveLecture({ isLectureEnd: false });
Expand All @@ -78,6 +82,16 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
}, []);
useEffect(() => {
if (didMount) {
axios
.get(`${import.meta.env.VITE_API_SERVER_URL}/lecture?code=${roomid}`)
.then((result) => {
const lectureTitle = result.data.title;
setLectureTitle(lectureTitle);
})
.catch(() => {
// showToast({ message: "존재하지 않는 강의실입니다.", type: "alert" });
// navigate("/");
});
enterLecture();
setLectureCode(roomid);
}
Expand Down Expand Up @@ -146,10 +160,12 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
// guest 판별 로직 추가 예정
socketRef.current = managerRef.current.socket("/enter-room", {
auth: {
accessToken: sampleAccessToken,
accessToken: localStorage.getItem("token") ? localStorage.getItem("token") : "",
refreshToken: "test"
}
});
//guestAccessToken
// accessToken: localStorage.getItem("token") ? localStorage.getItem("token") : "",

if (!socketRef.current) return;
socketRef.current.on(`serverAnswer`, (data) => handleServerAnswer(data));
Expand Down Expand Up @@ -220,16 +236,16 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
if (!onFrameIdRef.current) {
// 최초 연결 후 음소거 해제
startAnalyse();
setisSpeakerOn(true);
setIsSpeakerOn(true);
showToast({ message: "음소거가 해제되었습니다.", type: "success" });
} else if (isSpeakerOn) {
prevSpeakerVolumeRef.current = speakerVolumeRef.current;
setSpeakerVolume(0);
setisSpeakerOn(false);
setIsSpeakerOn(false);
showToast({ message: "음소거 되었습니다.", type: "alert" });
} else {
setSpeakerVolume(prevSpeakerVolumeRef.current);
setisSpeakerOn(true);
setIsSpeakerOn(true);
showToast({ message: "음소거가 해제되었습니다.", type: "success" });
}
};
Expand All @@ -243,6 +259,10 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
};
const timer = setInterval(updateElapsedTime, 1000);
timerIdRef.current = timer;

console.log(data);
console.log(data.whiteboard);

loadCanvasData({
fabricCanvas: fabricCanvasRef!,
currentData: canvasData,
Expand Down Expand Up @@ -277,10 +297,11 @@ const HeaderParticipantControls = ({ setLectureCode }: HeaderParticipantControls
if (!managerRef.current) return;
lectureSocketRef.current = managerRef.current.socket("/lecture", {
auth: {
accessToken: sampleAccessToken,
accessToken: localStorage.getItem("token") ? localStorage.getItem("token") : socketRef.current!.id,
refreshToken: "sample"
}
});
// accessToken: localStorage.getItem("token") ? localStorage.getItem("token") : "",
setParticipantSocket(lectureSocketRef.current);
lectureSocketRef.current.on("ended", () => handleLectureEnd());
lectureSocketRef.current.on("update", (data) => handleWhiteboardUpdate(data));
Expand Down
Loading