Refactor draft messaging to unified enum-based protocol
- Replaced scattered message strings with `DraftMessage` `StrEnum` and numeric `DraftPhase` `IntEnum` for clear, centralized definitions. - Added Python→JS constants sync via `scripts/generate_js_constants.py` to ensure backend/frontend parity. - Refactored WebSocket consumers to use `broadcast.*` and `direct.message` handlers with `_dispatch_broadcast` for consistent event delivery. - Enhanced `DraftStateManager` to store `draft_index` and explicitly manage `connected_participants`. - Added colored logging config in settings for improved debugging. - Frontend: split UI into `ParticipantList` and `DraftMoviePool`, extracted message handlers (`handleDraftStatusMessages`, `handleUserIdentifyMessages`), and updated components to use new message/phase enums.
This commit is contained in:
@@ -1,53 +1,13 @@
|
||||
// DraftAdmin.jsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useWebSocket } from "../WebSocketContext.jsx";
|
||||
import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
|
||||
import { DraftMessage, DraftPhases, DraftPhase } from '../constants.js';
|
||||
import { fetchDraftDetails } from "../common/utils.js"
|
||||
|
||||
const ParticipantList = ({ socket, participants, draftOrder }) => {
|
||||
const [connectedParticipants, setConnectedParticipants] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = async ({ data }) => {
|
||||
const message = JSON.parse(data)
|
||||
const { type, payload } = message
|
||||
console.log('socket changed', message)
|
||||
if (payload?.connected_participants) {
|
||||
const { connected_participants } = payload
|
||||
setConnectedParticipants(connected_participants)
|
||||
}
|
||||
}
|
||||
socket.addEventListener("message", handleMessage)
|
||||
return () => {
|
||||
socket.removeEventListener("message", handleMessage)
|
||||
}
|
||||
}, [socket])
|
||||
|
||||
const ListTag = draftOrder.length > 0 ? "ol" : "ul"
|
||||
console.log
|
||||
const listItems = draftOrder.length > 0 ? draftOrder.map(d => participants.find(p => p.username == d)) : participants
|
||||
import { ParticipantList } from "../common/ParticipantList.jsx";
|
||||
import { DraftMessage, DraftPhase, DraftPhaseLabel, DraftPhasesOrdered } from '../constants.js';
|
||||
import { fetchDraftDetails, isEmptyObject, handleDraftStatusMessages, handleUserIdentifyMessages } from "../common/utils.js"
|
||||
import { DraftMoviePool } from "../common/DraftMoviePool.jsx"
|
||||
|
||||
|
||||
return (
|
||||
<div className="participant-list-container">
|
||||
<label>Particpants</label>
|
||||
<ListTag className="participant-list">
|
||||
{listItems.map((p, i) => (
|
||||
<li key={i}>
|
||||
<span>{p?.full_name}</span>
|
||||
<div
|
||||
className={
|
||||
`ms-2 stop-light ${connectedParticipants.includes(p?.username) ? "success" : "danger"}`
|
||||
}
|
||||
></div>
|
||||
</li>
|
||||
))}
|
||||
</ListTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DraftPhaseDisplay = ({ draftPhase, nextPhaseHandler, prevPhaseHandler }) => {
|
||||
return (
|
||||
@@ -57,14 +17,14 @@ const DraftPhaseDisplay = ({ draftPhase, nextPhaseHandler, prevPhaseHandler }) =
|
||||
<div className="change-phase"><button onClick={prevPhaseHandler}><i className="bi bi-chevron-left"></i></button></div>
|
||||
<ol>
|
||||
{
|
||||
DraftPhases.map((p) => (
|
||||
DraftPhasesOrdered.map((p) => (
|
||||
<li key={p} className={p === draftPhase ? "current-phase" : ""}>
|
||||
<span>{p}</span>
|
||||
<span>{DraftPhaseLabel[p]}</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
<div className="change-phase"><button onClick={nextPhaseHandler}><i className="bi bi-chevron-right"></i></button></div>
|
||||
<div className="change-phase"><button onClick={nextPhaseHandler}><i className="bi bi-chevron-right"></i></button></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -72,86 +32,84 @@ const DraftPhaseDisplay = ({ draftPhase, nextPhaseHandler, prevPhaseHandler }) =
|
||||
|
||||
export const DraftAdmin = ({ draftSessionId }) => {
|
||||
const socket = useWebSocket();
|
||||
const [connectedParticipants, setConnectedParticipants] = useState([]);
|
||||
const [draftDetails, setDraftDetails] = useState();
|
||||
const [participants, setParticipants] = React.useState([]);
|
||||
const [draftPhase, setDraftPhase] = useState();
|
||||
const [draftOrder, setDraftOrder] = useState([]);
|
||||
console.log(socket)
|
||||
const [draftState, setDraftState] = useState({})
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDraftDetails(draftSessionId)
|
||||
.then((data) => {
|
||||
console.log("Fetched draft data", data)
|
||||
setParticipants(data.participants)
|
||||
setDraftDetails(data)
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(()=>{
|
||||
if (!socket) return;
|
||||
const openHandler = (event)=>{
|
||||
console.log('Websocket Opened')
|
||||
}
|
||||
const closeHandler = (event)=>{
|
||||
console.log('Websocket Closed')
|
||||
}
|
||||
socket.addEventListener('open', openHandler );
|
||||
socket.addEventListener('close', closeHandler );
|
||||
return ()=>{
|
||||
socket.removeEventListener('open', openHandler );
|
||||
socket.removeEventListener('close', closeHandler );
|
||||
}
|
||||
}, [socket])
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket) return;
|
||||
|
||||
const handleMessage = (event) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const { type, payload } = message;
|
||||
console.log(type, event)
|
||||
if (!payload) return
|
||||
if (type == DraftMessage.REQUEST.JOIN_PARTICIPANT) {
|
||||
console.log('join request', data)
|
||||
}
|
||||
if (payload.phase) {
|
||||
console.log('phase_change')
|
||||
setDraftPhase(payload.phase)
|
||||
}
|
||||
if (payload.draft_order) {
|
||||
console.log('draft_order', payload.draft_order)
|
||||
setDraftOrder(payload.draft_order)
|
||||
}
|
||||
}
|
||||
|
||||
socket.addEventListener('message', handleMessage);
|
||||
|
||||
socket.onclose = (event) => {
|
||||
console.log('Websocket Closed')
|
||||
socket = null;
|
||||
}
|
||||
|
||||
const draftStatusMessageHandler = (event) => handleDraftStatusMessages(event, setDraftState)
|
||||
const userIdentifyMessageHandler = (event) => handleUserIdentifyMessages(event, setCurrentUser)
|
||||
socket.addEventListener('message', draftStatusMessageHandler );
|
||||
socket.addEventListener('message', userIdentifyMessageHandler );
|
||||
|
||||
return () => {
|
||||
socket.removeEventListener('message', handleMessage)
|
||||
socket.close();
|
||||
socket.removeEventListener('message', draftStatusMessageHandler)
|
||||
socket.removeEventListener('message', userIdentifyMessageHandler );
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const handlePhaseChange = (target) => {
|
||||
let destination
|
||||
const origin = draftPhase
|
||||
if (target == "next") {
|
||||
console.log(DraftPhase)
|
||||
console.log("phase to be changed", origin, target, DraftPhase.WAITING)
|
||||
if (origin == "waiting"){
|
||||
destination = DraftPhase.DETERMINE_ORDER
|
||||
} else if (origin == "determine_order"){
|
||||
destination = DraftPhase.NOMINATION
|
||||
}
|
||||
}
|
||||
else if (target=="previous") {
|
||||
|
||||
const origin = draftState.phase
|
||||
const originPhaseIndex = DraftPhasesOrdered.findIndex(i => i == origin)
|
||||
console.log('origin phase index', originPhaseIndex)
|
||||
if (target == "next" && originPhaseIndex < DraftPhasesOrdered.length) {
|
||||
destination = DraftPhasesOrdered[originPhaseIndex + 1]
|
||||
}
|
||||
|
||||
if (!destination) {return}
|
||||
else if (target == "previous" && originPhaseIndex > 0) {
|
||||
destination = DraftPhasesOrdered[originPhaseIndex - 1]
|
||||
}
|
||||
console.log(destination)
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.REQUEST.PHASE_CHANGE, origin, destination }
|
||||
{ type: DraftMessage.PHASE_CHANGE_REQUEST, origin, destination }
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
const handleStartDraft = () => {
|
||||
|
||||
}
|
||||
|
||||
const handleAdvanceDraft = () => {
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const handleRequestDraftSummary = () => {
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.REQUEST.DRAFT_STATUS }
|
||||
{ type: DraftMessage.STATUS_SYNC_REQUEST }
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -169,12 +127,14 @@ export const DraftAdmin = ({ draftSessionId }) => {
|
||||
</div>
|
||||
|
||||
<ParticipantList
|
||||
socket={socket}
|
||||
participants={participants}
|
||||
draftOrder={draftOrder}
|
||||
draftState={draftState}
|
||||
draftDetails={draftDetails}
|
||||
isAdmin={true}
|
||||
/>
|
||||
<button onClick={handleAdvanceDraft} className="btn btn-primary">Advance Draft</button>
|
||||
<DraftMoviePool draftDetails={draftDetails}></DraftMoviePool>
|
||||
|
||||
<DraftPhaseDisplay draftPhase={draftPhase} nextPhaseHandler={ ()=>{handlePhaseChange('next')}} prevPhaseHandler= {() => {handlePhaseChange('previous')}}></DraftPhaseDisplay>
|
||||
<DraftPhaseDisplay draftPhase={draftState.phase} nextPhaseHandler={() => { handlePhaseChange('next') }} prevPhaseHandler={() => { handlePhaseChange('previous') }}></DraftPhaseDisplay>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
21
frontend/src/apps/draft/common/DraftMoviePool.jsx
Normal file
21
frontend/src/apps/draft/common/DraftMoviePool.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { isEmptyObject } from "./utils";
|
||||
|
||||
export const DraftMoviePool = ({ draftDetails }) => {
|
||||
if(isEmptyObject(draftDetails)) {return}
|
||||
const {movies} = draftDetails
|
||||
|
||||
return (
|
||||
<div className="movie-pool-container">
|
||||
<ul>
|
||||
{movies.map(m => (
|
||||
<li key={m.id}>
|
||||
<a href={`/api/movie/${m.id}/detail`}>
|
||||
{m.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
33
frontend/src/apps/draft/common/ParticipantList.jsx
Normal file
33
frontend/src/apps/draft/common/ParticipantList.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { fetchDraftDetails, isEmptyObject } from "../common/utils.js"
|
||||
|
||||
export const ParticipantList = ({ isAdmin, draftState, draftDetails }) => {
|
||||
if (isEmptyObject(draftState) || isEmptyObject(draftDetails)) { console.warn('empty draft state', draftState); return }
|
||||
const { draft_order, draft_index, connected_participants } = draftState
|
||||
const { participants } = draftDetails
|
||||
|
||||
const ListTag = draft_order.length > 0 ? "ol" : "ul"
|
||||
console.log
|
||||
const listItems = draft_order.length > 0 ? draft_order.map(d => participants.find(p => p.username == d)) : participants
|
||||
|
||||
|
||||
return (
|
||||
<div className="participant-list-container">
|
||||
<label>Particpants</label>
|
||||
<ListTag className="participant-list">
|
||||
{listItems.map((p, i) => (
|
||||
<li key={i} className={`${i == draft_index ? "fw-bold" : ""}`}>
|
||||
<span>{p?.full_name}</span>
|
||||
{isAdmin ? (
|
||||
<div
|
||||
className={
|
||||
`ms-2 stop-light ${connected_participants.includes(p?.username) ? "success" : "danger"}`
|
||||
}
|
||||
></div>
|
||||
) : null}
|
||||
</li>
|
||||
))}
|
||||
</ListTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DraftMessage } from "../constants"
|
||||
|
||||
export async function fetchDraftDetails(draftSessionId) {
|
||||
return fetch(`/api/draft/${draftSessionId}/`)
|
||||
.then((response) => {
|
||||
@@ -26,4 +28,40 @@ export async function fetchMovieDetails(draftSessionId) {
|
||||
.catch((err) => {
|
||||
console.error("Error fetching draft details", err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmptyObject(obj) {
|
||||
return obj == null || (Object.keys(obj).length === 0 && obj.constructor === Object);
|
||||
}
|
||||
|
||||
export const handleDraftStatusMessages = (event, setDraftState) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const { type, payload } = message;
|
||||
console.log("Message: ", type, event?.data)
|
||||
|
||||
if (!payload) return
|
||||
const {connected_participants, phase, draft_order, draft_index} = payload
|
||||
|
||||
if (type == DraftMessage.STATUS_SYNC_INFORM) {
|
||||
setDraftState(payload)
|
||||
}
|
||||
|
||||
setDraftState(prev=>({
|
||||
...prev,
|
||||
...(connected_participants ? { connected_participants } : {}),
|
||||
...(draft_order ? { draft_order } : {}),
|
||||
...(draft_index ? { draft_index } : {}),
|
||||
...(phase ? { phase: Number(phase) } : {}),
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
export const handleUserIdentifyMessages = (event, setUser) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const { type, payload } = message;
|
||||
console.log("Message: ", type, event?.data)
|
||||
|
||||
if (!payload) return
|
||||
const {current_user} = payload
|
||||
setUser(current_user)
|
||||
}
|
||||
@@ -1,51 +1,48 @@
|
||||
// AUTO-GENERATED. Do not edit by hand.
|
||||
// Run: python scripts/generate_js_constants.py
|
||||
|
||||
|
||||
export const DraftMessage = {
|
||||
// Server to Client
|
||||
INFORM: {
|
||||
PHASE_CHANGE: "inform.phase.change",
|
||||
PHASE: "inform.phase",
|
||||
STATUS: "inform.status",
|
||||
JOIN_USER: "inform.join.user",
|
||||
DRAFT_STATUS: "inform.draft_status"
|
||||
},
|
||||
|
||||
// Client to Server
|
||||
REQUEST: {
|
||||
PHASE_CHANGE: "request.phase.change",
|
||||
INFORM_STATUS: "request.inform.status",
|
||||
JOIN_PARTICIPANT: "request.join.participant",
|
||||
JOIN_ADMIN: "request.join.admin",
|
||||
DETERMINE_DRAFT_ORDER: "request.determine.draft_order",
|
||||
DRAFT_STATUS: "request.draft_status"
|
||||
},
|
||||
|
||||
// Confirmation messages (Server to Client)
|
||||
CONFIRM: {
|
||||
PHASE_CHANGE: "confirm.phase.change",
|
||||
JOIN_PARTICIPANT: "confirm.join.participant",
|
||||
JOIN_ADMIN: "confirm.join.admin",
|
||||
DETERMINE_DRAFT_ORDER: "confirm.determine.draft_order",
|
||||
},
|
||||
|
||||
// Client-side notification (to server)
|
||||
NOTIFY: {
|
||||
JOIN_USER: "notify.join.user",
|
||||
},
|
||||
PARTICIPANT_JOIN_REQUEST: "participant.join.request",
|
||||
PARTICIPANT_JOIN_CONFIRM: "participant.join.confirm",
|
||||
PARTICIPANT_JOIN_REJECT: "participant.join.reject",
|
||||
PARTICIPANT_LEAVE_INFORM: "participant.leave.inform",
|
||||
USER_JOIN_INFORM: "user.join.inform",
|
||||
USER_LEAVE_INFORM: "user.leave.inform",
|
||||
USER_IDENTIFICATION_INFORM: "user.identification.inform",
|
||||
PHASE_CHANGE_INFORM: "phase.change.inform",
|
||||
PHASE_CHANGE_REQUEST: "phase.change.request",
|
||||
PHASE_CHANGE_CONFIRM: "phase.change.confirm",
|
||||
STATUS_SYNC_REQUEST: "status.sync.request",
|
||||
STATUS_SYNC_INFORM: "status.sync.inform",
|
||||
DRAFT_INDEX_ADVANCE_REQUEST: "draft.index.advance.request",
|
||||
DRAFT_INDEX_ADVANCE_CONFIRM: "draft.index.advance.confirm",
|
||||
ORDER_DETERMINE_REQUEST: "order.determine.request",
|
||||
ORDER_DETERMINE_CONFIRM: "order.determine.confirm",
|
||||
BID_START_INFORM: "bid.start.inform",
|
||||
BID_PLACE_REQUEST: "bid.place.request",
|
||||
BID_UPDATE_INFORM: "bid.update.inform",
|
||||
BID_END_INFORM: "bid.end.inform",
|
||||
NOMINATION_SUBMIT_REQUEST: "nomination.submit.request",
|
||||
NOMINATION_CONFIRM: "nomination.submit.confirm",
|
||||
};
|
||||
|
||||
export const DraftPhase = {
|
||||
WAITING: 0,
|
||||
DETERMINE_ORDER: 10,
|
||||
NOMINATION: 20,
|
||||
BIDDING: 30,
|
||||
AWARD: 40,
|
||||
FINALIZE: 50,
|
||||
}
|
||||
WAITING: 10,
|
||||
DETERMINE_ORDER: 20,
|
||||
NOMINATING: 30,
|
||||
BIDDING: 40,
|
||||
AWARDING: 50,
|
||||
FINALIZING: 60,
|
||||
};
|
||||
|
||||
export const DraftPhases = [
|
||||
"waiting",
|
||||
"determine_order",
|
||||
"nomination",
|
||||
"bidding",
|
||||
"award",
|
||||
"finalize",
|
||||
]
|
||||
export const DraftPhaseLabel = {
|
||||
[DraftPhase.WAITING]: "waiting",
|
||||
[DraftPhase.DETERMINE_ORDER]: "determine_order",
|
||||
[DraftPhase.NOMINATING]: "nominating",
|
||||
[DraftPhase.BIDDING]: "bidding",
|
||||
[DraftPhase.AWARDING]: "awarding",
|
||||
[DraftPhase.FINALIZING]: "finalizing",
|
||||
};
|
||||
|
||||
export const DraftPhasesOrdered = [DraftPhase.WAITING, DraftPhase.DETERMINE_ORDER, DraftPhase.NOMINATING, DraftPhase.BIDDING, DraftPhase.AWARDING, DraftPhase.FINALIZING];
|
||||
|
||||
@@ -3,29 +3,18 @@ import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useWebSocket } from "../WebSocketContext.jsx";
|
||||
import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
|
||||
import { DraftMessage, DraftPhases } from '../constants.js';
|
||||
import { fetchDraftDetails } from "../common/utils.js"
|
||||
import { DraftMessage, DraftPhases} from '../constants.js';
|
||||
import { fetchDraftDetails } from "../common/utils.js";
|
||||
import { DraftMoviePool } from "../common/DraftMoviePool.jsx";
|
||||
import { ParticipantList } from "../common/ParticipantList.jsx";
|
||||
import { handleDraftStatusMessages } from '../common/utils.js'
|
||||
|
||||
const DraftMoviePool = ({ movies }) => {
|
||||
return (
|
||||
<div className="movie-pool-container">
|
||||
<ul>
|
||||
{movies.map(m => (
|
||||
<li id={m?.id}>
|
||||
<a href={`/api/movie/${m.id}/detail`}>
|
||||
{m.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const DraftParticipant = ({ draftSessionId }) => {
|
||||
const socket = useWebSocket();
|
||||
const [participants, setParticipants] = useState([]);
|
||||
const [draftPhase, setDraftPhase] = useState();
|
||||
const [draftState, setDraftState] = useState({});
|
||||
const [draftDetails, setDraftDetails] = useState({});
|
||||
|
||||
const [movies, setMovies] = useState([]);
|
||||
console.log(socket)
|
||||
|
||||
@@ -34,65 +23,41 @@ export const DraftParticipant = ({ draftSessionId }) => {
|
||||
.then((data) => {
|
||||
console.log("Fetched draft data", data)
|
||||
setMovies(data.movies)
|
||||
setDraftDetails(data)
|
||||
})
|
||||
}, [])
|
||||
}, [draftSessionId])
|
||||
|
||||
useEffect(()=>{
|
||||
if (!socket) return;
|
||||
socket.onclose = (event) => {
|
||||
console.log('Websocket Closed')
|
||||
}
|
||||
}, [socket])
|
||||
|
||||
useEffect(() => {
|
||||
if (!socket) return;
|
||||
else {
|
||||
console.warn("socket doesn't exist")
|
||||
}
|
||||
console.log('socket created', socket)
|
||||
|
||||
const handleMessage = (event) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const { type, payload } = message;
|
||||
console.log(type, event)
|
||||
if (type == DraftMessage.REQUEST.JOIN_PARTICIPANT) {
|
||||
console.log('join request', data)
|
||||
}
|
||||
else if (type == DraftMessage.CONFIRM.JOIN_PARTICIPANT) {
|
||||
setConnectedParticipants(data.connected_participants)
|
||||
}
|
||||
else if (type == DraftMessage.CONFIRM.PHASE_CHANGE || type == DraftMessage.INFORM.PHASE) {
|
||||
console.log('phase_change')
|
||||
setDraftPhase(payload.phase)
|
||||
}
|
||||
}
|
||||
|
||||
socket.addEventListener('message', handleMessage);
|
||||
|
||||
socket.onclose = (event) => {
|
||||
console.log('Websocket Closed')
|
||||
socket = null;
|
||||
}
|
||||
const handler = (event) => handleDraftStatusMessages(event, setDraftState)
|
||||
socket.addEventListener('message', handler );
|
||||
|
||||
return () => {
|
||||
socket.removeEventListener('message', handleMessage)
|
||||
socket.addEventListener('message', handler );
|
||||
socket.close();
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const handlePhaseChange = (destinationPhase) => {
|
||||
socket.send(
|
||||
JSON.stringify({ type: DraftMessage.REQUEST.PHASE_CHANGE, "destination": destinationPhase })
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const handleRequestDraftSummary = () => {
|
||||
socket.send(JSON.stringify({ type: 'request_summary' }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container draft-panel">
|
||||
<div className="d-flex justify-content-between border-bottom mb-2 p-1">
|
||||
<h3>Draft Panel</h3>
|
||||
<WebSocketStatus socket={socket} />
|
||||
</div>
|
||||
|
||||
<DraftMoviePool movies={movies}></DraftMoviePool>
|
||||
<ParticipantList
|
||||
draftState={draftState}
|
||||
draftDetails={draftDetails}
|
||||
isAdmin={false}
|
||||
/>
|
||||
<DraftMoviePool draftDetails={draftDetails}></DraftMoviePool>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user