diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 9c08b059e..ac2577057 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -10,6 +10,9 @@ export const setNetworkId = id => ({ type: SET_NETWORK_ID, id }) export const SET_PLAYER_ADDRESS = "SET_PLAYER_ADDRESS"; export const setPlayerAddress = address => ({ type: SET_PLAYER_ADDRESS, address }) +export const SET_GAME_READ_ONLY = "SET_GAME_READ_ONLY"; +export const setGameReadOnly = readOnly => ({ type: SET_GAME_READ_ONLY, readOnly }) + export const LOAD_GAME_DATA = "LOAD_GAME_DATA"; export const loadGamedata = () => ({ type: LOAD_GAME_DATA, levels: undefined }) diff --git a/client/src/containers/App.js b/client/src/containers/App.js index 7f089c227..b7baba979 100644 --- a/client/src/containers/App.js +++ b/client/src/containers/App.js @@ -17,7 +17,8 @@ import { deprecationDate, } from "../utils/networkDeprecation"; import { Helmet } from "react-helmet"; - +import { store } from "./../store"; +import * as actions from "../../src/actions"; class App extends React.Component { constructor() { @@ -61,6 +62,35 @@ class App extends React.Component { this.props.navigate(`${constants.PATH_LEVEL_ROOT}${target}`); } + async continueAnyway() { + const deployWindow = document.querySelectorAll(".deploy-window-bg"); + deployWindow[0].style.display = "none"; + } + + async continueInReadOnly() { + store.dispatch(actions.loadGamedata()); + store.dispatch(actions.setGameReadOnly(true)); + const accountConnectionWindow = document.querySelectorAll( + ".account-connection-window-bg" + ); + accountConnectionWindow[0].style.display = "none"; + } + + async displayConnectionWindow() { + const accountConnectionWindow = document.querySelectorAll( + ".account-connection-window-bg" + ); + accountConnectionWindow[0].style.display = "block"; + } + + async requestAccounts() { + await window.ethereum.request({ method: "eth_requestAccounts" }); + const accountConnectionWindow = document.querySelectorAll( + ".account-connection-window-bg" + ); + accountConnectionWindow[0].style.display = "none"; + } + render() { let language = localStorage.getItem("lang"); let strings = loadTranslations(language); @@ -117,11 +147,6 @@ class App extends React.Component { } } - async function continueAnyway() { - const deployWindow = document.querySelectorAll(".deploy-window-bg"); - deployWindow[0].style.display = "none"; - } - return (
@@ -180,13 +205,46 @@ class App extends React.Component { /> + {/*not Account Connected window*/} +
+
+ +

{randBadIcon()}

+
+

{strings.accountNotConnectedTitle}

+
+

{strings.accountNotConnectedMessage}

+
+
+ +
+
+
{/*not Deployed window*/}
{!networkOnDeprecationOrDeprecated(this.state.chainId) ? ( @@ -194,7 +252,6 @@ class App extends React.Component { {/*deploy window*/}

{randGoodIcon()}

{strings.deployMessageTitle}

-
{strings.deployMessage} {supportedNetworksList(supportedNetworks)}

{strings.deployConfirmation}

@@ -225,7 +282,7 @@ class App extends React.Component { {strings.switchToSepolia} {!isDeprecatedNetwork(this.state.chainId) && ( - )} diff --git a/client/src/containers/Header.js b/client/src/containers/Header.js index b008a72a5..20c445eb6 100644 --- a/client/src/containers/Header.js +++ b/client/src/containers/Header.js @@ -2,7 +2,7 @@ import React from "react"; import onClickOutside from "react-onclickoutside"; import { connect } from "react-redux"; import { withRouter } from "../hoc/withRouter"; -import { Link } from "react-router-dom"; +import { Link, Navigate } from "react-router-dom"; import { bindActionCreators } from "redux"; import * as actions from "../actions"; import * as constants from "../constants"; @@ -11,6 +11,7 @@ import PropTypes from "prop-types"; import { ProgressBar } from "react-loader-spinner"; import { svgFilter } from "../utils/svg"; import LeaderIcon from "../components/leaderboard/LeaderIcon"; +import { store } from "../store"; // import parse from "html-react-parser"; class Header extends React.Component { @@ -24,7 +25,7 @@ class Header extends React.Component { multiDDOpen: false, }; - if (this.props.web3) { + if (this.props.web3 && !store.getState().gamedata.readOnly) { window.ethereum.request({ method: "eth_chainId" }).then((id) => { this.setState({ chainId: Number(id) }); }); @@ -325,7 +326,8 @@ class Header extends React.Component {
{window.location.pathname === constants.PATH_ROOT && - !!this.props.web3 && ( + !!this.props.web3 && + !store.getState().gamedata.readOnly && ( this.toggleDropdownState()} to={constants.PATH_LEADERBOARD} @@ -342,45 +344,58 @@ class Header extends React.Component { className="element-in-row toggle --small" type="checkbox" /> +
+ {store.getState().gamedata.readOnly && ( + + )} +
- -
-

this.setActiveTab(2)}> - - {strings.Networks} -

-
- {Object.values(constants.NETWORKS_INGAME).map( - (network, index) => { - if (network && network.name !== "local") { - if (Number(network.id) === this.state.chainId) - return false; // filter out current network - return ( -
{ - e.preventDefault(); - this.changeNetwork(network); - }} - className="dropdown-pill" - > - - {network.name} - -
- ); + {this.props.web3 && !store.getState().gamedata.readOnly && ( +
+

this.setActiveTab(2)}> + + {strings.Networks} +

+
+ {Object.values(constants.NETWORKS_INGAME).map( + (network, index) => { + if (network && network.name !== "local") { + if (Number(network.id) === this.state.chainId) + return false; // filter out current network + return ( +
{ + e.preventDefault(); + this.changeNetwork(network); + }} + className="dropdown-pill" + > + + {network.name} + +
+ ); + } + return null; } - return null; - } - )} + )} +
-
- + )}

this.setActiveTab(1)}> @@ -438,7 +453,7 @@ class Header extends React.Component { wrapperClass="progress-bar-wrapper" visible={true} /> - {!this.props.web3 && ( + {!this.props.web3 && !store.getState().gamedata.readOnly && (

Hey, You dont have the supported wallet!); // let language = localStorage.getItem("lang"); @@ -43,10 +44,17 @@ if (!window.ethereum) { // store.dispatch(actions.setNetworkId(parseInt("5"))); store.dispatch(actions.loadGamedata()); } else { - window.ethereum.request({ method: "eth_chainId" }).then((res) => { - store.dispatch(actions.setNetworkId(parseInt(res))); - store.dispatch(actions.loadGamedata()); - }) + window.ethereum.request({ method: "eth_accounts" }).then((res) => { + if (res.length !== 0) { + window.ethereum.request({ method: "eth_chainId" }).then((res) => { + store.dispatch(actions.setNetworkId(parseInt(res))); + store.dispatch(actions.loadGamedata()); + }); + } else { + const accountConnectionWindow = document.querySelectorAll('.account-connection-window-bg'); + if (accountConnectionWindow[0]) accountConnectionWindow[0].style.display = 'block'; + } + }); } root.render( @@ -69,7 +77,13 @@ root.render( // Post-load actions. window.addEventListener("load", async () => { - if (window.ethereum) { + // Check if any MetaMask account is connected to the frontend + let accountConnected = false; + if(window.ethereum) { + const eth_accounts = await window.ethereum.request({ method: "eth_accounts" }); + accountConnected = eth_accounts.length !== 0; + } + if (accountConnected) { window.web3 = new constants.Web3(window.ethereum); try { await window.ethereum.request({ method: `eth_requestAccounts` }); @@ -79,8 +93,7 @@ window.addEventListener("load", async () => { window.web3 = null; } } - - if (window.web3) { + if (window.web3 && accountConnected) { ethutil.setWeb3(window.web3); // ethutil.attachLogger(); diff --git a/client/src/middlewares/setGameReadOnly.js b/client/src/middlewares/setGameReadOnly.js new file mode 100644 index 000000000..8cedf957b --- /dev/null +++ b/client/src/middlewares/setGameReadOnly.js @@ -0,0 +1,8 @@ +import * as actions from '../actions'; + +const setGameReadOnly = store => next => action => { + if (action.type !== actions.SET_GAME_READ_ONLY) return next(action) + +} + +export default setGameReadOnly diff --git a/client/src/reducers/gamedataReducer.js b/client/src/reducers/gamedataReducer.js index b4e129773..78ede1f19 100644 --- a/client/src/reducers/gamedataReducer.js +++ b/client/src/reducers/gamedataReducer.js @@ -1,6 +1,7 @@ import * as actions from '../actions'; const initialState = { + readOnly: false, ethernautAddress: undefined, activeLevel: undefined, levels: [] @@ -9,6 +10,12 @@ const initialState = { const gameDataReducer = function(state = initialState, action) { switch(action.type) { + case actions.SET_GAME_READ_ONLY: + return { + ...state, + readOnly: action.readOnly + } + case actions.LOAD_GAME_DATA: return { ...state, diff --git a/client/src/styles/app.css b/client/src/styles/app.css index 1bc810aa4..0b1a7279c 100644 --- a/client/src/styles/app.css +++ b/client/src/styles/app.css @@ -85,7 +85,6 @@ ul { margin-top: -15%; } */ - .lines { margin-bottom: 4%; margin-top: 2%; @@ -154,19 +153,18 @@ button :hover { align-items: center; } - .top-banner { display: flex; gap: 10%; align-items: center; justify-content: space-between; - width: 100%; + width: 100%; height: 80px; border-radius: 6px; border: 2px solid var(--secondary-color); margin-bottom: 4%; margin-top: 2%; - padding: 2% + padding: 2%; } .top-banner-text { @@ -205,7 +203,7 @@ nav li { font-family: helvetica, sans-serif; } -.nav-links button i:hover{ +.nav-links button i:hover { outline: none; background: transparent; } @@ -275,6 +273,58 @@ nav li { margin-bottom: 2em; } +.account-connection-window-bg { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.3); + z-index: 4; + display: none; +} + +.account-connection-window { + position: relative; + top: 20%; + width: 40%; + left: 30%; + right: 30%; + display: inherit; + background-color: var(--primary-color); + z-index: 5; + padding: 2% 2% 2% 2%; + box-shadow: black 0px 0px 60px; + border: 2px solid var(--secondary-color); + border-radius: 6px; +} + +.account-connection-window h1 { + text-align: center; +} + +.account-connection-window h2 { + font-size: 2.5em; + text-align: center; +} + +.account-connection-window p { + font-size: 1.1em; + text-align: justify; +} + +.account-connection-close-x { + position: absolute; + right: 2.5%; + top: 2.5%; + width: 1.75rem; + height: 1.75rem; +} + +.connect-button { + margin-left: auto; +} + .deploy-window-bg { position: fixed; top: 0; @@ -309,6 +359,14 @@ nav li { text-align: center; } +.deploy-window h2 { + font-size: 2.5em; +} + +.deploy-window p { + font-size: 1.1em; +} + .choice-buttons { display: flex; justify-content: space-evenly; @@ -424,10 +482,10 @@ nav li { padding-left: 50px; padding-right: 50px; } -.author-section-border{ +.author-section-border { border: none !important; } -.author-section{ +.author-section { width: 60%; margin-left: 20%; @@ -550,13 +608,12 @@ footer { display: none; } - /* --------- styles for multi dropdowns --------------*/ -.multi-dropdown{ +.multi-dropdown { padding: 0% 0px; } -.multi-dropdown__icon{ +.multi-dropdown__icon { color: var(--primary-color); background-color: var(--secondary-color); padding: 4px 9px; @@ -569,11 +626,11 @@ footer { font-size: 20px; } -.multi-dropdown__icon:hover{ - opacity: 0.90; +.multi-dropdown__icon:hover { + opacity: 0.9; } -.multi-dropdown__dropdown{ +.multi-dropdown__dropdown { background-color: var(--secondary-color); color: var(--primary-color); border: solid 10px var(--secondary-color); @@ -586,31 +643,31 @@ footer { margin-top: 5px; } -.multi-dropdown__dropdown.--closed{ +.multi-dropdown__dropdown.--closed { display: none; } -.multi-dropdown__dropdown > div{ +.multi-dropdown__dropdown > div { background-color: var(--primary-color); - color: var(--secondary-color); + color: var(--secondary-color); margin-bottom: 7px; transition: 300ms; } -.multi-dropdown__dropdown > div:last-of-type{ +.multi-dropdown__dropdown > div:last-of-type { margin-bottom: 0; } -.multi-dropdown__dropdown div:hover{ +.multi-dropdown__dropdown div:hover { opacity: 0.95; } -.single-dropdown{ +.single-dropdown { padding-top: 5px; padding-bottom: 5px; border-radius: 5px; } -.single-dropdown.--hidden{ +.single-dropdown.--hidden { display: none; } @@ -634,37 +691,37 @@ footer { gap: 3px; } -.single-dropdown > div.hide{ +.single-dropdown > div.hide { display: none; } -.dropdown-pill{ - background-color: var(--secondary-color); - color: var(--primary-color); +.dropdown-pill { + background-color: var(--secondary-color); + color: var(--primary-color); border-radius: 3px; padding: 5px 10px; cursor: pointer; line-height: 16px; } -.dropdown-pill a, .dropdown-pill span{ +.dropdown-pill a, +.dropdown-pill span { text-decoration: none; - color: var(--secondary-color); + color: var(--secondary-color); } -.dropdown-pill div{ +.dropdown-pill div { display: flex; align-items: center; } -.dropdown-pill.--left{ +.dropdown-pill.--left { text-align: left; } - -.single-dropdown a{ +.single-dropdown a { text-decoration: none; - color: var(--primary-color);; + color: var(--primary-color); } .toggle.--small { @@ -677,7 +734,7 @@ footer { height: 15px; } -.filled-icon{ +.filled-icon { background-color: var(--secondary-color); color: var(--primary-color); width: fit-content; @@ -685,7 +742,7 @@ footer { border-radius: 5px; } -.filled-icon svg{ +.filled-icon svg { fill: var(--primary-color); display: inline-block; height: 13px; @@ -696,7 +753,7 @@ footer { } /* --------- styles for multi dropdowns --------------*/ -@media only screen and (max-width: 1029px){ +@media only screen and (max-width: 1029px) { :root { margin-left: 10%; margin-right: 10%; @@ -713,20 +770,18 @@ footer { .tooltip-container { background-color: var(--secondary-color); color: var(--primary-color); - padding:5px 10px; + padding: 5px 10px; border-radius: 5px; } @media only screen and (max-width: 956px) { - .multi-dropdown__dropdown{ + .multi-dropdown__dropdown { margin-left: -15.8em; } - } - @media only screen and (max-width: 885px) { - .appcontainer{ + .appcontainer { width: 100vw; } @@ -743,18 +798,18 @@ footer { width: 95%; } - .multi-dropdown{ + .multi-dropdown { padding: 4px 9px; } - .header-ul{ + .header-ul { display: flex; margin-right: 20px; } - .helpcontainer{ + .helpcontainer { width: calc(100vw - 40px); } - .header-ul > .dropdown.chains{ + .header-ul > .dropdown.chains { display: none; } @@ -787,7 +842,7 @@ footer { width: calc(100vw - 40px); } - .author-section-border{ + .author-section-border { padding: 0; } @@ -813,10 +868,10 @@ footer { align-items: center; } - .page-not-found-title{ - margin-top: 3rem; - } +.page-not-found-title { + margin-top: 3rem; +} - .page-not-found-text{ +.page-not-found-text { margin-top: 1rem; -} \ No newline at end of file +}