- Added 'react-bootstrap' to frontend dependencies for improved UI components.
- Updated bid placement mechanics: backend now stores bids as a list of {user, amount}; frontend displays live bid leaderboard, including highest bid.
- Implemented bid placement form and UI in participant draft screen.
- Used React-Bootstrap Collapse for nominee menu accordion behavior.
- Expanded DraftStateManager and websocket consumers to broadcast bid updates in the new format.
- Added missing 'bids' syncing to all relevant state handling code.
- Improved styling for bidding, panel headers, and pick lists in SCSS; leveraged Bootstrap variables/utilities more extensively.
- Other minor JS, Python, and style tweaks for better stability and robustness.
236 lines
8.0 KiB
JavaScript
236 lines
8.0 KiB
JavaScript
// DraftAdmin.jsx
|
|
import React, { useEffect, useState, useRef } from "react";
|
|
|
|
import { useWebSocket } from "../common/WebSocketContext.jsx";
|
|
import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
|
|
import { DraftMessage, DraftPhaseLabel, DraftPhases } from '../constants.js';
|
|
import { fetchDraftDetails, handleUserIdentifyMessages, isEmptyObject } from "../common/utils.js";
|
|
import { DraftMoviePool } from "../common/DraftMoviePool.jsx";
|
|
import { ParticipantList } from "../common/ParticipantList.jsx";
|
|
import { DraftCountdownClock } from "../common/DraftCountdownClock.jsx"
|
|
import { handleDraftStatusMessages } from '../common/utils.js'
|
|
// import { Collapse } from 'bootstrap/dist/js/bootstrap.bundle.min.js';
|
|
import { Collapse, ListGroup } from "react-bootstrap";
|
|
|
|
|
|
const NominateMenu = ({ socket, draftState, draftDetails, currentUser, }) => {
|
|
if (!socket || isEmptyObject(draftDetails) || isEmptyObject(draftState)) return;
|
|
const [open, setOpen] = useState(false);
|
|
const { movies } = draftDetails
|
|
|
|
const requestNomination = (event) => {
|
|
event.preventDefault()
|
|
const formData = new FormData(event.target)
|
|
socket.send(JSON.stringify({
|
|
type: DraftMessage.NOMINATION_SUBMIT_REQUEST,
|
|
payload: {
|
|
id: formData.get('movie'),
|
|
user: currentUser
|
|
}
|
|
}))
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (isEmptyObject(draftState) || isEmptyObject(draftState.current_pick)) return;
|
|
|
|
if (currentUser == draftState.current_pick.participant) {
|
|
setOpen(true)
|
|
} else {
|
|
setOpen(false)
|
|
}
|
|
|
|
// collapse.toggle()
|
|
}, [draftState])
|
|
|
|
return (
|
|
<Collapse in={open} className="nominate-menu">
|
|
<div> {/* Everything must be wrapped in one parent */}
|
|
<label>Nominate</label>
|
|
<div className="d-flex">
|
|
<form onSubmit={requestNomination}>
|
|
<select className="form-control" name="movie">
|
|
{movies.map(m => (
|
|
<option key={m.id} value={m.id}>{m.title}</option>
|
|
))}
|
|
</select>
|
|
<button className="btn btn-primary">Nominate</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</Collapse>
|
|
);
|
|
}
|
|
|
|
export const DraftParticipant = ({ draftSessionId }) => {
|
|
const socket = useWebSocket();
|
|
const [draftState, setDraftState] = useState({});
|
|
const [draftDetails, setDraftDetails] = useState({});
|
|
const [currentUser, setCurrentUser] = useState(null);
|
|
|
|
const [movies, setMovies] = useState([]);
|
|
console.log(socket)
|
|
|
|
useEffect(() => {
|
|
fetchDraftDetails(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;
|
|
|
|
const draftStatusMessageHandler = (event) => handleDraftStatusMessages(event, setDraftState)
|
|
const userIdentifyMessageHandler = (event) => handleUserIdentifyMessages(event, setCurrentUser)
|
|
socket.addEventListener('message', draftStatusMessageHandler);
|
|
socket.addEventListener('message', userIdentifyMessageHandler);
|
|
|
|
return () => {
|
|
socket.removeEventListener('message', draftStatusMessageHandler);
|
|
socket.removeEventListener('message', userIdentifyMessageHandler);
|
|
};
|
|
}, [socket]);
|
|
|
|
function submitBidRequest(event) {
|
|
event.preventDefault()
|
|
const form = event.target
|
|
const formData = new FormData(form)
|
|
console.log('submitting bid...')
|
|
socket.send(JSON.stringify({
|
|
type: DraftMessage.BID_PLACE_REQUEST,
|
|
payload: {
|
|
bid_amount: formData.get('bidAmount'),
|
|
user: currentUser
|
|
}
|
|
}))
|
|
}
|
|
|
|
return (
|
|
<div className="wrapper">
|
|
<section className="panel draft-live">
|
|
<header className="panel-header">
|
|
<h2 className="panel-title">Draft Live</h2>
|
|
<div className="d-flex gap-1">
|
|
<div className="phase-indicator badge bg-primary">{DraftPhaseLabel[draftState.phase]}</div>
|
|
<WebSocketStatus socket={socket} />
|
|
</div>
|
|
</header>
|
|
<div className="panel-body">
|
|
<div className="draft-live-state-container">
|
|
<DraftCountdownClock endTime={draftState.bidding_timer_end}></DraftCountdownClock>
|
|
<div className="pick-description">
|
|
{console.log("draft_state", draftState)}
|
|
<div>Round {draftState.current_pick?.round}</div>
|
|
<div>Pick {draftState.current_pick?.pick_in_round}</div>
|
|
<div>{draftState.current_pick?.overall + 1} Overall</div>
|
|
</div>
|
|
</div>
|
|
<div className="bid-status">
|
|
<div className="d-flex">
|
|
<div className="flex-grow-1 text-center">
|
|
{draftState.bids?.length > 0 ? Math.max(draftState.bids?.map(i=>i.bid_amount)) : ""}
|
|
</div>
|
|
<div className="flex-grow-1 text-center">
|
|
highest bid
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<ol className="bid-list">
|
|
{draftState.bids?.map((bid, idx) => (
|
|
<li key={idx}>{bid.user}: {bid.amount}</li>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
|
|
</div>
|
|
<div className="bid-controls btn-group d-flex flex-column">
|
|
<form id="bid" onSubmit={submitBidRequest}>
|
|
<div className="d-flex">
|
|
<div className="flex-grow-1 text-center">
|
|
<input type="number" id="bidAmount" name="bidAmount"></input>
|
|
</div>
|
|
<div className="flex-grow-1 text-center">
|
|
<button className="flex-grow-1">Submit</button>
|
|
</div>
|
|
</div>
|
|
<div className="d-flex">
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div>
|
|
<ul className="pick-list">
|
|
<li>
|
|
<div>Current Pick: {draftState.current_pick?.participant}</div>
|
|
</li>
|
|
<li
|
|
>
|
|
<div>Next Pick: {draftState.next_picks ? draftState.next_picks[0]?.participant : ""}</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
<section className="panel draft-catalog">
|
|
<header className="panel-header">
|
|
<h2 className="panel-title">Draft Catalog</h2>
|
|
</header>
|
|
<div className="panel-body">
|
|
<div className="current-movie card">
|
|
<span>Current Nomination: {movies.find(i => draftState.current_movie == i.id)?.title}</span>
|
|
</div>
|
|
<NominateMenu socket={socket} currentUser={currentUser} draftState={draftState} draftDetails={draftDetails}></NominateMenu>
|
|
<div className="movie-filters"></div>
|
|
|
|
<DraftMoviePool isParticipant={true} draftDetails={draftDetails} draftState={draftState}></DraftMoviePool>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<section className="panel my-team">
|
|
<header className="panel-header">
|
|
<h2 className="panel-title">My Team</h2>
|
|
</header>
|
|
<div className="panel-body">
|
|
<ul className="team-movie-list list-group">
|
|
<li className="team-movie-item list-group-item"></li>
|
|
</ul>
|
|
<div className="budget-status"></div>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<section className="panel teams">
|
|
<header className="panel-header">
|
|
<h2 className="panel-title">Teams</h2>
|
|
</header>
|
|
<div className="panel-body">
|
|
<ParticipantList
|
|
currentUser={currentUser}
|
|
draftState={draftState}
|
|
draftDetails={draftDetails}
|
|
/>
|
|
<ul className="team-list list-group">
|
|
<li className="team-item list-group-item">
|
|
<div className="team-name fw-bold"></div>
|
|
<ul className="team-movie-list list-group list-group-flush">
|
|
<li className="team-movie-item list-group-item"></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
);
|
|
}; |