chg: usr: add share button to the overlay when the game ends #4
This commit is contained in:
@@ -471,58 +471,122 @@
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .share-copy-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 2px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(35, 111, 135, 0.25);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 2px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(35, 111, 135, 0.25);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(35, 111, 135, 0.4);
|
||||
transform: translateY(-2px);
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(35, 111, 135, 0.4);
|
||||
transform: translateY(-2px);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.copied {
|
||||
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||
border-color: #2a9e60;
|
||||
color: #a0f0c0;
|
||||
box-shadow: 0 4px 12px rgba(26, 104, 68, 0.4);
|
||||
}
|
||||
&.copied {
|
||||
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||
border-color: #2a9e60;
|
||||
color: #a0f0c0;
|
||||
box-shadow: 0 4px 12px rgba(26, 104, 68, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
#mine-wrapper .game-wrapper .game-overlay .game-overlay-window .game-overlay-share {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
background: linear-gradient(to bottom, #236f87 0%, #1a5068 100%);
|
||||
border: 2px solid #2e7a9a;
|
||||
color: #e0f4ff;
|
||||
font-family: 'Rajdhani', sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
padding: 12px 24px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 300ms cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(35, 111, 135, 0.25);
|
||||
z-index: 10;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(to bottom, #2d8aa8 0%, #236f87 100%);
|
||||
border-color: #5ba4d4;
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(35, 111, 135, 0.4);
|
||||
transform: translateY(-2px);
|
||||
|
||||
&::before {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.copied {
|
||||
background: linear-gradient(to bottom, #1a6844 0%, #135233 100%);
|
||||
border-color: #2a9e60;
|
||||
color: #a0f0c0;
|
||||
box-shadow: 0 4px 12px rgba(26, 104, 68, 0.4);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export const GameBoard = ({ gameAssoc, gameInherited, isEnvDev }) => {
|
||||
|
||||
return (
|
||||
<GridControl
|
||||
gameAssoc={gameAssoc}
|
||||
onClick={onClick}
|
||||
resign={resign}
|
||||
/>
|
||||
|
||||
@@ -7,20 +7,31 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import { useGame } from '@mine-contexts';
|
||||
import GridField from './GridField';
|
||||
import UserControl from '../user/UserControl';
|
||||
import GameTimer from '../GameTimer';
|
||||
import { BOMB_SYMBOLS, bombRadius } from '@mine-utils';
|
||||
|
||||
const GridControl = ({ onClick, resign }) => {
|
||||
const GridControl = ({ gameAssoc, onClick, resign }) => {
|
||||
const {
|
||||
overlay, overlayTitle, overlaySubTitle,
|
||||
webPlayer, activePlayer, bombSelected,
|
||||
cells, setCells,
|
||||
cells, setCells, endRef,
|
||||
} = useGame();
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
const shareUrl = gameAssoc ? `${window.location.origin}/battle/${gameAssoc}` : null;
|
||||
|
||||
const handleShare = () => {
|
||||
if (!shareUrl) return;
|
||||
navigator.clipboard.writeText(shareUrl).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2200);
|
||||
});
|
||||
};
|
||||
|
||||
const handleHover = (row, col) => {
|
||||
if (!bombSelected) return;
|
||||
const activeColor = activePlayer ? 'blue' : 'red';
|
||||
@@ -47,7 +58,22 @@ const GridControl = ({ onClick, resign }) => {
|
||||
<div className={`game-overlay${overlay ? '' : ' hide'}`}>
|
||||
<div className="game-overlay-window">
|
||||
<h1>{overlayTitle}</h1>
|
||||
<h2>{overlaySubTitle}</h2>
|
||||
{'string' === typeof overlaySubTitle ? (
|
||||
<h2>{overlaySubTitle}</h2>
|
||||
) : (
|
||||
overlaySubTitle
|
||||
)}
|
||||
{gameAssoc && endRef.current && (
|
||||
<button
|
||||
className={`game-overlay-share${copied ? ' copied' : ''}`}
|
||||
onClick={handleShare}
|
||||
title="Copy share link"
|
||||
aria-label="Copy share link"
|
||||
>
|
||||
<i className={`fa ${copied ? 'fa-check' : 'fa-share-alt'}`} />
|
||||
{copied ? 'Copied!' : 'Share Battle'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<UserControl
|
||||
|
||||
@@ -41,6 +41,8 @@ export const GameProvider = ({ children }) => {
|
||||
connectionLost, setConnectionLost,
|
||||
} = useGameState();
|
||||
|
||||
const [gameUuid, setGameUuid] = React.useState(null);
|
||||
|
||||
const sounds = useRef({
|
||||
click: new Howl({ src: ['/sound/click.mp3'] }),
|
||||
bomb: new Howl({ src: ['/sound/bomb.mp3'] }),
|
||||
@@ -202,8 +204,11 @@ export const GameProvider = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const resignProcess = color => {
|
||||
const resignProcess = (color, uuid = null) => {
|
||||
const wp = webPlayerRef.current;
|
||||
if (uuid) {
|
||||
setGameUuid(uuid);
|
||||
}
|
||||
showOverlay(
|
||||
color === wp ? 'You have been give up' : 'Your opponent has been resigned',
|
||||
color === wp ? 'You LOSE!' : 'You WIN!',
|
||||
@@ -225,9 +230,9 @@ export const GameProvider = ({ children }) => {
|
||||
value={{
|
||||
// State (for rendering)
|
||||
webPlayer, activePlayer, overlay, overlayTitle, overlaySubTitle,
|
||||
mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost,
|
||||
mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost, gameUuid,
|
||||
// Setters needed by useServerComm
|
||||
setCells, setGridReady,
|
||||
setCells, setGridReady, setGameUuid,
|
||||
// Refs (needed by useServerComm for async-safe reads)
|
||||
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef,
|
||||
// Sync helpers
|
||||
|
||||
@@ -20,7 +20,7 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
||||
/** Async-safe refs */
|
||||
webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef,
|
||||
/** State setters */
|
||||
setGridReady,
|
||||
setGridReady, setGameUuid,
|
||||
/** Sync helpers */
|
||||
syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
|
||||
/** Game logic */
|
||||
@@ -193,9 +193,12 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
||||
}
|
||||
|
||||
applyStep(payload.data);
|
||||
if (payload.data.uuid && !endRef.current) {
|
||||
setGameUuid(payload.data.uuid);
|
||||
}
|
||||
makeGameEndIfItEnds(payload.data.bluePoints, payload.data.redPoints, false, payload.data.leftMines);
|
||||
} else {
|
||||
resignProcess(payload.data.resign);
|
||||
resignProcess(payload.data.resign, payload.data.uuid);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -312,6 +315,9 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
||||
try {
|
||||
const result = await stepMutation.mutateAsync(dataPack);
|
||||
applyStep(result);
|
||||
if (result.uuid && !endRef.current) {
|
||||
setGameUuid(result.uuid);
|
||||
}
|
||||
makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines);
|
||||
} catch (e) {
|
||||
isEnvDev && console.error('Step error', e);
|
||||
@@ -321,8 +327,16 @@ const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
|
||||
const clickResign = () => {
|
||||
const color = activePlayerRef.current ? 'blue' : 'red';
|
||||
const stepElapsed = getStepElapsed(activePlayerRef.current, isGameRunningRef.current);
|
||||
stepMutation.mutate({ resign: color, stepElapsed });
|
||||
resignProcess(webPlayerRef.current);
|
||||
stepMutation.mutate(
|
||||
{ resign: color, stepElapsed },
|
||||
{
|
||||
onSuccess: result => {
|
||||
if (result?.uuid && !endRef.current) {
|
||||
resignProcess(webPlayerRef.current, result.uuid);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const resign = () => {
|
||||
|
||||
Reference in New Issue
Block a user