Add live bidding UI and backend support; integrate react-bootstrap
- 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.
This commit is contained in:
@@ -32,6 +32,7 @@ class DraftMessage(StrEnum):
|
||||
BID_START_INFORM = "bid.start.inform" # server -> client (movie, ends_at)
|
||||
BID_START_REQUEST = "bid.start.request" # server -> client (movie, ends_at)
|
||||
BID_PLACE_REQUEST = "bid.place.request" # client -> server (amount)
|
||||
BID_PLACE_CONFIRM = "bid.update.confirm" # server -> client (high bid)
|
||||
BID_UPDATE_INFORM = "bid.update.inform" # server -> client (high bid)
|
||||
BID_END_INFORM = "bid.end.inform" # server -> client (winner)
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
return self.user.is_authenticated
|
||||
|
||||
async def receive_json(self, content):
|
||||
logger.info(f"receiving message {content}")
|
||||
event_type = content.get("type")
|
||||
if event_type == DraftMessage.STATUS_SYNC_REQUEST:
|
||||
await self.send_json(
|
||||
@@ -206,7 +207,7 @@ class DraftAdminConsumer(DraftConsumerBase):
|
||||
{
|
||||
"type": "broadcast.session",
|
||||
"subtype": DraftMessage.BID_START_INFORM,
|
||||
"payload": self.get_draft_status(),
|
||||
"payload": {**self.draft_state},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -314,6 +315,18 @@ class DraftParticipantConsumer(DraftConsumerBase):
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if event_type == DraftMessage.BID_PLACE_REQUEST:
|
||||
bid_amount = content.get('payload',{}).get('bid_amount')
|
||||
self.draft_state.place_bid(self.user, bid_amount)
|
||||
await self.channel_layer.group_send(
|
||||
self.group_names.session,
|
||||
{
|
||||
"type": "broadcast.session",
|
||||
"subtype": DraftMessage.BID_PLACE_CONFIRM,
|
||||
"payload": {**self.draft_state},
|
||||
},
|
||||
)
|
||||
|
||||
# === Broadcast handlers ===
|
||||
|
||||
|
||||
@@ -181,13 +181,15 @@ class DraftStateManager:
|
||||
self.cache.set(self.cache_keys.current_movie, movie_id)
|
||||
self.cache.delete(self.cache_keys.bids)
|
||||
|
||||
def place_bid(self, user_id: int, amount: int):
|
||||
def place_bid(self, user: User, amount: int|str):
|
||||
if isinstance(amount, str):
|
||||
amount = int(amount)
|
||||
bids = self.get_bids()
|
||||
bids[user_id] = amount
|
||||
bids.append({"user":user.username, "amount":amount})
|
||||
self.cache.set(self.cache_keys.bids, json.dumps(bids))
|
||||
|
||||
def get_bids(self) -> dict:
|
||||
return json.loads(self.cache.get(self.cache_keys.bids) or "{}")
|
||||
return json.loads(self.cache.get(self.cache_keys.bids) or "[]")
|
||||
|
||||
def current_movie(self) -> Movie | None:
|
||||
movie_id = self.cache.get(self.cache_keys.current_movie)
|
||||
@@ -216,7 +218,7 @@ class DraftStateManager:
|
||||
"draft_index": self.draft_index,
|
||||
"connected_participants": self.connected_participants,
|
||||
"current_movie": self.cache.get(self.cache_keys.current_movie),
|
||||
# "bids": self.get_bids(),
|
||||
"bids": self.get_bids(),
|
||||
"bidding_timer_end": self.get_timer_end(),
|
||||
"bidding_timer_start": self.get_timer_start(),
|
||||
"current_pick": picks[0] if picks else None,
|
||||
|
||||
Reference in New Issue
Block a user