Private
Public Access
1
0

chg: dev: increase the minimum PHP version to the latest major - and massive refactor on back-end, like Controllers and Repositories #4

This commit is contained in:
2026-04-12 08:01:46 +02:00
parent 92bfa5b301
commit c0dcc2896a
12 changed files with 511 additions and 104 deletions

View File

@@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import Dialog from '@mui/material/Dialog';
const DIALOG_SX = {
@@ -45,6 +45,9 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
const [loading, setLoading] = useState(false);
const [refreshKey, setRefreshKey] = useState(0);
const [snapshotLoaded, setSnapshotLoaded] = useState(false);
const [challengingGameAssoc, setChallengingGameAssoc] = useState(null);
const [declinedMsg, setDeclinedMsg] = useState('');
const declinedTimerRef = useRef(null);
const addPlayer = useCallback(entry => {
setPlayers(prev =>
@@ -103,6 +106,31 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
return () => es.close();
}, [open, snapshotLoaded, addPlayer, removePlayer, currentGameAssoc]);
useEffect(() => {
const handler = () => {
setChallengingGameAssoc(null);
clearTimeout(declinedTimerRef.current);
setDeclinedMsg('Challenge was not accepted.');
declinedTimerRef.current = setTimeout(() => setDeclinedMsg(''), 3500);
};
window.addEventListener('challenge-declined', handler);
return () => {
window.removeEventListener('challenge-declined', handler);
clearTimeout(declinedTimerRef.current);
};
}, []);
const handleChallenge = player => {
if (challengingGameAssoc) return;
setChallengingGameAssoc(player.gameAssoc);
setDeclinedMsg('');
fetch('/api/game/challenge/' + player.gameAssoc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ challengerGameAssoc: currentGameAssoc }),
}).catch(() => setChallengingGameAssoc(null));
};
const visible = players
.filter(p => p.gameAssoc !== currentGameAssoc)
.filter(p => p.name.toLowerCase().includes(search.toLowerCase()));
@@ -177,24 +205,38 @@ const OnlinePlayersDialog = ({ open, onClose, currentGameAssoc }) => {
</div>
)}
{!loading && shown.map(player => (
<div key={player.gameAssoc} className="opd-row">
<div className="opd-avatar">
{player.name.slice(0, 2).toUpperCase()}
</div>
<div className="opd-info">
<span className="opd-name">{player.name}</span>
<span className="opd-since">
<i className="fa fa-clock-o" />
{' '}Waiting {formatSince(player.since)}
</span>
</div>
<a className="opd-join" href={`/play/${player.gameAssoc}`}>
<i className="fa fa-play" />
Join
</a>
{declinedMsg && (
<div className="opd-declined">
<i className="fa fa-times-circle" />
{' '}{declinedMsg}
</div>
))}
)}
{!loading && shown.map(player => {
const isWaiting = challengingGameAssoc === player.gameAssoc;
return (
<div key={player.gameAssoc} className="opd-row">
<div className="opd-avatar">
{player.name.slice(0, 2).toUpperCase()}
</div>
<div className="opd-info">
<span className="opd-name">{player.name}</span>
<span className="opd-since">
<i className="fa fa-clock-o" />
{' '}Waiting {formatSince(player.since)}
</span>
</div>
<button
className={`opd-join${isWaiting ? ' opd-join--waiting' : ''}`}
onClick={() => handleChallenge(player)}
disabled={!!challengingGameAssoc}
>
<i className={`fa ${isWaiting ? 'fa-spinner fa-spin' : 'fa-play'}`} />
{isWaiting ? 'Waiting...' : 'Join'}
</button>
</div>
);
})}
</div>
{!loading && hasMore && (

View File

@@ -130,6 +130,51 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
showOverlay('The connection has been lost w/ your friend...', 'Please, restart the game!');
};
const wChallenge = payload => {
const { challengerName, challengerGameAssoc } = payload;
const handleAccept = () => {
fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accepted: true, targetGameAssoc: gameAssoc }),
}).then(() => {
showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
}).catch(() => {});
};
const handleDecline = () => {
fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accepted: false, targetGameAssoc: gameAssoc }),
}).then(() => {
showOverlay('We are waiting for your opponent...', gameAssoc ? (
<WaitingOverlayContent
shareUrl={window.location.origin + '/play/' + gameAssoc}
currentGameAssoc={gameAssoc}
/>
) : '');
}).catch(() => {});
};
showOverlay(
challengerName + ' wants to challenge you!',
<div className="resign">
<a onClick={handleAccept}>Accept</a>
<a onClick={handleDecline}>Decline</a>
</div>,
);
};
const wChallengeResponse = payload => {
if (payload.accepted) {
window.location.href = '/play/' + payload.targetGameAssoc;
} else {
window.dispatchEvent(new CustomEvent('challenge-declined'));
}
};
const wTopic = payload => {
if (webPlayerRef.current !== payload.data.player) {
if (null === payload.data.resign) {
@@ -152,6 +197,12 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
};
const handleMercureMessage = payload => {
if (undefined !== payload.type) {
if ('challenge' === payload.type) wChallenge(payload);
else if ('challenge-response' === payload.type) wChallengeResponse(payload);
return;
}
if (undefined !== payload.data) {
wTopic(payload);
} else if (undefined === payload.msg) {