Commit f3b19503 authored by 권병윤's avatar 권병윤
Browse files

master, woojiweon, byoungyun1 비교후 합침

parent 3e9c534c
PG_DATABASE=boraIt
PG_USER=postgres
PG_PASSWORD=postgres
\ No newline at end of file
node_modules/
package-lock.json
config/
env.development
\ No newline at end of file
// express 설정 파일
import express from "express";
import cors from "cors";
import socketio from "socket.io";
import http from "http";
import wrtc from "wrtc";
import cookieParser from "cookie-parser";
import mainRouter from "./routes/index.js";
const app = express();
app.use(cors());
const server = http.createServer(app);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use('/uploads', express.static('uploads'))
app.use("/api", mainRouter);
let receiverPCs = {};
let senderPCs = {};
let users = {};
let socketToRoom = {};
const pc_config = {
iceServers: [
// {
// urls: 'stun:[STUN_IP]:[PORT]',
// 'credentials': '[YOR CREDENTIALS]',
// 'username': '[USERNAME]'
// },
{
urls: "stun:stun.l.google.com:19302",
},
],
};
const isIncluded = (array, id) => {
let len = array.length;
for (let i = 0; i < len; i++) {
if (array[i].id === id) return true;
}
return false;
};
const createReceiverPeerConnection = (socketID, socket, roomID) => {
let pc = new wrtc.RTCPeerConnection(pc_config);
if (receiverPCs[socketID]) receiverPCs[socketID] = pc;
else receiverPCs = { ...receiverPCs, [socketID]: pc };
pc.onicecandidate = (e) => {
//console.log(`socketID: ${socketID}'s receiverPeerConnection icecandidate`);
socket.to(socketID).emit("getSenderCandidate", {
candidate: e.candidate,
});
};
pc.oniceconnectionstatechange = (e) => {
//console.log(e);
};
pc.ontrack = (e) => {
if (users[roomID]) {
if (!isIncluded(users[roomID], socketID)) {
users[roomID].push({
id: socketID,
stream: e.streams[0],
});
} else return;
} else {
users[roomID] = [
{
id: socketID,
stream: e.streams[0],
},
];
}
socket.broadcast.to(roomID).emit("userEnter", { id: socketID });
};
return pc;
};
const createSenderPeerConnection = (
receiverSocketID,
senderSocketID,
socket,
roomID
) => {
let pc = new wrtc.RTCPeerConnection(pc_config);
if (senderPCs[senderSocketID]) {
senderPCs[senderSocketID].filter((user) => user.id !== receiverSocketID);
senderPCs[senderSocketID].push({ id: receiverSocketID, pc: pc });
} else
senderPCs = {
...senderPCs,
[senderSocketID]: [{ id: receiverSocketID, pc: pc }],
};
pc.onicecandidate = (e) => {
//console.log(`socketID: ${receiverSocketID}'s senderPeerConnection icecandidate`);
socket.to(receiverSocketID).emit("getReceiverCandidate", {
id: senderSocketID,
candidate: e.candidate,
});
};
pc.oniceconnectionstatechange = (e) => {
//console.log(e);
};
const sendUser = users[roomID].filter((user) => user.id === senderSocketID);
sendUser[0].stream.getTracks().forEach((track) => {
pc.addTrack(track, sendUser[0].stream);
});
return pc;
};
const getOtherUsersInRoom = (socketID, roomID) => {
let allUsers = [];
if (!users[roomID]) return allUsers;
let len = users[roomID].length;
for (let i = 0; i < len; i++) {
if (users[roomID][i].id === socketID) continue;
allUsers.push({ id: users[roomID][i].id });
}
return allUsers;
};
const deleteUser = (socketID, roomID) => {
let roomUsers = users[roomID];
if (!roomUsers) return;
roomUsers = roomUsers.filter((user) => user.id !== socketID);
users[roomID] = roomUsers;
if (roomUsers.length === 0) {
delete users[roomID];
}
delete socketToRoom[socketID];
};
const closeRecevierPC = (socketID) => {
if (!receiverPCs[socketID]) return;
receiverPCs[socketID].close();
delete receiverPCs[socketID];
};
const closeSenderPCs = (socketID) => {
if (!senderPCs[socketID]) return;
let len = senderPCs[socketID].length;
for (let i = 0; i < len; i++) {
senderPCs[socketID][i].pc.close();
let _senderPCs = senderPCs[senderPCs[socketID][i].id];
let senderPC = _senderPCs.filter((sPC) => sPC.id === socketID);
if (senderPC[0]) {
senderPC[0].pc.close();
senderPCs[senderPCs[socketID][i].id] = _senderPCs.filter(
(sPC) => sPC.id !== socketID
);
}
}
delete senderPCs[socketID];
};
const io = socketio.listen(server);
io.sockets.on("connection", (socket) => {
console.log("socket connection complete")
socket.on("joinRoom", (data) => {
try {
let allUsers = getOtherUsersInRoom(data.id, data.roomID);
io.to(data.id).emit("allUsers", { users: allUsers });
} catch (error) {
console.log(error);
}
});
socket.on("senderOffer", async (data) => {
try {
socketToRoom[data.senderSocketID] = data.roomID;
let pc = createReceiverPeerConnection(
data.senderSocketID,
socket,
data.roomID
);
await pc.setRemoteDescription(data.sdp);
let sdp = await pc.createAnswer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
});
await pc.setLocalDescription(sdp);
socket.join(data.roomID);
io.to(data.senderSocketID).emit("getSenderAnswer", { sdp });
} catch (error) {
console.log(error);
}
});
socket.on("senderCandidate", async (data) => {
try {
let pc = receiverPCs[data.senderSocketID];
await pc.addIceCandidate(new wrtc.RTCIceCandidate(data.candidate));
} catch (error) {
console.log(error);
}
});
socket.on("receiverOffer", async (data) => {
try {
let pc = createSenderPeerConnection(
data.receiverSocketID,
data.senderSocketID,
socket,
data.roomID
);
await pc.setRemoteDescription(data.sdp);
let sdp = await pc.createAnswer({
offerToReceiveAudio: false,
offerToReceiveVideo: false,
});
await pc.setLocalDescription(sdp);
io.to(data.receiverSocketID).emit("getReceiverAnswer", {
id: data.senderSocketID,
sdp,
});
} catch (error) {
console.log(error);
}
});
socket.on("receiverCandidate", async (data) => {
try {
const senderPC = senderPCs[data.senderSocketID].filter(
(sPC) => sPC.id === data.receiverSocketID
);
await senderPC[0].pc.addIceCandidate(
new wrtc.RTCIceCandidate(data.candidate)
);
} catch (error) {
console.log(error);
}
});
socket.on("disconnect", () => {
try {
let roomID = socketToRoom[socket.id];
deleteUser(socket.id, roomID);
closeRecevierPC(socket.id);
closeSenderPCs(socket.id);
socket.broadcast.to(roomID).emit("userExit", { id: socket.id });
} catch (error) {
console.log(error);
}
});
});
export default server;
REACT_APP_KAKAO_KEY=783f1ca14e1f14bc6442eedcd37c76a1
REACT_FETCH_URL=http://localhost:3000
\ No newline at end of file
...@@ -22,4 +22,5 @@ npm-debug.log* ...@@ -22,4 +22,5 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
.env .env
\ No newline at end of file kakao-congif.json
\ No newline at end of file
{
"REACT_APP_KAKAO_KEY": "783f1ca14e1f14bc6442eedcd37c76a1"
}
\ No newline at end of file
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"@testing-library/user-event": "^12.1.10", "@testing-library/user-event": "^12.1.10",
"axios": "^0.21.1", "axios": "^0.21.1",
"bootstrap": "^5.0.2", "bootstrap": "^5.0.2",
"nanoid": "^3.1.23",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
...@@ -23,6 +24,13 @@ ...@@ -23,6 +24,13 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"nodemonConfig": {
"ignore": [
"test/*",
"docs/*",
"client/*"
]
},
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
"react-app", "react-app",
......
...@@ -25,6 +25,11 @@ ...@@ -25,6 +25,11 @@
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>BORA It</title> <title>BORA It</title>
<script src="//developers.kakao.com/sdk/js/kakao.js"></script>
<script>
Kakao.init(process.env.REACT_APP_KAKAO_KEY);//SDK초기화
console.log(Kakao.isInitialized());// 초기화 여부 판단
</script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
......
...@@ -5,6 +5,7 @@ import HomeUserPage from "./pages/HomeUserPage"; ...@@ -5,6 +5,7 @@ import HomeUserPage from "./pages/HomeUserPage";
import ProfilePage from "./pages/ProfilePage"; import ProfilePage from "./pages/ProfilePage";
import RoomPage from "./pages/RoomPage"; import RoomPage from "./pages/RoomPage";
import InfoUpdatePage from "./pages/InfoUpdatePage"; import InfoUpdatePage from "./pages/InfoUpdatePage";
import InvitePage from "./pages/InvitePage";
function App() { function App() {
return ( return (
...@@ -16,6 +17,7 @@ function App() { ...@@ -16,6 +17,7 @@ function App() {
<Route path="/profile/:id/update" component={InfoUpdatePage} /> <Route path="/profile/:id/update" component={InfoUpdatePage} />
<Route path="/profile/:id" component={ProfilePage} /> <Route path="/profile/:id" component={ProfilePage} />
<Route path="/room/:roomId/:channelId" component={RoomPage} /> <Route path="/room/:roomId/:channelId" component={RoomPage} />
<Route path="/room/Invite" component={InvitePage} />
</Switch> </Switch>
{/* </AuthProvider> */} {/* </AuthProvider> */}
</Router> </Router>
......
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Redirect } from "react-router-dom"; import { Redirect } from "react-router-dom";
import roomApi from "../../apis/room.api"; import roomApi from "../../apis/room.api";
import catchErrors from "../../context/catchError";
const userId = localStorage.getItem('user'); const userId = localStorage.getItem('user');
...@@ -18,21 +19,17 @@ const JoinRoom = () => { ...@@ -18,21 +19,17 @@ const JoinRoom = () => {
function handleChange(event) { function handleChange(event) {
const { value } = event.target; const { value } = event.target;
setRoomId(value); setRoomId(value);
// console.log(roomId);
} }
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
try { try {
// setLoading(true); // setLoading(true);
// setError(""); setError("");
// console.log('userId:', userId)
// console.log('roomId:', roomId)
const data = await roomApi.join({ userId: userId, roomId: roomId }); const data = await roomApi.join({ userId: userId, roomId: roomId });
// console.log(data);
setSuccess(true); setSuccess(true);
} catch (error) { } catch (error) {
// catchErrors(error, setError); catchErrors(error, setError);
} finally { } finally {
// setLoading(false); // setLoading(false);
} }
...@@ -45,7 +42,9 @@ const JoinRoom = () => { ...@@ -45,7 +42,9 @@ const JoinRoom = () => {
<div className="modal-content"> <div className="modal-content">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="modal-header"> <div className="modal-header">
<div className="modal-title" id="joinModal">방참여하기</div> <div className="modal-title" id="joinModal">
방참여하기
</div>
<button <button
type="button" type="button"
className="btn-close" className="btn-close"
...@@ -77,4 +76,4 @@ const JoinRoom = () => { ...@@ -77,4 +76,4 @@ const JoinRoom = () => {
); );
}; };
export default JoinRoom; export default JoinRoom;
\ No newline at end of file
import React, { useEffect } from "react";
const KakaoShareButton = () => {
useEffect(() => {
createKakaoButton();
}, []);
const ad = "이름"
const createKakaoButton = () => {
// kakao sdk script이 정상적으로 불러와졌으면 window.Kakao로 접근이 가능합니다
if (window.Kakao) {
const kakao = window.Kakao;
// 중복 initialization 방지
if (!kakao.isInitialized()) {
// 두번째 step 에서 가져온 javascript key 를 이용하여 initialize
kakao.init(process.env.REACT_APP_KAKAO_KEY);
}
kakao.Link.createDefaultButton({
container: '#kakao-link-btn',
objectType: 'text',
text:
`${ad}`,
//'기본 템플릿으로 제공되는 텍스트 템플릿은 텍스트를 최대 200자까지 표시할 수 있습니다. 텍스트 템플릿은 텍스트 영역과 하나의 기본 버튼을 가집니다. 임의의 버튼을 설정할 수도 있습니다. 여러 장의 이미지, 프로필 정보 등 보다 확장된 형태의 카카오링크는 다른 템플릿을 이용해 보낼 수 있습니다.',
link: {
mobileWebUrl:
'http://localhost:3000/room/Invite',
webUrl:
'http://localhost:3000/room/Invite',
},
});
}
};
return (
<div className="kakao-share-button">
{/* Kakao share button */}
<button
id="kakao-link-btn"
type="submit"
className="col-2 p-1 btn btn-primary"
data-bs-dismiss="modal"
style={{ width: "120px" }}
>카카오로 초대
</button>
</div>
);
};
export default KakaoShareButton;
...@@ -14,6 +14,7 @@ const Login = () => { ...@@ -14,6 +14,7 @@ const Login = () => {
const [disabled, setDisabled] = useState(true) const [disabled, setDisabled] = useState(true)
const [error, setError] = useState('') const [error, setError] = useState('')
const [success, setSuccess] = useState(false) const [success, setSuccess] = useState(false)
const [id, setId] = useState('')
useEffect(() => { useEffect(() => {
const isUser = Object.values(user).every((el) => Boolean(el)) const isUser = Object.values(user).every((el) => Boolean(el))
...@@ -33,6 +34,7 @@ const Login = () => { ...@@ -33,6 +34,7 @@ const Login = () => {
// setError(""); // setError("");
const data = await userApi.login(user) const data = await userApi.login(user)
console.log(data) console.log(data)
setId(data.id)
handleLogin(data.id) handleLogin(data.id)
setSuccess(true) setSuccess(true)
} catch (error) { } catch (error) {
...@@ -44,7 +46,7 @@ const Login = () => { ...@@ -44,7 +46,7 @@ const Login = () => {
} }
if (success) { if (success) {
alert('로그인 되었습니다') alert('로그인 되었습니다')
return <Redirect to="/user" /> return <Redirect to={`/user/${id}`} />
} }
const { email, password } = user const { email, password } = user
......
import userApi from "../../apis/user.api"; import userApi from "../../apis/user.api";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import catchErrors from "../../context/catchError";
const userprofile = localStorage.getItem("user"); const userprofile = localStorage.getItem("user");
const INIT_PROFILE = { const INIT_PROFILE = {
...@@ -11,12 +12,15 @@ const INIT_PROFILE = { ...@@ -11,12 +12,15 @@ const INIT_PROFILE = {
const Info = () => { const Info = () => {
const [profile, setProfile] = useState(INIT_PROFILE); const [profile, setProfile] = useState(INIT_PROFILE);
const [error, setError]= useState("");
async function getProfile(userID) { async function getProfile(userID) {
try { try {
const data = await userApi.getUser(userID); const data = await userApi.getUser(userID);
setProfile(data); setProfile(data);
} catch (error) {} } catch (error) {
catchErrors(error, setError);
}
} }
useEffect(() => { useEffect(() => {
getProfile(userprofile); getProfile(userprofile);
......
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import userApi from "../../apis/user.api"; import userApi from "../../apis/user.api";
import catchErrors from "../../context/catchError";
const userprofile = localStorage.getItem("user"); const userprofile = localStorage.getItem("user");
const INIT_PROFILE = { const INIT_PROFILE = {
...@@ -16,12 +17,15 @@ const InfoUpdate = () => { ...@@ -16,12 +17,15 @@ const InfoUpdate = () => {
const [profile, setProfile] = useState(INIT_PROFILE); const [profile, setProfile] = useState(INIT_PROFILE);
const [error,setError]=useState("");
async function getProfile(userID) { async function getProfile(userID) {
try { try {
const data = await userApi.getUser(userID); const data = await userApi.getUser(userID);
setProfile(data); setProfile(data);
} catch (error) {} } catch (error) {
catchErrors(error, setError);
}
} }
useEffect(() => { useEffect(() => {
...@@ -39,7 +43,9 @@ const InfoUpdate = () => { ...@@ -39,7 +43,9 @@ const InfoUpdate = () => {
setProfile({...profile, img:res}) setProfile({...profile, img:res})
}else{ }else{
setProfile() setProfile()
} } catch (error) {} } } catch (error) {
catchErrors(error, setError);
}
}; };
const changeinfo = async (event) => { const changeinfo = async (event) => {
......
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import userApi from "../../apis/user.api"; import userApi from "../../apis/user.api";
import catchErrors from "../../context/catchError";
const userprofile = localStorage.getItem("user"); const userprofile = localStorage.getItem("user");
const INIT_PROFILE = { const INIT_PROFILE = {
...@@ -11,13 +11,15 @@ const INIT_PROFILE = { ...@@ -11,13 +11,15 @@ const INIT_PROFILE = {
const Profile = () => { const Profile = () => {
const [profile, setProfile] = useState(INIT_PROFILE); const [profile, setProfile] = useState(INIT_PROFILE);
const [error,setError]= useState("");
async function getProfile(userID) { async function getProfile(userID) {
try { try {
const data = await userApi.getUser(userID); const data = await userApi.getUser(userID);
setProfile(data.img) setProfile(data.img)
} catch (error) {} } catch (error) {
catchErrors(error, setError);
}
} }
useEffect(() => { useEffect(() => {
getProfile(userprofile); getProfile(userprofile);
......
...@@ -9,7 +9,7 @@ const ChannelList = () => { ...@@ -9,7 +9,7 @@ const ChannelList = () => {
<nav className="navbar navbar-light d-flex justify-content-between"> <nav className="navbar navbar-light d-flex justify-content-between">
<LeftHamburger /> <LeftHamburger />
<div> <div>
<Link to="/user"> <Link to="/user:id">
<img src="/BORA.png" style={{ width: '160px' }} /> <img src="/BORA.png" style={{ width: '160px' }} />
</Link> </Link>
</div> </div>
......
import UserState from "./UserState";
import { Link } from "react-router-dom";
import KakaoShareButton from "../KakaoShareButton";
import { useEffect } from "react";
const LeftHamberger = () => {
useEffect(() => {
const script = document.createElement("script");
script.src = "https://developers.kakao.com/sdk/js/kakao.js";
script.async = true;
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, []);
// function KakaoCode() {
// window.location.href
// ="https://kauth.kakao.com/oauth/authorize?client_id=ca4c7126765a1dfd4c127c27415d4abc&redirect_uri=http://localhost:3000/room/Invite&response_type=code&prompt=login&scope=talk_message,friends"
// }
function roomIdCopy() {
const t = document.querySelector("#roomId").innerText;
console.log(t);
navigator.clipboard.writeText(t);
//document.execCommand("copy");
}
return (
<div>
<div>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#left-hamburger"
aria-controls="left-hamburger"
aria-expanded="false"
aria-label="Toggle navigation"
style={{ border: "#f4c1f2" }}
>
<span className="navbar-toggler-icon"></span>
</button>
</div>
<div
className="offcanvas offcanvas-start"
style={{ width: "330px" }}
tabIndex="-1"
id="left-hamburger"
aria-labelledby="hamburgerLabel"
>
<div className="offcanvas-header">
<p
className="col-6 offcanvas-title"
id="offcanvasExampleLabel"
style={{
fontWeight: "bold",
fontSize: "15px",
width: "150px",
color: "#000000",
}}
>
/오프라인 사용자
</p>
<h6 className="mt-2" id="roomId">
{" "}
#ASV2AE985{" "}
</h6>
</div>
<UserState />
<div>
<div className="d-flex flex-row-reverse">
<button
type="button"
className="m-3 rounded"
data-bs-toggle="modal"
data-bs-target="#InviteRoom"
style={{
height: "30px",
fontWeight: "bold",
backgroundColor: "#E0CEE8",
color: "black",
border: "1px #D64D61",
}}
>
초대
</button>
<div
className="modal fade"
id="InviteRoom"
tabIndex="-1"
aria-labelledby="InviteRoomLabel"
aria-hidden="true"
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div className="modal-body d-flex justify-content-center">
어떤 방식으로 초대하시겠습니까?
</div>
<div className="row mb-3">
<div className="d-flex justify-content-evenly">
{/* <button
type="submit"
className="col-2 p-1 btn btn-primary"
data-bs-dismiss="modal"
style={{ width: "120px" }}
onClick={KakaoCode}
>
카카오로 초대
</button> */}
<KakaoShareButton />
<button
type="submit"
className="col-2 p-1 btn btn-primary"
data-bs-dismiss="modal"
style={{ width: "120px" }}
onClick={roomIdCopy}
>
Id 복사
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default LeftHamberger;
import backward from "../../images/backward.png";
import ChannelSingle from "./ChannelSingle";
const LeftHamberger = () => {
function roomIdCopy() {
const t = document.querySelector("#roomId").innerText;
console.log(t);
navigator.clipboard.writeText(t);
document.execCommand("copy");
}
return (
<div>
<div>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#left-hamburger"
aria-controls="left-hamburger"
aria-expanded="false"
aria-label="Toggle navigation"
style={{ border: "#f4c1f2" }}
>
<span className="navbar-toggler-icon"></span>
</button>
</div>
<div
className="offcanvas offcanvas-start"
style={{ width: "330px" }}
tabIndex="-1"
id="left-hamburger"
aria-labelledby="hamburgerLabel"
>
<div className="offcanvas-header">
<p
className="col-6 offcanvas-title"
id="offcanvasExampleLabel"
style={{
fontWeight: "bold",
fontSize: "15px",
width: "150px",
color: "#000000",
}}
>
/오프라인 사용자
</p>
<h6 className="mt-2" id="roomId">
{" "}
#ASV2AE985{" "}
</h6>
<button
type="button"
className="btn-close text-reset"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div className="overflow-auto" style={{ height: "610px" }}>
<div className="mb-3">
<div className="m-3 p-1 row" style={{ backgroundColor: "#E0CEE8" }}>
<h5 className="col mt-2">온라인 사용자</h5>
</div>
<ul className="mx-5" style={{ color: "#76D079" }}>
<li>
<p style={{ color: "black" }}>CHERRY</p>
</li>
<li>
<p style={{ color: "black" }}>JAEYEON</p>
</li>
<li>
<p style={{ color: "black" }}>SEOYEON</p>
</li>
<li>
<p style={{ color: "black" }}>JIWEON</p>
</li>
<li>
<p style={{ color: "black" }}>BYOUNGYUN</p>
</li>
</ul>
</div>
<div className="mb-3">
<div className="m-3 p-1 row" style={{ backgroundColor: "#E0CEE8" }}>
<h5 className="col mt-2">오프라인 사용자</h5>
</div>
<ul className="mx-5">
<li>CHERRY</li>
<li>JAEYEON</li>
<li>SEOYEON</li>
<li>JIWEON</li>
<li>BYOUNGYUN</li>
</ul>
</div>
</div>
<div>
<div className="d-flex flex-row-reverse">
<button
type="button"
className="m-3 rounded"
data-bs-toggle="modal"
data-bs-target="#inviteRoom"
style={{
height: "30px",
fontWeight: "bold",
backgroundColor: "#E0CEE8",
color: "black",
border: "1px #D64D61",
}}
>
초대
</button>
<div
className="modal fade"
id="inviteRoom"
tabIndex="-1"
aria-labelledby="exitRoomLabel"
aria-hidden="true"
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div className="modal-body d-flex justify-content-center">
어떤 방식으로 초대하시겠습니까?
</div>
<div className="row mb-3">
<div className="d-flex justify-content-evenly">
<button
type="submit"
className="col-2 p-1 btn btn-primary"
style={{ width: "120px" }}
>
카카오로 초대
</button>
{/*
<button
type="submit"
className="col-2 p-1 btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#copyRoomId"
onClick={roomIdCopy}
style={{ width: "120px" }}
>
방 Id 복사
</button> */}
<button
type="submit"
className="col-2 p-1 btn btn-primary"
data-bs-dismiss="modal"
style={{ width: "120px" }}
onClick={roomIdCopy}
>
Id 복사
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default LeftHamberger;
import { Link } from 'react-router-dom'
const RoomHeader = () => { const RoomHeader = () => {
return ( return (
<div <div
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment