Add DRF API app and real-time draft management UI
- Created new `api` Django app with serializers, viewsets, and routers to expose draft sessions, participants, and movie data. - Registered `api` app in settings and updated root URL configuration. - Extended WebSocket consumers with `inform.draft_status` / `request.draft_status` to allow fetching current draft state. - Updated `DraftSession` and related models to support reverse lookups for draft picks. - Enhanced draft state manager to include `draft_order` in summaries. - Added React WebSocket context provider, connection status component, and new admin/participant panels with phase and participant tracking. - Updated SCSS for participant lists, phase indicators, and status badges. - Modified Django templates to mount new React roots for admin and participant views. - Updated Webpack dev server config to proxy WebSocket connections.
This commit is contained in:
16
frontend/src/apps/draft/WebSocketContext.jsx
Normal file
16
frontend/src/apps/draft/WebSocketContext.jsx
Normal file
@@ -0,0 +1,16 @@
|
||||
// WebSocketContext.jsx
|
||||
import React, { useState, createContext, useContext, useRef, useEffect } from "react";
|
||||
|
||||
const WebSocketContext = createContext(null);
|
||||
|
||||
export const WebSocketProvider = ({ url, children }) => {
|
||||
const [socket] = useState(() => new WebSocket(url));
|
||||
|
||||
return (
|
||||
<WebSocketContext.Provider value={socket}>
|
||||
{children}
|
||||
</WebSocketContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useWebSocket = () => useContext(WebSocketContext);
|
||||
198
frontend/src/apps/draft/admin/DraftAdmin.jsx
Normal file
198
frontend/src/apps/draft/admin/DraftAdmin.jsx
Normal file
@@ -0,0 +1,198 @@
|
||||
// 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';
|
||||
|
||||
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
|
||||
|
||||
|
||||
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 }) => {
|
||||
return (
|
||||
<div className="draft-phase-container">
|
||||
<label>Phase</label>
|
||||
<ol>
|
||||
{
|
||||
DraftPhases.map((p) => (
|
||||
<li key={p} className={p === draftPhase ? "current-phase" : ""}>
|
||||
<span>{p}</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const DraftOrder = ({ socket, draftOrder }) => {
|
||||
console.log("in component", draftOrder)
|
||||
return (
|
||||
<div>
|
||||
<label>Draft Order</label>
|
||||
<ol>
|
||||
{
|
||||
draftOrder.map((p) => (
|
||||
<li key={p}>
|
||||
{p}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchDraftDetails(draftSessionId) {
|
||||
fetch(`/api/draft/${draftSessionId}/`)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
}
|
||||
else {
|
||||
throw new Error()
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
setParticipants(data.participants)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error fetching draft details", err)
|
||||
})
|
||||
}
|
||||
fetchDraftDetails(draftSessionId)
|
||||
}, [])
|
||||
|
||||
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 (!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;
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket.removeEventListener('message', handleMessage)
|
||||
socket.close();
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const handlePhaseChange = (destinationPhase) => {
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.REQUEST.PHASE_CHANGE, "origin": draftPhase, "destination": destinationPhase }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const handleRequestDraftSummary = () => {
|
||||
socket.send(
|
||||
JSON.stringify(
|
||||
{ type: DraftMessage.REQUEST.DRAFT_STATUS }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="container draft-panel admin">
|
||||
<h3>Draft Admin Panel</h3>
|
||||
<WebSocketStatus socket={socket} />
|
||||
{/* <MessageLogger socket={socketRef.current} /> */}
|
||||
|
||||
<ParticipantList
|
||||
socket={socket}
|
||||
participants={participants}
|
||||
draftOrder={draftOrder}
|
||||
/>
|
||||
<DraftPhaseDisplay draftPhase={draftPhase}></DraftPhaseDisplay>
|
||||
<button onClick={() => handlePhaseChange(DraftPhase.DETERMINE_ORDER)} className="btn btn-primary mt-2 me-2">
|
||||
Determine Draft Order
|
||||
</button>
|
||||
<button onClick={() => handleRequestDraftSummary()} className="btn btn-primary mt-2">
|
||||
Request status
|
||||
</button>
|
||||
<button onClick={() => handlePhaseChange(DraftPhase.NOMINATION)} className="btn btn-primary mt-2 me-2">
|
||||
Go to Nominate
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
34
frontend/src/apps/draft/common/WebSocketStatus.jsx
Normal file
34
frontend/src/apps/draft/common/WebSocketStatus.jsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
export const WebSocketStatus = ({ socket }) => {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('socket changed', socket)
|
||||
if (!socket) return;
|
||||
|
||||
const handleOpen = () => { console.log('socket open'); setIsConnected(true) };
|
||||
const handleClose = () => { console.log('socket close'); setIsConnected(false) };
|
||||
const handleError = () => { console.log('socket error'); setIsConnected(false) };
|
||||
|
||||
socket.addEventListener("open", handleOpen);
|
||||
socket.addEventListener("close", handleClose);
|
||||
socket.addEventListener("error", handleError);
|
||||
|
||||
// 🧹 Cleanup to remove listeners when component unmounts or socket changes
|
||||
return () => {
|
||||
socket.removeEventListener("open", handleOpen);
|
||||
socket.removeEventListener("close", handleClose);
|
||||
socket.removeEventListener("error", handleError);
|
||||
};
|
||||
|
||||
}, [socket])
|
||||
return (
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<span
|
||||
className={`badge ${isConnected ? "text-bg-success" : "text-bg-danger"}`}
|
||||
>
|
||||
{isConnected ? "Connected" : "Disconnected"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,8 +2,10 @@ 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
|
||||
@@ -13,6 +15,7 @@ export const DraftMessage = {
|
||||
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)
|
||||
@@ -36,4 +39,13 @@ export const DraftPhase = {
|
||||
BIDDING: 30,
|
||||
AWARD: 40,
|
||||
FINALIZE: 50,
|
||||
}
|
||||
}
|
||||
|
||||
export const DraftPhases = [
|
||||
"waiting",
|
||||
"determine_order",
|
||||
"nomination",
|
||||
"bidding",
|
||||
"award",
|
||||
"finalize",
|
||||
]
|
||||
@@ -1,21 +1,61 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import { DraftMessage, DraftPhase } from './constants.js';
|
||||
import React, { createContext, useContext, useEffect, useState, useRef } from "react";
|
||||
import { DraftMessage, DraftPhases } from './constants.js';
|
||||
|
||||
|
||||
const WebSocketContext = createContext(null);
|
||||
|
||||
export const WebSocketProvider = ({ url, children }) => {
|
||||
const socketRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!socketRef.current) {
|
||||
socketRef.current = new WebSocket(url);
|
||||
}
|
||||
|
||||
return () => {
|
||||
socketRef.current?.close();
|
||||
socketRef.current = null;
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
return (
|
||||
<WebSocketContext.Provider value={socketRef.current}>
|
||||
{children}
|
||||
</WebSocketContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useWebSocket = () => {
|
||||
return useContext(WebSocketContext);
|
||||
};
|
||||
|
||||
export const WebSocketStatus = ({ socket }) => {
|
||||
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('socket changed', socket)
|
||||
if (!socket) return;
|
||||
|
||||
if (!socket) return;
|
||||
const handleOpen = () => {console.log('socket open'); setIsConnected(true)};
|
||||
const handleClose = () => setIsConnected(false);
|
||||
const handleError = () => setIsConnected(false);
|
||||
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
setIsConnected(true);
|
||||
}
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
console.log('socket already connected')
|
||||
setIsConnected(true);
|
||||
}
|
||||
|
||||
socket.addEventListener("open", handleOpen);
|
||||
socket.addEventListener("close", handleClose);
|
||||
socket.addEventListener("error", handleError);
|
||||
|
||||
// 🧹 Cleanup to remove listeners when component unmounts or socket changes
|
||||
return () => {
|
||||
socket.removeEventListener("open", handleOpen);
|
||||
socket.removeEventListener("close", handleClose);
|
||||
socket.removeEventListener("error", handleError);
|
||||
};
|
||||
|
||||
socket.addEventListener("open", () => setIsConnected(true));
|
||||
socket.addEventListener("close", () => setIsConnected(false));
|
||||
socket.addEventListener("error", () => setIsConnected(false));
|
||||
}, [socket])
|
||||
return (
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
@@ -49,6 +89,7 @@ export const MessageLogger = ({ socket }) => {
|
||||
socket.addEventListener("message", handleMessage);
|
||||
|
||||
return () => {
|
||||
console.log('removing event listeners')
|
||||
socket.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, [socket]);
|
||||
@@ -56,7 +97,7 @@ export const MessageLogger = ({ socket }) => {
|
||||
useEffect(() => {
|
||||
// Scroll to bottom when messages update
|
||||
if (bottomRef.current) {
|
||||
bottomRef.current.scrollIntoView({ behavior: "smooth" });
|
||||
bottomRef.current.scrollIntoView({ behavior: "smooth" , block: 'nearest', inline: 'start'});
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
@@ -77,21 +118,21 @@ export const MessageLogger = ({ socket }) => {
|
||||
};
|
||||
|
||||
export const DraftAdmin = ({ draftSessionId }) => {
|
||||
const [latestMessage, setLatestMessage] = useState(null);
|
||||
const [connectedParticipants, setConnectedParticipants] = useState([]);
|
||||
const [draftPhase, setDraftPhase] = useState();
|
||||
|
||||
const socketRef = useRef(null);
|
||||
const socketRef = useWebSocket();
|
||||
const wsUrl = `ws://${window.location.host}/ws/draft/session/${draftSessionId}/admin`;
|
||||
|
||||
useEffect(() => {
|
||||
if (socketRef.current) return;
|
||||
console.log('socket created')
|
||||
socketRef.current = new WebSocket(wsUrl);
|
||||
|
||||
socketRef.current.onmessage = (event) => {
|
||||
const message = JSON.parse(event.data)
|
||||
const { type, payload } = message;
|
||||
console.log(type, event)
|
||||
setLatestMessage(message);
|
||||
if (type == DraftMessage.REQUEST.JOIN_PARTICIPANT) {
|
||||
console.log('join request', data)
|
||||
}
|
||||
@@ -124,10 +165,11 @@ export const DraftAdmin = ({ draftSessionId }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<WebSocketContext url={wsUrl}>
|
||||
<div className="container draft-panel">
|
||||
<h3>Draft Admin Panel</h3>
|
||||
<WebSocketStatus socket={socketRef.current} />
|
||||
<MessageLogger socket={socketRef.current}></MessageLogger>
|
||||
<WebSocketStatus socket={socket} />
|
||||
{/* <MessageLogger socket={socketRef.current} /> */}
|
||||
<label>Connected Particpants</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -147,11 +189,11 @@ export const DraftAdmin = ({ draftSessionId }) => {
|
||||
Request status
|
||||
</button>
|
||||
</div>
|
||||
</WebSocketContext>
|
||||
);
|
||||
};
|
||||
|
||||
export const DraftParticipant = ({ draftSessionId }) => {
|
||||
const [latestMessage, setLatestMessage] = useState(null);
|
||||
const socketRef = useRef(null);
|
||||
const wsUrl = `ws://${window.location.host}/ws/draft/session/${draftSessionId}/participant`;
|
||||
|
||||
@@ -161,7 +203,6 @@ export const DraftParticipant = ({ draftSessionId }) => {
|
||||
socketRef.current.onmessage = (evt) => {
|
||||
const data = JSON.parse(evt.data);
|
||||
console.log(data)
|
||||
setLatestMessage(data);
|
||||
};
|
||||
|
||||
socketRef.current.onclose = () => {
|
||||
@@ -183,11 +224,7 @@ export const DraftParticipant = ({ draftSessionId }) => {
|
||||
<h3 >Draft Participant Panel</h3>
|
||||
<WebSocketStatus socket={socketRef.current} />
|
||||
<label>Latest Message</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly disabled
|
||||
value={latestMessage ? JSON.stringify(latestMessage) : ""}
|
||||
/>
|
||||
<MessageLogger socket={socketRef.current}></MessageLogger>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
86
frontend/src/apps/draft/participant/DraftParticipant.jsx
Normal file
86
frontend/src/apps/draft/participant/DraftParticipant.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
// DraftAdmin.jsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { useWebSocket } from "../WebSocketContext.jsx";
|
||||
import { WebSocketStatus } from "../common/WebSocketStatus.jsx";
|
||||
import { DraftMessage, DraftPhases } from '../constants.js';
|
||||
|
||||
export const DraftParticipant = ({ draftSessionId }) => {
|
||||
const socket = useWebSocket();
|
||||
const [connectedParticipants, setConnectedParticipants] = useState([]);
|
||||
const [draftPhase, setDraftPhase] = useState();
|
||||
console.log(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;
|
||||
}
|
||||
|
||||
return () => {
|
||||
socket.removeEventListener('message', handleMessage)
|
||||
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">
|
||||
<h3>Draft Admin Panel</h3>
|
||||
<WebSocketStatus socket={socket} />
|
||||
{/* <MessageLogger socket={socketRef.current} /> */}
|
||||
<label>Connected Particpants</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly disabled
|
||||
value={connectedParticipants ? JSON.stringify(connectedParticipants) : ""}
|
||||
/>
|
||||
<label>Draft Phase</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly disabled
|
||||
value={draftPhase ? draftPhase : ""}
|
||||
/>
|
||||
<button onClick={() => handlePhaseChange(DraftPhases.DETERMINE_ORDER)} className="btn btn-primary mt-2 me-2">
|
||||
Determine Draft Order
|
||||
</button>
|
||||
<button onClick={handleRequestDraftSummary} className="btn btn-primary mt-2">
|
||||
Request status
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +1,29 @@
|
||||
import './scss/styles.scss'
|
||||
console.log("Webpack HMR loaded!");
|
||||
|
||||
import React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import {DraftAdmin, DraftParticipant} from './apps/draft/index.jsx'
|
||||
import { WebSocketProvider } from "./apps/draft/WebSocketContext.jsx";
|
||||
import { DraftAdmin } from "./apps/draft/admin/DraftAdmin.jsx";
|
||||
import { DraftParticipant} from './apps/draft/participant/DraftParticipant.jsx'
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const draftAdminApp = document.getElementById("draft-admin-app");
|
||||
const draftApp = document.getElementById("draft-app")
|
||||
if (draftApp) {
|
||||
const root = createRoot(draftApp);
|
||||
const draftId = draftApp.dataset.draftId
|
||||
root.render(<DraftParticipant draftSessionId={draftId} />);
|
||||
|
||||
}
|
||||
if (draftAdminApp) {
|
||||
const root = createRoot(draftAdminApp);
|
||||
const draftId = draftAdminApp.dataset.draftId
|
||||
root.render(<DraftAdmin draftSessionId={draftId} />);
|
||||
}
|
||||
});
|
||||
const draftAdminRoot = document.getElementById("draft-admin-root");
|
||||
const draftPartipantRoot = document.getElementById("draft-participant-root")
|
||||
const {draftSessionId} = window; // from backend template
|
||||
|
||||
if (draftPartipantRoot) {
|
||||
const wsUrl = `ws://${window.location.host}/ws/draft/session/${draftSessionId}/participant`;
|
||||
createRoot(draftPartipantRoot).render(
|
||||
<WebSocketProvider url={wsUrl}>
|
||||
<DraftParticipant />
|
||||
</WebSocketProvider>
|
||||
);
|
||||
}
|
||||
if (draftAdminRoot) {
|
||||
const wsUrl = `ws://${window.location.host}/ws/draft/session/${draftSessionId}/admin`;
|
||||
createRoot(draftAdminRoot).render(
|
||||
<WebSocketProvider url={wsUrl}>
|
||||
<DraftAdmin draftSessionId={draftSessionId}/>
|
||||
</WebSocketProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,3 +37,66 @@
|
||||
padding: 1em;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.stop-light {
|
||||
@extend .me-2;
|
||||
// @extend .badge;
|
||||
// @extend .rounded-pill;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.success {
|
||||
@extend .bg-success;
|
||||
}
|
||||
|
||||
.danger {
|
||||
@extend .bg-danger;
|
||||
}
|
||||
|
||||
.draft-phase-container {
|
||||
label {
|
||||
@extend .fs-3;
|
||||
}
|
||||
ol, ul {
|
||||
@extend .list-group;
|
||||
@extend .list-group-horizontal;
|
||||
li {
|
||||
@extend .list-group-item;
|
||||
@extend .p-1;
|
||||
@extend .ps-2;
|
||||
@extend .pe-2;
|
||||
|
||||
&.current-phase {
|
||||
@extend .active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.participant-list-container {
|
||||
max-width: 575.98px;
|
||||
label {
|
||||
@extend .fs-3;
|
||||
}
|
||||
@extend .list-group;
|
||||
ol {
|
||||
@extend .list-group-numbered;
|
||||
}
|
||||
li {
|
||||
@extend .list-group-item;
|
||||
@extend .d-flex;
|
||||
@extend .justify-content-between;
|
||||
@extend .align-items-center;
|
||||
span {
|
||||
@extend .me-auto;
|
||||
@extend .ps-1;
|
||||
}
|
||||
&::marker{
|
||||
content:">";
|
||||
color:green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,13 @@ module.exports = {
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
{
|
||||
context: (pathname) => pathname.startsWith('/ws/'),
|
||||
target: 'ws://localhost:8000',
|
||||
ws: true, // <-- enable websocket proxying
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
ignoreWarnings: [
|
||||
|
||||
Reference in New Issue
Block a user