Private
Public Access
1
0

new: usr: add timer for the acceptance of the challenge #4

This commit is contained in:
2026-04-14 20:30:18 +02:00
parent 3525aaeeb7
commit b134358e9e
5 changed files with 143 additions and 24 deletions

View File

@@ -47,6 +47,7 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
const [snapshotLoaded, setSnapshotLoaded] = useState(false);
const [challengingGameAssoc, setChallengingGameAssoc] = useState(null);
const [declinedMsg, setDeclinedMsg] = useState('');
const [waitingCountdown, setWaitingCountdown] = useState(0);
const declinedTimerRef = useRef(null);
const addPlayer = useCallback(entry => {
@@ -111,6 +112,7 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
setChallengingGameAssoc(null);
clearTimeout(declinedTimerRef.current);
setDeclinedMsg('Challenge was not accepted.');
setWaitingCountdown(0);
declinedTimerRef.current = setTimeout(() => setDeclinedMsg(''), 3500);
};
window.addEventListener('challenge-declined', handler);
@@ -120,15 +122,30 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
};
}, []);
useEffect(() => {
if (!waitingCountdown) return;
const interval = setInterval(() => {
setWaitingCountdown(prev => {
if (1 >= prev) return 0;
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
}, [waitingCountdown]);
const handleChallenge = player => {
if (challengingGameAssoc) return;
setChallengingGameAssoc(player.gameAssoc);
setDeclinedMsg('');
setWaitingCountdown(30);
fetch('/api/game/challenge/' + player.gameAssoc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ challengerGameAssoc: currentGameAssoc }),
}).catch(() => setChallengingGameAssoc(null));
}).catch(() => {
setChallengingGameAssoc(null);
setWaitingCountdown(0);
});
};
const visible = players
@@ -147,7 +164,12 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
}
return (
<Dialog open={open} onClose={onClose} sx={DIALOG_SX}>
<Dialog
open={open}
onClose={waitingCountdown > 0 ? undefined : onClose}
disableEscapeKeyDown={waitingCountdown > 0}
sx={DIALOG_SX}
>
<div className="opd">
<div className="opd-header">
<div className="opd-header-text">
@@ -160,32 +182,44 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
<div className="opd-header-actions">
<button
className={`opd-refresh${loading ? ' opd-refresh--spin' : ''}`}
onClick={() => setRefreshKey(k => k + 1)}
disabled={loading}
onClick={() => { if (waitingCountdown === 0) setRefreshKey(k => k + 1); }}
disabled={loading || waitingCountdown > 0}
aria-label="Refresh"
title="Refresh list"
>
<i className="fa fa-refresh" />
</button>
<button className="opd-close" onClick={onClose} aria-label="Close">
<button
className="opd-close"
onClick={() => { if (waitingCountdown === 0) onClose(); }}
disabled={waitingCountdown > 0}
aria-label="Close"
>
<i className="fa fa-times" />
</button>
</div>
</div>
<div className="opd-search-wrap">
<i className="fa fa-search opd-search-icon" />
<input
className="opd-search"
placeholder="Search by username…"
value={search}
onChange={e => setSearch(e.target.value)}
/>
{search && (
<button className="opd-search-clear" onClick={() => setSearch('')}>
<i className="fa fa-times" />
</button>
)}
</div>
{0 < waitingCountdown ? (
<div className="opd-waiting">
<i className="fa fa-hourglass-start" />
<p>Waiting {waitingCountdown} second{1 === waitingCountdown ? '' : 's'} for opponent's answer...</p>
</div>
) : (
<div className="opd-search-wrap">
<i className="fa fa-search opd-search-icon" />
<input
className="opd-search"
placeholder="Search by username…"
value={search}
onChange={e => setSearch(e.target.value)}
/>
{search && (
<button className="opd-search-clear" onClick={() => setSearch('')}>
<i className="fa fa-times" />
</button>
)}
</div>
)}
<div className="opd-list">
{loading && (
<div className="opd-empty">