Merge branch 'account' into develop
This commit is contained in:
commit
ee60167d1e
14
WORKLOG.md
14
WORKLOG.md
@ -1,6 +1,15 @@
|
|||||||
# WORK WORK
|
# WORK WORK
|
||||||
## NOW
|
## NOW
|
||||||
*PRODUCTION*
|
*PRODUCTION*
|
||||||
|
* ACP
|
||||||
|
* essential
|
||||||
|
* error log
|
||||||
|
* account lookup w/ pw reset
|
||||||
|
|
||||||
|
* nice to have
|
||||||
|
|
||||||
|
* bot game grind
|
||||||
|
|
||||||
* serde serialize privatise
|
* serde serialize privatise
|
||||||
* stripe prod
|
* stripe prod
|
||||||
* mobile styles
|
* mobile styles
|
||||||
@ -10,6 +19,11 @@
|
|||||||
* graphs n shit
|
* graphs n shit
|
||||||
* acp init
|
* acp init
|
||||||
* DO postgres
|
* DO postgres
|
||||||
|
* make our own toasts / msg pane
|
||||||
|
* send account_instances on players update
|
||||||
|
|
||||||
|
* only clear effects on post_resolve
|
||||||
|
electrify doesn't work if you ko the construct
|
||||||
|
|
||||||
## SOON
|
## SOON
|
||||||
*SERVER*
|
*SERVER*
|
||||||
|
|||||||
9
acp/.babelrc
Normal file
9
acp/.babelrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
"es2015",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["transform-react-jsx", { "pragma":"preact.h" }]
|
||||||
|
]
|
||||||
|
}
|
||||||
1501
acp/.eslintrc.js
Normal file
1501
acp/.eslintrc.js
Normal file
File diff suppressed because it is too large
Load Diff
5
acp/.gitignore
vendored
Normal file
5
acp/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package-lock.json
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.cache/
|
||||||
|
assets/molecules
|
||||||
19
acp/acp.html
Normal file
19
acp/acp.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>mnml - acp</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
<meta name=apple-mobile-web-app-capable content=yes>
|
||||||
|
<meta name=apple-mobile-web-app-status-bar-style content=black>
|
||||||
|
<meta name="description" content="mnml pvp tbs">
|
||||||
|
<meta name="author" content="ntr@smokestack.io">
|
||||||
|
<link rel="stylesheet" href="./node_modules/izitoast/dist/css/iziToast.min.css"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Jura" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>js is required to run mnml</noscript>
|
||||||
|
</body>
|
||||||
|
<script src="./acp.js"></script>
|
||||||
|
</html>
|
||||||
18
acp/acp.js
Normal file
18
acp/acp.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
require('./../client/assets/styles/normalize.css');
|
||||||
|
require('./../client/assets/styles/skeleton.css');
|
||||||
|
|
||||||
|
require('./../client/assets/styles/styles.less');
|
||||||
|
require('./../client/assets/styles/menu.less');
|
||||||
|
require('./../client/assets/styles/acp.less');
|
||||||
|
require('./../client/assets/styles/nav.less');
|
||||||
|
require('./../client/assets/styles/footer.less');
|
||||||
|
require('./../client/assets/styles/account.less');
|
||||||
|
require('./../client/assets/styles/controls.less');
|
||||||
|
require('./../client/assets/styles/instance.less');
|
||||||
|
require('./../client/assets/styles/vbox.less');
|
||||||
|
require('./../client/assets/styles/game.less');
|
||||||
|
require('./../client/assets/styles/styles.mobile.css');
|
||||||
|
require('./../client/assets/styles/instance.mobile.css');
|
||||||
|
|
||||||
|
// kick it off
|
||||||
|
require('./src/acp');
|
||||||
49
acp/package.json
Normal file
49
acp/package.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "mnml-client",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "parcel watch acp.html --no-hmr --out-dir /var/lib/mnml/public/current",
|
||||||
|
"build": "parcel build acp.html",
|
||||||
|
"lint": "eslint --fix --ext .jsx src/",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"dependencies": {
|
||||||
|
"anime": "^0.1.2",
|
||||||
|
"animejs": "^3.0.1",
|
||||||
|
"async": "^2.6.2",
|
||||||
|
"borc": "^2.0.3",
|
||||||
|
"docco": "^0.7.0",
|
||||||
|
"izitoast": "^1.4.0",
|
||||||
|
"keymaster": "^1.6.2",
|
||||||
|
"linkstate": "^1.1.1",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"node-sass": "^4.12.0",
|
||||||
|
"parcel": "^1.12.3",
|
||||||
|
"preact": "^8.4.2",
|
||||||
|
"preact-compat": "^3.19.0",
|
||||||
|
"preact-context": "^1.1.3",
|
||||||
|
"preact-redux": "^2.1.0",
|
||||||
|
"react-stripe-elements": "^3.0.0",
|
||||||
|
"redux": "^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-plugin-module-resolver": "^3.2.0",
|
||||||
|
"babel-preset-es2015": "^6.24.1",
|
||||||
|
"babel-preset-react": "^6.24.1",
|
||||||
|
"eslint": "^5.6.0",
|
||||||
|
"eslint-config-airbnb-base": "^13.1.0",
|
||||||
|
"eslint-plugin-import": "^2.14.0",
|
||||||
|
"eslint-plugin-react": "^7.11.1",
|
||||||
|
"jest": "^18.0.0",
|
||||||
|
"less": "^3.9.0"
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"react": "preact-compat",
|
||||||
|
"react-dom": "preact-compat"
|
||||||
|
}
|
||||||
|
}
|
||||||
36
acp/src/acp.game.list.jsx
Normal file
36
acp/src/acp.game.list.jsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
const { Component } = require('preact');
|
||||||
|
const { connect } = require('preact-redux');
|
||||||
|
const linkState = require('linkstate').default;
|
||||||
|
|
||||||
|
const actions = require('./actions');
|
||||||
|
|
||||||
|
const addState = connect(
|
||||||
|
function receiveState(state) {
|
||||||
|
const {
|
||||||
|
games,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
games
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function AcpGameList(args) {
|
||||||
|
const {
|
||||||
|
games,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
if (!games) return false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{games.map((g, i) => <tr key={i}><td>{JSON.stringify(g)}</td></tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addState(AcpGameList);
|
||||||
34
acp/src/acp.jsx
Normal file
34
acp/src/acp.jsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
// const logger = require('redux-diff-logger');
|
||||||
|
|
||||||
|
const { Provider, connect } = require('preact-redux');
|
||||||
|
const { createStore, combineReducers } = require('redux');
|
||||||
|
|
||||||
|
const reducers = require('./reducers');
|
||||||
|
const actions = require('./actions');
|
||||||
|
|
||||||
|
const Main = require('./acp.main');
|
||||||
|
|
||||||
|
// Redux Store
|
||||||
|
const store = createStore(
|
||||||
|
combineReducers(reducers),
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
|
||||||
|
);
|
||||||
|
|
||||||
|
document.fonts.load('16pt "Jura"').then(() => {
|
||||||
|
const Acp = () => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<div id="mnml" class="acp">
|
||||||
|
<nav>
|
||||||
|
<h1>acp</h1>
|
||||||
|
<hr/>
|
||||||
|
</nav>
|
||||||
|
<Main />
|
||||||
|
<aside></aside>
|
||||||
|
</div>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
preact.render(<Acp />, document.body);
|
||||||
|
});
|
||||||
173
acp/src/acp.main.jsx
Normal file
173
acp/src/acp.main.jsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
const { Component } = require('preact');
|
||||||
|
const { connect } = require('preact-redux');
|
||||||
|
const linkState = require('linkstate').default;
|
||||||
|
|
||||||
|
const actions = require('./actions');
|
||||||
|
const { postData, errorToast } = require('./../../client/src/utils');
|
||||||
|
|
||||||
|
const AcpGameList = require('./acp.game.list');
|
||||||
|
const AcpUser = require('./acp.user');
|
||||||
|
|
||||||
|
const addState = connect(
|
||||||
|
function receiveState(state) {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
user,
|
||||||
|
msg,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
account,
|
||||||
|
user,
|
||||||
|
msg,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
function receiveDispatch(dispatch) {
|
||||||
|
function setUser(user) {
|
||||||
|
dispatch(actions.setUser(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGames(list) {
|
||||||
|
dispatch(actions.setGames(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMsg(msg) {
|
||||||
|
dispatch(actions.setMsg(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setUser,
|
||||||
|
setMsg,
|
||||||
|
setGames,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
class AcpMain extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
id: null,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(args, state) {
|
||||||
|
const {
|
||||||
|
setGames,
|
||||||
|
setUser,
|
||||||
|
setMsg,
|
||||||
|
|
||||||
|
msg,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setMsg(null);
|
||||||
|
this.setState({ id: null, name: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUser = () => {
|
||||||
|
reset();
|
||||||
|
postData('/acp/user', { id, name })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) return setMsg(data.error);
|
||||||
|
setUser(data);
|
||||||
|
})
|
||||||
|
.catch(error => errorToast(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
const gameList = () => {
|
||||||
|
reset();
|
||||||
|
postData('/acp/game/list', { number: 20 })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) return setMsg(data.error);
|
||||||
|
console.log(data);
|
||||||
|
setGames(data.data);
|
||||||
|
})
|
||||||
|
.catch(error => errorToast(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
const gameOpen = () => {
|
||||||
|
reset();
|
||||||
|
postData('/acp/game/open')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) return setMsg(data.error);
|
||||||
|
console.log(data);
|
||||||
|
setGames(data);
|
||||||
|
})
|
||||||
|
.catch(error => errorToast(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main class='menu'>
|
||||||
|
<div class="top">
|
||||||
|
<div class="msg">{msg}</div>
|
||||||
|
<AcpUser />
|
||||||
|
<AcpGameList />
|
||||||
|
</div>
|
||||||
|
<div class="bottom acp list">
|
||||||
|
<div>
|
||||||
|
<label for="current">Username:</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
value={this.state.name}
|
||||||
|
onInput={linkState(this, 'name')}
|
||||||
|
placeholder="name"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="text"
|
||||||
|
name="userid"
|
||||||
|
value={this.state.id}
|
||||||
|
onInput={linkState(this, 'id')}
|
||||||
|
placeholder="id"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={getUser}>
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="current">Game:</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="text"
|
||||||
|
name="userid"
|
||||||
|
value={this.state.id}
|
||||||
|
onInput={linkState(this, 'id')}
|
||||||
|
placeholder="id"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={getUser}>
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={gameList}>
|
||||||
|
Last 20
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={gameOpen}>
|
||||||
|
Open
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addState(AcpMain);
|
||||||
97
acp/src/acp.user.jsx
Normal file
97
acp/src/acp.user.jsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
const { Component } = require('preact');
|
||||||
|
const { connect } = require('preact-redux');
|
||||||
|
const linkState = require('linkstate').default;
|
||||||
|
|
||||||
|
const { postData, errorToast } = require('./../../client/src/utils');
|
||||||
|
const actions = require('./actions');
|
||||||
|
|
||||||
|
const addState = connect(
|
||||||
|
function receiveState(state) {
|
||||||
|
const {
|
||||||
|
user,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
function receiveDispatch(dispatch) {
|
||||||
|
function setUser(user) {
|
||||||
|
dispatch(actions.setUser(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMsg(msg) {
|
||||||
|
dispatch(actions.setMsg(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setUser,
|
||||||
|
setMsg,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function AcpUser(args) {
|
||||||
|
const {
|
||||||
|
user,
|
||||||
|
setUser,
|
||||||
|
setMsg,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
const {
|
||||||
|
credits,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
if (!user) return false;
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setMsg(null);
|
||||||
|
this.setState({ credits: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
const addCredits = () => {
|
||||||
|
reset();
|
||||||
|
postData('/acp/user/credits', { id: user.id, credits })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) return setMsg(data.error);
|
||||||
|
return setUser(data);
|
||||||
|
})
|
||||||
|
.catch(error => setMsg(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="user">
|
||||||
|
<dl>
|
||||||
|
<h1>{user.name}</h1>
|
||||||
|
<dt>Id</dt>
|
||||||
|
<dd>{user.id}</dd>
|
||||||
|
<dt>Credits</dt>
|
||||||
|
<dd>{user.balance}</dd>
|
||||||
|
<dt>Subscribed</dt>
|
||||||
|
<dd>{user.subscribed.toString()}</dd>
|
||||||
|
</dl>
|
||||||
|
<div>
|
||||||
|
<label for="current">Add Credits:</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="number"
|
||||||
|
name="credits"
|
||||||
|
value={credits}
|
||||||
|
onInput={linkState(this, 'credits')}
|
||||||
|
placeholder="credits"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={addCredits}>
|
||||||
|
Add Credits
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Constructs</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addState(AcpUser);
|
||||||
4
acp/src/actions.jsx
Normal file
4
acp/src/actions.jsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const setAccount = value => ({ type: 'SET_ACCOUNT', value });
|
||||||
|
export const setUser = value => ({ type: 'SET_USER', value });
|
||||||
|
export const setMsg = value => ({ type: 'SET_MSG', value });
|
||||||
|
export const setGames = value => ({ type: 'SET_GAMES', value });
|
||||||
18
acp/src/reducers.jsx
Normal file
18
acp/src/reducers.jsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
function createReducer(defaultState, actionType) {
|
||||||
|
return function reducer(state = defaultState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case actionType:
|
||||||
|
return action.value;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-disable key-spacing */
|
||||||
|
module.exports = {
|
||||||
|
account: createReducer(null, 'SET_ACCOUNT'),
|
||||||
|
user: createReducer(null, 'SET_USER'),
|
||||||
|
msg: createReducer(null, 'SET_MSG'),
|
||||||
|
games: createReducer([], 'SET_GAMES'),
|
||||||
|
};
|
||||||
@ -12,3 +12,9 @@ cd $MNML_PATH/client
|
|||||||
rm -rf dist
|
rm -rf dist
|
||||||
npm i
|
npm i
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
echo "Building acp version $VERSION"
|
||||||
|
cd $MNML_PATH/acp
|
||||||
|
rm -rf dist
|
||||||
|
npm i
|
||||||
|
npm run build
|
||||||
|
|||||||
@ -6,8 +6,12 @@ MNML_PATH=$(realpath "$DIR/../")
|
|||||||
VERSION=$(<"$MNML_PATH/VERSION")
|
VERSION=$(<"$MNML_PATH/VERSION")
|
||||||
|
|
||||||
SERVER_BIN_DIR="/usr/local/mnml/bin"
|
SERVER_BIN_DIR="/usr/local/mnml/bin"
|
||||||
|
|
||||||
CLIENT_DIST_DIR="/var/lib/mnml/client"
|
CLIENT_DIST_DIR="/var/lib/mnml/client"
|
||||||
CLIENT_PUBLIC_DIR="/var/lib/mnml/public/current"
|
CLIENT_PUBLIC_DIR="/var/lib/mnml/public/client"
|
||||||
|
|
||||||
|
ACP_DIST_DIR="/var/lib/mnml/acp"
|
||||||
|
ACP_PUBLIC_DIR="/var/lib/mnml/public/acp"
|
||||||
|
|
||||||
# server updates
|
# server updates
|
||||||
echo "syncing server $VERSION "
|
echo "syncing server $VERSION "
|
||||||
@ -19,6 +23,11 @@ ssh -q mnml ls -lah "$SERVER_BIN_DIR"
|
|||||||
echo "syncing client $VERSION"
|
echo "syncing client $VERSION"
|
||||||
rsync -a --delete --delete-excluded "$MNML_PATH/client/dist/" mnml:"$CLIENT_DIST_DIR/$VERSION/"
|
rsync -a --delete --delete-excluded "$MNML_PATH/client/dist/" mnml:"$CLIENT_DIST_DIR/$VERSION/"
|
||||||
ssh -q mnml ln -nfs "$CLIENT_DIST_DIR/$VERSION" "$CLIENT_PUBLIC_DIR"
|
ssh -q mnml ln -nfs "$CLIENT_DIST_DIR/$VERSION" "$CLIENT_PUBLIC_DIR"
|
||||||
|
|
||||||
|
# acp updates
|
||||||
|
echo "syncing acp $VERSION"
|
||||||
|
rsync -a --delete --delete-excluded "$MNML_PATH/acp/dist/" mnml:"$ACP_DIST_DIR/$VERSION/"
|
||||||
|
ssh -q mnml ln -nfs "$ACP_DIST_DIR/$VERSION" "$ACP_PUBLIC_DIR"
|
||||||
ssh -q mnml ls -lah "/var/lib/mnml/public"
|
ssh -q mnml ls -lah "/var/lib/mnml/public"
|
||||||
|
|
||||||
echo "restarting mnml service"
|
echo "restarting mnml service"
|
||||||
|
|||||||
55
client/assets/styles/account.less
Normal file
55
client/assets/styles/account.less
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
@import 'colours.less';
|
||||||
|
|
||||||
|
.account {
|
||||||
|
margin-top: 2em;
|
||||||
|
grid-area: bottom;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
grid-gap: 0 1em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
// height: 3em;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 75%;
|
||||||
|
height: 3em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.list {
|
||||||
|
letter-spacing: 0.25em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
figure {
|
||||||
|
font-size: 125%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stripe-btn {
|
||||||
|
background: @yellow;
|
||||||
|
color: black;
|
||||||
|
border-radius: 2px;
|
||||||
|
border-width: 0;
|
||||||
|
|
||||||
|
&:active, &:focus, &:hover {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
border: 1px solid @yellow;
|
||||||
|
color: @yellow;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
24
client/assets/styles/acp.less
Normal file
24
client/assets/styles/acp.less
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#mnml.acp {
|
||||||
|
user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-ms-user-select: text;
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@
|
|||||||
@blue: #3050f8;
|
@blue: #3050f8;
|
||||||
@white: #f5f5f5; // whitesmoke
|
@white: #f5f5f5; // whitesmoke
|
||||||
@purple: #9355b5; // 6lack - that far cover
|
@purple: #9355b5; // 6lack - that far cover
|
||||||
|
@yellow: #ffa100;
|
||||||
|
|
||||||
@black: black;
|
@black: black;
|
||||||
@gray: #222;
|
@gray: #222;
|
||||||
@ -30,3 +31,33 @@ svg {
|
|||||||
stroke: @blue;
|
stroke: @blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.green {
|
||||||
|
color: @green;
|
||||||
|
stroke: @green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
color: @red;
|
||||||
|
stroke: @red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red-fill {
|
||||||
|
fill: @red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blue {
|
||||||
|
color: @blue;
|
||||||
|
stroke: @blue;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gray {
|
||||||
|
color: #333;
|
||||||
|
stroke: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.white {
|
||||||
|
color: @white;
|
||||||
|
stroke: @white;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,10 +1,22 @@
|
|||||||
aside {
|
aside {
|
||||||
grid-area: ctrl;
|
grid-area: ctrl;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"timer controls"
|
||||||
|
"timer controls"
|
||||||
|
"timer controls";
|
||||||
|
|
||||||
|
grid-template-columns: min-content 1fr;
|
||||||
grid-template-rows: 1fr 1fr 1fr;
|
grid-template-rows: 1fr 1fr 1fr;
|
||||||
grid-gap: 0.5em 0;
|
grid-gap: 0.5em 0;
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em 1em 1em 0;
|
||||||
|
|
||||||
|
// fix chrome being inconsistent
|
||||||
|
table {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -28,6 +40,30 @@ aside {
|
|||||||
border-color: forestgreen;
|
border-color: forestgreen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timer-container {
|
||||||
|
grid-area: timer;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
|
||||||
|
width: 0.25em;
|
||||||
|
max-width: 0.25em;
|
||||||
|
|
||||||
|
margin: 0 1em 0 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer {
|
||||||
|
background: whitesmoke;
|
||||||
|
transition-property: all;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
transition-delay: 0;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ready-btn:hover, .ready-btn:focus, .ready-btn:active {
|
.ready-btn:hover, .ready-btn:focus, .ready-btn:active {
|
||||||
|
|||||||
51
client/assets/styles/footer.less
Normal file
51
client/assets/styles/footer.less
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
footer {
|
||||||
|
display: none;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
grid-area: footer;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: #222;
|
||||||
|
font-size: 1.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
background: #222;
|
||||||
|
border-right: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ready {
|
||||||
|
background: forestgreen;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer-container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 0.25em;
|
||||||
|
max-height: 0.25em;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
margin: 1em 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timer {
|
||||||
|
background: whitesmoke;
|
||||||
|
transition-property: all;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
transition-delay: 0;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-btn, #instance-nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
@import 'colours.less';
|
||||||
/* GAME */
|
/* GAME */
|
||||||
|
|
||||||
.game {
|
.game {
|
||||||
@ -322,15 +323,6 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@keyframes rotate {
|
|
||||||
0% {
|
|
||||||
transform: rotateY(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotateY(50deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
.resolving .skills button {
|
.resolving .skills button {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
@ -338,80 +330,9 @@
|
|||||||
.skill-animation {
|
.skill-animation {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
stroke-width: 5px;
|
stroke-width: 5px;
|
||||||
|
height: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
COMBAT ANIMATIONS
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ----------------------------------------------
|
|
||||||
* Generated by Animista on 2019-5-21 11:38:30
|
|
||||||
* w: http://animista.net, t: @cssanimista
|
|
||||||
* ---------------------------------------------- */
|
|
||||||
|
|
||||||
|
|
||||||
/*.attack-cast {
|
|
||||||
-webkit-animation: attack-cast 0.5s cubic-bezier(0.550, 0.085, 0.680, 0.530) both;
|
|
||||||
animation: attack-cast 0.5s cubic-bezier(0.550, 0.085, 0.680, 0.530) both;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes attack-cast {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: translateY(0);
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: translateY(-5em);
|
|
||||||
transform: translateY(-5em);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes attack-cast {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: translateY(0);
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: translateY(-5em);
|
|
||||||
transform: translateY(-5em);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.attack-hit {
|
|
||||||
-webkit-animation: attack-hit 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
|
||||||
animation: attack-hit 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
|
|
||||||
}
|
|
||||||
|
|
||||||
@-webkit-keyframes attack-hit {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: translateY(-5em) translateX(5em);
|
|
||||||
transform: translateY(-5em) translateX(5em);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: translateY(0) translateX(0);
|
|
||||||
transform: translateY(0) translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes attack-hit {
|
|
||||||
0% {
|
|
||||||
-webkit-transform: translateY(-5em) translateX(5em);
|
|
||||||
transform: translateY(-5em) translateX(5em);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
-webkit-transform: translateY(0) translateX(0);
|
|
||||||
transform: translateY(0) translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 1000px) {
|
@media (max-width: 1000px) {
|
||||||
.game {
|
.game {
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
|||||||
@ -426,6 +426,12 @@
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
grid-area: playername;
|
grid-area: playername;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.team {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min-content, 33%));
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile Nav*/
|
/* Mobile Nav*/
|
||||||
|
|||||||
90
client/assets/styles/menu.less
Normal file
90
client/assets/styles/menu.less
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
@import 'colours.less';
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-rows: minmax(min-content, 2fr) 1fr;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"top"
|
||||||
|
"bottom";
|
||||||
|
|
||||||
|
.top {
|
||||||
|
grid-area: top;
|
||||||
|
padding: 0 0 0.5em 2em;
|
||||||
|
border-bottom: 0.1em solid #222;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
grid-area: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.team {
|
||||||
|
display: grid;
|
||||||
|
grid-area: top;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min-content, 33%));
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
.team-select:not(:nth-child(3n)) {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.construct {
|
||||||
|
flex: 1 1 33%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border: 1px solid black;
|
||||||
|
transition-property: border;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
transition-delay: 0;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
|
||||||
|
button:not(:last-child) {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
pointer-events: none;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inventory {
|
||||||
|
margin-top: 2em;
|
||||||
|
grid-area: bottom;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
letter-spacing: 0.25em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-gap: 1em;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
font-size: 125%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
client/assets/styles/nav.less
Normal file
99
client/assets/styles/nav.less
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
nav {
|
||||||
|
grid-area: nav;
|
||||||
|
padding-left: 2em;
|
||||||
|
margin-right: 2em;
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2:first-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 1em 0;
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: block;
|
||||||
|
color: #888;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.active {
|
||||||
|
color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[disabled], button[disabled]:hover {
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
color: whitesmoke;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus, button:active {
|
||||||
|
color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-info {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-header {
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
flex: 1;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-info svg {
|
||||||
|
margin: 0.5em 0 0 1em;
|
||||||
|
height: 1em;
|
||||||
|
background-color: black;
|
||||||
|
stroke: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ping-path {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 4px;
|
||||||
|
stroke-dasharray: 121, 242;
|
||||||
|
animation: saw 2s infinite linear;
|
||||||
|
|
||||||
|
transition-property: stroke-color;
|
||||||
|
transition-duration: 0.25s;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ping-text {
|
||||||
|
margin-left: 1em;
|
||||||
|
min-width: 3em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes saw {
|
||||||
|
0% {
|
||||||
|
stroke-dashoffset: 363;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,6 @@
|
|||||||
@import 'colours.less';
|
@import 'colours.less';
|
||||||
|
|
||||||
/*
|
|
||||||
GLOBAL
|
|
||||||
*/
|
|
||||||
|
|
||||||
html, body, #mnml {
|
html, body, #mnml {
|
||||||
/*width: 100%;*/
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
background-color: black;
|
background-color: black;
|
||||||
@ -48,6 +43,9 @@ html {
|
|||||||
h1 {
|
h1 {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -66,6 +64,7 @@ h4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
|
color: #222;
|
||||||
margin: 1.5em 0;
|
margin: 1.5em 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -85,70 +84,11 @@ figure {
|
|||||||
"nav main ctrl";
|
"nav main ctrl";
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
|
||||||
grid-area: nav;
|
|
||||||
padding-left: 2em;
|
|
||||||
margin-right: 2em;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav h2:first-child {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav h2 {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav hr {
|
|
||||||
margin: 1em 0;
|
|
||||||
border-color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav button {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: block;
|
|
||||||
color: #888;
|
|
||||||
flex: 1 1 100%;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border-width: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav button.active {
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav button[disabled], nav button[disabled]:hover {
|
|
||||||
color: #333;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav button:hover {
|
|
||||||
color: whitesmoke;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav button:focus, nav button:active {
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
grid-area: main;
|
grid-area: main;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.right:focus, tr.right:hover {
|
|
||||||
box-shadow: inset -0.5em 0 0 0 whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
transition-property: color, background;
|
|
||||||
transition-duration: 0.25s;
|
|
||||||
transition-delay: 0;
|
|
||||||
transition-timing-function: ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button, input {
|
button, input {
|
||||||
font-family: 'Jura';
|
font-family: 'Jura';
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
@ -166,22 +106,34 @@ button, input {
|
|||||||
transition-duration: 0.25s;
|
transition-duration: 0.25s;
|
||||||
transition-delay: 0;
|
transition-delay: 0;
|
||||||
transition-timing-function: ease;
|
transition-timing-function: ease;
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
&:hover {
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
border-color: @gray-hover;
|
border-color: @gray-hover;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:focus {
|
&:focus {
|
||||||
/*colour necesary to bash skellington*/
|
/*colour necesary to bash skellington*/
|
||||||
color: @gray-focus;
|
color: @gray-focus;
|
||||||
border-color: @gray-focus;
|
border-color: @gray-focus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: whitesmoke;
|
color: whitesmoke;
|
||||||
font-size: 150%;
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: whitesmoke;
|
||||||
|
border-color: @gray-hover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
/*colour necesary to bash skellington*/
|
||||||
|
color: @gray-focus;
|
||||||
|
border-color: @gray-focus;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@ -191,19 +143,6 @@ svg {
|
|||||||
height: 2em;
|
height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skill-animation {
|
|
||||||
height: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.team .avatar {
|
|
||||||
object-fit: contain;
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -246,64 +185,19 @@ button[disabled] {
|
|||||||
border-color: #222;
|
border-color: #222;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
COLOURS
|
|
||||||
*/
|
|
||||||
|
|
||||||
.green {
|
|
||||||
color: #1FF01F;
|
|
||||||
stroke: #1FF01F;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red {
|
|
||||||
color: #a52a2a;
|
|
||||||
stroke: #a52a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red-fill {
|
|
||||||
fill: #a52a2a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blue {
|
|
||||||
color: #3050f8;
|
|
||||||
stroke: #3050f8;
|
|
||||||
stroke-linecap: round;
|
|
||||||
}
|
|
||||||
|
|
||||||
.yellow {
|
|
||||||
color: #FFD123;
|
|
||||||
stroke: #FFD123;
|
|
||||||
}
|
|
||||||
|
|
||||||
.purple {
|
|
||||||
color: #A25AC1;
|
|
||||||
stroke: #A25AC1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cyan {
|
|
||||||
color: #6AD1BF;
|
|
||||||
stroke: #6AD1BF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gray {
|
|
||||||
color: #333;
|
|
||||||
stroke: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.white {
|
|
||||||
color: whitesmoke;
|
|
||||||
stroke: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
LOGIN
|
LOGIN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.login {
|
.login {
|
||||||
width: 25%;
|
width: 50%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#mnml input, #mnml select {
|
#mnml input, #mnml select {
|
||||||
@ -316,242 +210,6 @@ button[disabled] {
|
|||||||
border-color: #888;
|
border-color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
account
|
|
||||||
*/
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
grid-area: hd;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
flex: 1;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-status {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-header {
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
flex: 1;
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-status svg {
|
|
||||||
margin: 0.5em 0 0 1em;
|
|
||||||
height: 1em;
|
|
||||||
background-color: black;
|
|
||||||
stroke: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ping-path {
|
|
||||||
fill: none;
|
|
||||||
stroke-width: 4px;
|
|
||||||
stroke-dasharray: 121, 242;
|
|
||||||
animation: saw 2s infinite linear;
|
|
||||||
|
|
||||||
transition-property: stroke-color;
|
|
||||||
transition-duration: 0.25s;
|
|
||||||
transition-timing-function: ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ping-text {
|
|
||||||
margin-left: 1em;
|
|
||||||
min-width: 3em;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes saw {
|
|
||||||
0% {
|
|
||||||
stroke-dashoffset: 363;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
stroke-dashoffset: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
TEAM
|
|
||||||
*/
|
|
||||||
|
|
||||||
.team {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(min-content, 33%));
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-construct {
|
|
||||||
margin: 0.5em;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border: 1px solid black;
|
|
||||||
transition-property: border;
|
|
||||||
transition-duration: 0.25s;
|
|
||||||
transition-delay: 0;
|
|
||||||
transition-timing-function: ease;
|
|
||||||
|
|
||||||
button:not(:last-child) {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
pointer-events: none;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawn-btn.menu-construct {
|
|
||||||
border: 1px solid #333;
|
|
||||||
color: #333;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawn-btn.menu-construct.selected {
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawn-btn.menu-construct:hover {
|
|
||||||
border: 1px solid whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawn-btn input {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawn-btn button {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
margin: 0.5em 1em;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spawn-btn input[disabled], .spawn-btn button[disabled] {
|
|
||||||
opacity: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
.play {
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
grid-template-rows: minmax(min-content, 2fr) 1fr;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
|
|
||||||
grid-template-areas:
|
|
||||||
"team"
|
|
||||||
"inventory";
|
|
||||||
|
|
||||||
.team {
|
|
||||||
grid-area: team;
|
|
||||||
|
|
||||||
/* poor man's <hr>*/
|
|
||||||
padding: 0.5em 2em 0 0;
|
|
||||||
border-bottom: 0.1em solid #444;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-construct {
|
|
||||||
flex: 1 0 33%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inventory {
|
|
||||||
margin: 2em 2em 0 0;
|
|
||||||
grid-area: inventory;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
|
|
||||||
.list {
|
|
||||||
letter-spacing: 0.25em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
grid-gap: 1em;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure {
|
|
||||||
font-size: 125%;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-instance-btn {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stripe-btn {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
margin: 0.25em 0;
|
|
||||||
background: whitesmoke;
|
|
||||||
color: black;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stripe-btn:hover {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.play-btn {
|
|
||||||
font-size: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-btn {
|
|
||||||
border: 1px solid #222;
|
|
||||||
float: right;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.create-form {
|
|
||||||
grid-area: create;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
justify-self: flex-end;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
margin-bottom: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.create-form button {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.create-form button:first-child {
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.create-form button:hover, .create-form button:focus {
|
|
||||||
border-color: whitesmoke;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure.gray {
|
figure.gray {
|
||||||
color: #333;
|
color: #333;
|
||||||
stroke-color: #333;
|
stroke-color: #333;
|
||||||
@ -562,56 +220,9 @@ figure.gray {
|
|||||||
fill: none;
|
fill: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
main .top button {
|
.credits {
|
||||||
width: 100%;
|
color: @yellow;
|
||||||
}
|
font-weight: 800;
|
||||||
|
|
||||||
.timer-container {
|
|
||||||
display: flex;
|
|
||||||
flex: 1 1 100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 0.25em;
|
|
||||||
max-height: 0.25em;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
margin: 1em 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer {
|
|
||||||
background: whitesmoke;
|
|
||||||
transition-property: all;
|
|
||||||
transition-duration: 0.25s;
|
|
||||||
transition-delay: 0;
|
|
||||||
transition-timing-function: ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
display: none;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
grid-area: footer;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer button {
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
background: #222;
|
|
||||||
font-size: 1.5em;
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer button:disabled {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer button:not(:last-child) {
|
|
||||||
background: #222;
|
|
||||||
border-right: 1px solid black;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer button .ready {
|
|
||||||
background: forestgreen;
|
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1500px) {
|
@media (max-width: 1500px) {
|
||||||
@ -624,10 +235,6 @@ footer button .ready {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-btn, #instance-nav {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-title {
|
.mobile-title {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 0;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login {
|
.login {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>mnml.gg</title>
|
<title>mnml - abstract strategy</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name=apple-mobile-web-app-capable content=yes>
|
<meta name=apple-mobile-web-app-capable content=yes>
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
require('./assets/styles/styles.less');
|
require('./assets/styles/styles.less');
|
||||||
|
require('./assets/styles/menu.less');
|
||||||
|
require('./assets/styles/nav.less');
|
||||||
|
require('./assets/styles/footer.less');
|
||||||
|
require('./assets/styles/account.less');
|
||||||
|
require('./assets/styles/controls.less');
|
||||||
require('./assets/styles/instance.less');
|
require('./assets/styles/instance.less');
|
||||||
require('./assets/styles/vbox.less');
|
require('./assets/styles/vbox.less');
|
||||||
require('./assets/styles/game.less');
|
require('./assets/styles/game.less');
|
||||||
require('./assets/styles/controls.less');
|
|
||||||
require('./assets/styles/styles.mobile.css');
|
require('./assets/styles/styles.mobile.css');
|
||||||
require('./assets/styles/instance.mobile.css');
|
require('./assets/styles/instance.mobile.css');
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"docco": "^0.7.0",
|
"docco": "^0.7.0",
|
||||||
"izitoast": "^1.4.0",
|
"izitoast": "^1.4.0",
|
||||||
"keymaster": "^1.6.2",
|
"keymaster": "^1.6.2",
|
||||||
|
"linkstate": "^1.1.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"parcel": "^1.12.3",
|
"parcel": "^1.12.3",
|
||||||
|
|||||||
151
client/src/components/account.management.jsx
Normal file
151
client/src/components/account.management.jsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
const { Component } = require('preact')
|
||||||
|
const { connect } = require('preact-redux');
|
||||||
|
const linkState = require('linkstate').default;
|
||||||
|
|
||||||
|
const SpawnButton = require('./spawn.button');
|
||||||
|
|
||||||
|
const { postData, errorToast, infoToast } = require('../utils');
|
||||||
|
const actions = require('../actions');
|
||||||
|
|
||||||
|
const addState = connect(
|
||||||
|
function receiveState(state) {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
ping,
|
||||||
|
ws,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
|
||||||
|
function setPassword(current, password) {
|
||||||
|
postData('/account/password', { current, password })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) return errorToast(data.error_message);
|
||||||
|
infoToast('Password changed. Reloading...')
|
||||||
|
setTimeout(() => window.location.reload(), 5000);
|
||||||
|
})
|
||||||
|
.catch(error => errorToast(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
postData('/account/logout').then(() => window.location.reload(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sendConstructSpawn(name) {
|
||||||
|
return ws.sendMtxConstructSpawn(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
account,
|
||||||
|
ping,
|
||||||
|
logout,
|
||||||
|
setPassword,
|
||||||
|
sendConstructSpawn,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
class AccountStatus extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
setPassword: { current: '', password: '', confirm: ''},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render(args) {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
ping,
|
||||||
|
logout,
|
||||||
|
setPassword,
|
||||||
|
sendConstructSpawn,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
const passwordsEqual = () =>
|
||||||
|
this.state.setPassword.password === this.state.setPassword.confirm;
|
||||||
|
|
||||||
|
const setPasswordDisabled = () => {
|
||||||
|
const { current, password, confirm } = this.state.setPassword;
|
||||||
|
return !(passwordsEqual() && password && current && confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section class='account'>
|
||||||
|
<div>
|
||||||
|
<h1>{account.name}</h1>
|
||||||
|
<dl>
|
||||||
|
<dt>Subscription</dt>
|
||||||
|
<dd>{account.subscribed ? 'some date' : 'unsubscribed'}</dd>
|
||||||
|
</dl>
|
||||||
|
<button><a href={`mailto:support@mnml.gg?subject=Account%20Support:%20${account.name}`}>✉ support</a></button>
|
||||||
|
<button onClick={() => logout()}>Logout</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="email">Email:</label>
|
||||||
|
<dl>
|
||||||
|
<dt>Current Email</dt>
|
||||||
|
<dd>{account.email ? account.email : 'No email set'}</dd>
|
||||||
|
<dt>Status</dt>
|
||||||
|
<dd>{account.email_confirmed ? 'Confirmed' : 'Unconfirmed'}</dd>
|
||||||
|
</dl>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="new email"
|
||||||
|
/>
|
||||||
|
<button>Update</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="current">Password:</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="password"
|
||||||
|
name="current"
|
||||||
|
value={this.state.setPassword.current}
|
||||||
|
onInput={linkState(this, 'setPassword.current')}
|
||||||
|
placeholder="current"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="password"
|
||||||
|
name="new"
|
||||||
|
value={this.state.setPassword.password}
|
||||||
|
onInput={linkState(this, 'setPassword.password')}
|
||||||
|
placeholder="new password"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="password"
|
||||||
|
name="confirm"
|
||||||
|
value={this.state.setPassword.confirm}
|
||||||
|
onInput={linkState(this, 'setPassword.confirm')}
|
||||||
|
placeholder="confirm"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
disabled={setPasswordDisabled()}
|
||||||
|
onClick={() => setPassword(this.state.setPassword.current, this.state.setPassword.password)}>
|
||||||
|
Set Password
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="list">
|
||||||
|
<figure>
|
||||||
|
<figcaption>spawn new construct</figcaption>
|
||||||
|
<button onClick={() => sendConstructSpawn()} type="submit">
|
||||||
|
¤50
|
||||||
|
</button>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addState(AccountStatus);
|
||||||
33
client/src/components/account.page.jsx
Normal file
33
client/src/components/account.page.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
const { connect } = require('preact-redux');
|
||||||
|
const preact = require('preact');
|
||||||
|
|
||||||
|
const Team = require('./team');
|
||||||
|
const AccountManagement = require('./account.management');
|
||||||
|
|
||||||
|
const addState = connect(
|
||||||
|
function receiveState(state) {
|
||||||
|
const {
|
||||||
|
ws,
|
||||||
|
account,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
account,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function Account(args) {
|
||||||
|
const {
|
||||||
|
account,
|
||||||
|
} = args;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main class="menu">
|
||||||
|
<Team />
|
||||||
|
<AccountManagement />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addState(Account);
|
||||||
@ -1,9 +1,7 @@
|
|||||||
const { connect } = require('preact-redux');
|
const { connect } = require('preact-redux');
|
||||||
const preact = require('preact');
|
const preact = require('preact');
|
||||||
const { Elements, injectStripe } = require('react-stripe-elements');
|
|
||||||
|
|
||||||
const { saw } = require('./shapes');
|
const { saw } = require('./shapes');
|
||||||
const { postData } = require('./../utils');
|
|
||||||
const actions = require('../actions');
|
const actions = require('../actions');
|
||||||
|
|
||||||
function pingColour(ping) {
|
function pingColour(ping) {
|
||||||
@ -12,55 +10,6 @@ function pingColour(ping) {
|
|||||||
return 'red';
|
return 'red';
|
||||||
}
|
}
|
||||||
|
|
||||||
function BitsBtn(args) {
|
|
||||||
const {
|
|
||||||
stripe,
|
|
||||||
account,
|
|
||||||
} = args;
|
|
||||||
function subscribeClick(e) {
|
|
||||||
stripe.redirectToCheckout({
|
|
||||||
items: [{plan: 'plan_FGmRwawcOJJ7Nv', quantity: 1}],
|
|
||||||
successUrl: 'http://localhost/api/payments/success',
|
|
||||||
cancelUrl: 'http://localhost/api/payments/cancel',
|
|
||||||
clientReferenceId: account.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function bitsClick(e) {
|
|
||||||
stripe.redirectToCheckout({
|
|
||||||
items: [{sku: 'sku_FHUfNEhWQaVDaT', quantity: 1}],
|
|
||||||
successUrl: 'http://localhost/payments/success',
|
|
||||||
cancelUrl: 'http://localhost/payments/cancel',
|
|
||||||
clientReferenceId: account.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscription = account.subscribed
|
|
||||||
? <h3 class="account-header">Subscribed</h3>
|
|
||||||
: <button
|
|
||||||
onClick={subscribeClick}
|
|
||||||
class="stripe-btn"
|
|
||||||
role="link">
|
|
||||||
Subscribe
|
|
||||||
</button>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div id="error-message"></div>
|
|
||||||
{subscription}
|
|
||||||
<button
|
|
||||||
onClick={bitsClick}
|
|
||||||
class="stripe-btn"
|
|
||||||
role="link">
|
|
||||||
Get Credits
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const StripeBitsBtn = injectStripe(BitsBtn);
|
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
function receiveState(state) {
|
function receiveState(state) {
|
||||||
const {
|
const {
|
||||||
@ -68,23 +17,28 @@ const addState = connect(
|
|||||||
ping,
|
ping,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
postData('/logout').then(() => window.location.reload(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
account,
|
account,
|
||||||
ping,
|
ping,
|
||||||
logout,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
function receiveDispatch(dispatch) {
|
function receiveDispatch(dispatch) {
|
||||||
function selectConstructs() {
|
function accountPage() {
|
||||||
return dispatch(actions.setNav('team'));
|
dispatch(actions.setGame(null));
|
||||||
|
dispatch(actions.setInstance(null));
|
||||||
|
dispatch(actions.setCombiner([]));
|
||||||
|
dispatch(actions.setReclaiming(false));
|
||||||
|
dispatch(actions.setActiveSkill(null));
|
||||||
|
dispatch(actions.setActiveConstruct(null));
|
||||||
|
dispatch(actions.setInfo(null));
|
||||||
|
dispatch(actions.setItemEquip(null));
|
||||||
|
dispatch(actions.setItemUnequip([]));
|
||||||
|
dispatch(actions.setVboxHighlight([]));
|
||||||
|
return dispatch(actions.setNav('account'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectConstructs,
|
accountPage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -94,25 +48,20 @@ function AccountStatus(args) {
|
|||||||
const {
|
const {
|
||||||
account,
|
account,
|
||||||
ping,
|
ping,
|
||||||
logout,
|
accountPage,
|
||||||
selectConstructs,
|
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="account">
|
<div class="account-status">
|
||||||
<div class="account-status">
|
<div class="account-info">
|
||||||
<h2 class="account-header">{account.name}</h2>
|
<h2 class="account-header">{account.name}</h2>
|
||||||
{saw(pingColour(ping))}
|
{saw(pingColour(ping))}
|
||||||
<div class="ping-text">{ping}ms</div>
|
<div class="ping-text">{ping}ms</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="account-header">{`¤${account.balance}`}</h3>
|
<h3 class="account-header credits">{`¤${account.balance}`}</h3>
|
||||||
<Elements>
|
<button onClick={() => accountPage()}>⚙ account</button>
|
||||||
<StripeBitsBtn account={account} />
|
|
||||||
</Elements>
|
|
||||||
<button onClick={() => selectConstructs()}>Constructs </button>
|
|
||||||
<button onClick={() => logout()}>Logout</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,12 +10,14 @@ const addState = connect(
|
|||||||
function receiveState(state) {
|
function receiveState(state) {
|
||||||
const {
|
const {
|
||||||
ws,
|
ws,
|
||||||
|
account,
|
||||||
game,
|
game,
|
||||||
instance,
|
instance,
|
||||||
nav,
|
nav,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
account,
|
||||||
game,
|
game,
|
||||||
instance,
|
instance,
|
||||||
nav,
|
nav,
|
||||||
@ -26,15 +28,18 @@ const addState = connect(
|
|||||||
function Controls(args) {
|
function Controls(args) {
|
||||||
const {
|
const {
|
||||||
game,
|
game,
|
||||||
|
account,
|
||||||
instance,
|
instance,
|
||||||
nav,
|
nav,
|
||||||
sendGameReady,
|
sendGameReady,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
|
if (!account) return false;
|
||||||
|
|
||||||
if (game) return <GameCtrl />;
|
if (game) return <GameCtrl />;
|
||||||
if (instance) return <InstanceCtrl />;
|
if (instance) return <InstanceCtrl />;
|
||||||
if (nav === 'play' || !nav) return <PlayCtrl />
|
if (nav === 'play' || !nav) return <PlayCtrl />
|
||||||
if (nav === 'team' || !nav) return <TeamCtrl />
|
if (nav === 'team' || nav === 'account') return <TeamCtrl />
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,16 +20,22 @@ const addState = connect(
|
|||||||
return ws.sendGameReady(game.id);
|
return ws.sendGameReady(game.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInstanceState() {
|
||||||
|
return ws.sendInstanceState(game.instance);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
game,
|
game,
|
||||||
sendReady,
|
sendReady,
|
||||||
account,
|
account,
|
||||||
|
getInstanceState,
|
||||||
animating,
|
animating,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
function receiveDispatch(dispatch) {
|
function receiveDispatch(dispatch) {
|
||||||
function quit() {
|
function quit() {
|
||||||
|
dispatch(actions.setNav('transition'));
|
||||||
dispatch(actions.setGame(null));
|
dispatch(actions.setGame(null));
|
||||||
dispatch(actions.setInstance(null));
|
dispatch(actions.setInstance(null));
|
||||||
}
|
}
|
||||||
@ -74,6 +80,7 @@ function Controls(args) {
|
|||||||
animating,
|
animating,
|
||||||
sendReady,
|
sendReady,
|
||||||
quit,
|
quit,
|
||||||
|
getInstanceState,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
if (!game) return false;
|
if (!game) return false;
|
||||||
@ -81,11 +88,43 @@ function Controls(args) {
|
|||||||
const opponent = game.players.find(t => t.id !== account.id);
|
const opponent = game.players.find(t => t.id !== account.id);
|
||||||
const player = game.players.find(t => t.id === account.id);
|
const player = game.players.find(t => t.id === account.id);
|
||||||
|
|
||||||
|
function quitClick() {
|
||||||
|
getInstanceState();
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const zero = Date.parse(game.phase_start);
|
||||||
|
const now = Date.now();
|
||||||
|
const end = Date.parse(game.phase_end);
|
||||||
|
const timerPct = game.phase_end
|
||||||
|
? ((now - zero) / (end - zero) * 100)
|
||||||
|
: 100;
|
||||||
|
|
||||||
|
const displayColour = !game.phase_end
|
||||||
|
? '#222'
|
||||||
|
: player.ready
|
||||||
|
? 'forestgreen'
|
||||||
|
: timerPct > 80
|
||||||
|
? 'red'
|
||||||
|
: 'whitesmoke';
|
||||||
|
|
||||||
|
const timerStyles = {
|
||||||
|
height: `${timerPct > 100 ? 100 : timerPct}%`,
|
||||||
|
background: displayColour,
|
||||||
|
};
|
||||||
|
|
||||||
|
const timer = (
|
||||||
|
<div class="timer-container">
|
||||||
|
<div class="timer" style={timerStyles} > </div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const readyBtn = <button disabled={animating} class="ready" onClick={() => sendReady()}>Ready</button>;
|
const readyBtn = <button disabled={animating} class="ready" onClick={() => sendReady()}>Ready</button>;
|
||||||
const quitBtn = <button disabled={animating} onClick={() => quit()}>Back</button>;
|
const quitBtn = <button disabled={animating} onClick={quitClick}>Back</button>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside class="controls">
|
<aside class="controls">
|
||||||
|
{timer}
|
||||||
{scoreboard(game, opponent, true)}
|
{scoreboard(game, opponent, true)}
|
||||||
{game.phase === 'Finish' ? quitBtn : readyBtn}
|
{game.phase === 'Finish' ? quitBtn : readyBtn}
|
||||||
{scoreboard(game, player)}
|
{scoreboard(game, player)}
|
||||||
|
|||||||
@ -66,8 +66,35 @@ function Controls(args) {
|
|||||||
const opponent = instance.players.find(t => t.id !== account.id);
|
const opponent = instance.players.find(t => t.id !== account.id);
|
||||||
const player = instance.players.find(t => t.id === account.id);
|
const player = instance.players.find(t => t.id === account.id);
|
||||||
|
|
||||||
|
const zero = Date.parse(instance.phase_start);
|
||||||
|
const now = Date.now();
|
||||||
|
const end = Date.parse(instance.phase_end);
|
||||||
|
const timerPct = instance.phase_end
|
||||||
|
? ((now - zero) / (end - zero) * 100)
|
||||||
|
: 100;
|
||||||
|
|
||||||
|
const displayColour = !instance.phase_end
|
||||||
|
? '#222'
|
||||||
|
: player.ready
|
||||||
|
? 'forestgreen'
|
||||||
|
: timerPct > 80
|
||||||
|
? 'red'
|
||||||
|
: 'whitesmoke';
|
||||||
|
|
||||||
|
const timerStyles = {
|
||||||
|
height: `${timerPct > 100 ? 100 : timerPct}%`,
|
||||||
|
background: displayColour,
|
||||||
|
};
|
||||||
|
|
||||||
|
const timer = (
|
||||||
|
<div class="timer-container">
|
||||||
|
<div class="timer" style={timerStyles} > </div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside class="controls">
|
<aside class="controls">
|
||||||
|
{timer}
|
||||||
{scoreboard(instance, opponent, true)}
|
{scoreboard(instance, opponent, true)}
|
||||||
<button class="ready" onClick={() => sendReady()}>Ready</button>
|
<button class="ready" onClick={() => sendReady()}>Ready</button>
|
||||||
{scoreboard(instance, player)}
|
{scoreboard(instance, player)}
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
const { connect } = require('preact-redux');
|
const { connect } = require('preact-redux');
|
||||||
|
const { Elements } = require('react-stripe-elements');
|
||||||
|
|
||||||
const preact = require('preact');
|
const preact = require('preact');
|
||||||
const toast = require('izitoast');
|
const toast = require('izitoast');
|
||||||
|
|
||||||
const actions = require('./../actions');
|
const actions = require('./../actions');
|
||||||
|
const StripeBtns = require('./stripe.buttons');
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
function receiveState(state) {
|
function receiveState(state) {
|
||||||
@ -63,15 +66,18 @@ function Inventory(args) {
|
|||||||
return (
|
return (
|
||||||
<div class="inventory">
|
<div class="inventory">
|
||||||
<div>
|
<div>
|
||||||
<h1>¤ {account.balance}</h1>
|
<h1>Shop</h1>
|
||||||
|
<Elements>
|
||||||
|
<StripeBtns account={account} />
|
||||||
|
</Elements>
|
||||||
<div class='list'>
|
<div class='list'>
|
||||||
{shop.owned.map(useMtx)}
|
{shop.available.map(availableMtx)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1>Shop</h1>
|
<h1 class="credits">¤ {account.balance}</h1>
|
||||||
<div class='list'>
|
<div class='list'>
|
||||||
{shop.available.map(availableMtx)}
|
{shop.owned.map(useMtx)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
const preact = require('preact');
|
const preact = require('preact');
|
||||||
const { Component } = require('preact')
|
const { Component } = require('preact')
|
||||||
const { connect } = require('preact-redux');
|
const { connect } = require('preact-redux');
|
||||||
|
const linkState = require('linkstate').default;
|
||||||
|
|
||||||
const { postData, errorToast } = require('../utils');
|
const { postData, errorToast } = require('../utils');
|
||||||
|
|
||||||
@ -11,10 +12,10 @@ const addState = connect(
|
|||||||
ws
|
ws
|
||||||
} = state;
|
} = state;
|
||||||
function submitLogin(name, password) {
|
function submitLogin(name, password) {
|
||||||
postData('/login', { name, password })
|
postData('/account/login', { name, password })
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.success) return errorToast(data.error_message);
|
if (data.error) return errorToast(data.error);
|
||||||
console.log(data.response);
|
console.log(data.response);
|
||||||
ws.connect();
|
ws.connect();
|
||||||
})
|
})
|
||||||
@ -22,10 +23,10 @@ const addState = connect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function submitRegister(name, password, code) {
|
function submitRegister(name, password, code) {
|
||||||
postData('/register', { name, password, code })
|
postData('/account/register', { name, password, code })
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.success) return errorToast(data.error_message);
|
if (data.error) return errorToast(data.error);
|
||||||
console.log(data.response);
|
console.log(data.response);
|
||||||
ws.connect();
|
ws.connect();
|
||||||
})
|
})
|
||||||
@ -43,87 +44,122 @@ class Login extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = { name: '', password: '', code: ''};
|
this.state = {
|
||||||
|
login: { name: '', password: '', code: ''},
|
||||||
|
register: { name: '', password: '', confirm: '', code: ''},
|
||||||
|
};
|
||||||
|
|
||||||
this.nameInput = this.nameInput.bind(this);
|
|
||||||
this.passwordInput = this.passwordInput.bind(this);
|
|
||||||
this.codeInput = this.codeInput.bind(this);
|
|
||||||
this.loginSubmit = this.loginSubmit.bind(this);
|
this.loginSubmit = this.loginSubmit.bind(this);
|
||||||
this.registerSubmit = this.registerSubmit.bind(this);
|
this.registerSubmit = this.registerSubmit.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
nameInput(event) {
|
|
||||||
this.setState({ name: event.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordInput(event) {
|
|
||||||
this.setState({ password: event.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
codeInput(event) {
|
|
||||||
this.setState({ code: event.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
loginSubmit(event) {
|
loginSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log(this.state);
|
console.log(this.state);
|
||||||
this.props.submitLogin(this.state.name, this.state.password);
|
this.props.submitLogin(this.state.login.name, this.state.login.password);
|
||||||
this.setState({ name: '', password: '' });
|
this.setState({ login: { name: '', password: '' }});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSubmit(event) {
|
registerSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.props.submitRegister(this.state.name, this.state.password, this.state.code);
|
this.props.submitRegister(this.state.register.name, this.state.register.password, this.state.register.code);
|
||||||
console.log(this.state);
|
this.setState({ register: { name: '', password: '', confirm: '', code: ''}});
|
||||||
this.setState({ name: '', password: '', code: ''});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const registerConfirm = () =>
|
||||||
|
this.state.register.password === this.state.register.confirm;
|
||||||
|
|
||||||
|
const loginDisabled = () => {
|
||||||
|
const { password, name } = this.state.login;
|
||||||
|
return !(password && name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerDisabled = () => {
|
||||||
|
const { password, name, code } = this.state.register;
|
||||||
|
return !(registerConfirm() && password && name && code);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<h1 class="mobile-title" >mnml.gg</h1>
|
<h1>mnml.gg</h1>
|
||||||
<div class="login">
|
<div class="login">
|
||||||
|
<div>mnml is an abstract turn based strategy game</div>
|
||||||
|
<div>free to play</div>
|
||||||
|
<div>no email required</div>
|
||||||
|
</div>
|
||||||
|
<div class="login">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<label for="username">Username</label>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="username"
|
placeholder="username"
|
||||||
tabIndex={1}
|
tabIndex={1}
|
||||||
value={this.state.name}
|
value={this.state.login.name}
|
||||||
onInput={this.nameInput}
|
onInput={linkState(this, 'login.name')}
|
||||||
/>
|
/>
|
||||||
|
<label for="password">Password</label>
|
||||||
<input
|
<input
|
||||||
class="login-input"
|
class="login-input"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="password"
|
placeholder="password"
|
||||||
tabIndex={2}
|
tabIndex={2}
|
||||||
value={this.state.password}
|
value={this.state.login.password}
|
||||||
onInput={this.passwordInput}
|
onInput={linkState(this, 'login.password')}
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="login-input"
|
|
||||||
type="text"
|
|
||||||
placeholder="code"
|
|
||||||
tabIndex={3}
|
|
||||||
value={this.state.code}
|
|
||||||
onInput={this.codeInput}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="login-btn"
|
class="login-btn"
|
||||||
tabIndex={4}
|
tabIndex={4}
|
||||||
|
disabled={loginDisabled()}
|
||||||
onClick={this.loginSubmit}>
|
onClick={this.loginSubmit}>
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="login">
|
||||||
|
<h2>Register</h2>
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="email"
|
||||||
|
placeholder="username"
|
||||||
|
value={this.state.register.name}
|
||||||
|
onInput={linkState(this, 'register.name')}
|
||||||
|
/>
|
||||||
|
<label for="password">Password - min 12 chars</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="password"
|
||||||
|
placeholder="password"
|
||||||
|
value={this.state.register.password}
|
||||||
|
onInput={linkState(this, 'register.password')}
|
||||||
|
/>
|
||||||
|
<label for="confirm">Confirm Password</label>
|
||||||
|
<input
|
||||||
|
class={`${registerConfirm() ? '' : 'red'} login-input`}
|
||||||
|
type="password"
|
||||||
|
placeholder="confirm"
|
||||||
|
value={this.state.register.confirm}
|
||||||
|
onInput={linkState(this, 'register.confirm')}
|
||||||
|
/>
|
||||||
|
<label for="code">Access Code</label>
|
||||||
|
<input
|
||||||
|
class="login-input"
|
||||||
|
type="text"
|
||||||
|
placeholder="code"
|
||||||
|
value={this.state.register.code}
|
||||||
|
onInput={linkState(this, 'register.code')}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="login-btn"
|
class="login-btn"
|
||||||
tabIndex={5}
|
disabled={registerDisabled()}
|
||||||
onClick={this.registerSubmit}>
|
onClick={this.registerSubmit}>
|
||||||
Register
|
Register
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="login-btn"
|
class="login-btn"
|
||||||
onClick={() => document.location.assign('https://discord.gg/YJJgurM')}>
|
onClick={() => document.location.assign('https://discord.gg/YJJgurM')}>
|
||||||
Discord + Invites
|
Discord + Codes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ const Game = require('./game');
|
|||||||
const Instance = require('./instance.component');
|
const Instance = require('./instance.component');
|
||||||
const Team = require('./team');
|
const Team = require('./team');
|
||||||
const Play = require('./play');
|
const Play = require('./play');
|
||||||
|
const Account = require('./account.page');
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
state => {
|
state => {
|
||||||
@ -38,6 +39,7 @@ function Main(props) {
|
|||||||
if (nav === 'transition') return false;
|
if (nav === 'transition') return false;
|
||||||
if (nav === 'play') return <Play />;
|
if (nav === 'play') return <Play />;
|
||||||
if (nav === 'team') return <Team />;
|
if (nav === 'team') return <Team />;
|
||||||
|
if (nav === 'account') return <Account />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Play />
|
<Play />
|
||||||
|
|||||||
@ -69,6 +69,8 @@ function Nav(args) {
|
|||||||
hideNav,
|
hideNav,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
|
if (!account) return false;
|
||||||
|
|
||||||
function navTo(p) {
|
function navTo(p) {
|
||||||
return setNav(p);
|
return setNav(p);
|
||||||
}
|
}
|
||||||
@ -90,6 +92,7 @@ function Nav(args) {
|
|||||||
|
|
||||||
const canJoin = team.some(c => !c);
|
const canJoin = team.some(c => !c);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav onClick={hideNav} >
|
<nav onClick={hideNav} >
|
||||||
<h1 class="header-title">mnml.gg</h1>
|
<h1 class="header-title">mnml.gg</h1>
|
||||||
|
|||||||
@ -27,7 +27,8 @@ function JoinButtons(args) {
|
|||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside>
|
<aside class='play-ctrl'>
|
||||||
|
<div class="timer-container"></div>
|
||||||
<button
|
<button
|
||||||
class='pvp ready'
|
class='pvp ready'
|
||||||
onClick={() => sendInstanceQueue()}
|
onClick={() => sendInstanceQueue()}
|
||||||
|
|||||||
@ -97,7 +97,7 @@ function Play(args) {
|
|||||||
if (mtxActive === 'Rename') return setConstructRename(construct.id);
|
if (mtxActive === 'Rename') return setConstructRename(construct.id);
|
||||||
return sendConstructAvatarReroll(construct.id);
|
return sendConstructAvatarReroll(construct.id);
|
||||||
}}
|
}}
|
||||||
class="menu-construct" >
|
class="construct">
|
||||||
<ConstructAvatar construct={construct} />
|
<ConstructAvatar construct={construct} />
|
||||||
{constructName}
|
{constructName}
|
||||||
{confirm}
|
{confirm}
|
||||||
@ -107,8 +107,8 @@ function Play(args) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main class="play">
|
<main class="menu">
|
||||||
<div class="team">
|
<div class="team top">
|
||||||
{constructPanels}
|
{constructPanels}
|
||||||
</div>
|
</div>
|
||||||
<Inventory />
|
<Inventory />
|
||||||
|
|||||||
54
client/src/components/stripe.buttons.jsx
Normal file
54
client/src/components/stripe.buttons.jsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const preact = require('preact');
|
||||||
|
const { injectStripe } = require('react-stripe-elements');
|
||||||
|
|
||||||
|
function BitsBtn(args) {
|
||||||
|
const {
|
||||||
|
stripe,
|
||||||
|
account,
|
||||||
|
} = args;
|
||||||
|
function subscribeClick() {
|
||||||
|
stripe.redirectToCheckout({
|
||||||
|
items: [{ plan: 'plan_FGmRwawcOJJ7Nv', quantity: 1 }],
|
||||||
|
successUrl: 'http://localhost',
|
||||||
|
cancelUrl: 'http://localhost',
|
||||||
|
clientReferenceId: account.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bitsClick() {
|
||||||
|
stripe.redirectToCheckout({
|
||||||
|
items: [{ sku: 'sku_FHUfNEhWQaVDaT', quantity: 1 }],
|
||||||
|
successUrl: 'http://localhost',
|
||||||
|
cancelUrl: 'http://localhost',
|
||||||
|
clientReferenceId: account.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = account.subscribed
|
||||||
|
? <button
|
||||||
|
class="stripe-btn"
|
||||||
|
disabled>
|
||||||
|
Subscribed
|
||||||
|
</button>
|
||||||
|
: <button
|
||||||
|
onClick={subscribeClick}
|
||||||
|
class="stripe-btn"
|
||||||
|
role="link">
|
||||||
|
Subscribe
|
||||||
|
</button>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class='list'>
|
||||||
|
{subscription}
|
||||||
|
<button
|
||||||
|
onClick={bitsClick}
|
||||||
|
class="stripe-btn"
|
||||||
|
role="link">
|
||||||
|
Get Credits
|
||||||
|
</button>
|
||||||
|
<div id="error-message"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = injectStripe(BitsBtn);
|
||||||
@ -29,6 +29,7 @@ function TeamCtrl(args) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside>
|
<aside>
|
||||||
|
<div class="timer-container"></div>
|
||||||
<button
|
<button
|
||||||
class='ready'
|
class='ready'
|
||||||
disabled={teamSelect.some(c => !c)}
|
disabled={teamSelect.some(c => !c)}
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
const preact = require('preact');
|
const preact = require('preact');
|
||||||
const { connect } = require('preact-redux');
|
const { connect } = require('preact-redux');
|
||||||
const range = require('lodash/range');
|
|
||||||
|
|
||||||
const actions = require('./../actions');
|
const actions = require('./../actions');
|
||||||
const { COLOURS } = require('./../utils');
|
const { COLOURS } = require('./../utils');
|
||||||
const { stringSort } = require('./../utils');
|
|
||||||
const SpawnButton = require('./spawn.button');
|
|
||||||
const { ConstructAvatar } = require('./construct');
|
const { ConstructAvatar } = require('./construct');
|
||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
@ -19,7 +16,6 @@ const addState = connect(
|
|||||||
return {
|
return {
|
||||||
constructs,
|
constructs,
|
||||||
teamSelect,
|
teamSelect,
|
||||||
sendConstructSpawn,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
function receiveDispatch(dispatch) {
|
function receiveDispatch(dispatch) {
|
||||||
@ -31,7 +27,6 @@ const addState = connect(
|
|||||||
setTeam,
|
setTeam,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function Team(args) {
|
function Team(args) {
|
||||||
@ -39,7 +34,6 @@ function Team(args) {
|
|||||||
constructs,
|
constructs,
|
||||||
teamSelect,
|
teamSelect,
|
||||||
setTeam,
|
setTeam,
|
||||||
sendConstructSpawn,
|
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
if (!constructs) return <div></div>;
|
if (!constructs) return <div></div>;
|
||||||
@ -70,29 +64,19 @@ function Team(args) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={construct.id}
|
key={construct.id}
|
||||||
class="menu-construct"
|
class="construct team-select"
|
||||||
style={ { 'border-color': borderColour || 'whitesmoke' } }
|
style={ { 'border-color': borderColour || 'whitesmoke' } }
|
||||||
onClick={() => selectConstruct(construct.id)} >
|
onClick={() => selectConstruct(construct.id)} >
|
||||||
<div class="controls">
|
|
||||||
<h2>
|
|
||||||
{construct.name}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<ConstructAvatar construct={construct} />
|
<ConstructAvatar construct={construct} />
|
||||||
|
<h2>{construct.name}</h2>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const spawnButtonsNum = (3 - constructs.length % 3);
|
|
||||||
|
|
||||||
const spawnButtons = range(spawnButtonsNum)
|
|
||||||
.map(i => <SpawnButton key={constructs.length + i} spawn={() => sendConstructSpawn()} />);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main class="team">
|
<section class="team top">
|
||||||
{constructPanels}
|
{constructPanels}
|
||||||
{spawnButtons}
|
</section>
|
||||||
</main>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -213,6 +213,15 @@ function errorToast(message) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function infoToast(message) {
|
||||||
|
toast.info({
|
||||||
|
position: 'topRight',
|
||||||
|
drag: false,
|
||||||
|
close: false,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function convertItem(v) {
|
function convertItem(v) {
|
||||||
if (['Red', 'Green', 'Blue'].includes(v)) {
|
if (['Red', 'Green', 'Blue'].includes(v)) {
|
||||||
return (
|
return (
|
||||||
@ -233,6 +242,7 @@ module.exports = {
|
|||||||
postData,
|
postData,
|
||||||
convertItem,
|
convertItem,
|
||||||
errorToast,
|
errorToast,
|
||||||
|
infoToast,
|
||||||
NULL_UUID,
|
NULL_UUID,
|
||||||
STATS,
|
STATS,
|
||||||
COLOURS,
|
COLOURS,
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
error_log /var/log/mnml/nginx.log;
|
|
||||||
|
|
||||||
upstream mnml_http {
|
upstream mnml_http {
|
||||||
server 127.0.0.1:40000;
|
server 127.0.0.1:40000;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
upstream mnml_dev {
|
upstream mnml_http {
|
||||||
server 0.0.0.0:41337;
|
server 127.0.0.1:40000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstream mnml_ws {
|
||||||
|
server 127.0.0.1:40055;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default upgrade;
|
default upgrade;
|
||||||
'' close;
|
'' close;
|
||||||
@ -9,25 +14,19 @@ map $http_upgrade $connection_upgrade {
|
|||||||
|
|
||||||
# DEV
|
# DEV
|
||||||
server {
|
server {
|
||||||
root /var/lib/mnml/public/;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
server_name dev.mnml.gg; # managed by Certbot
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
root /var/lib/mnml/public/current;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /imgs/ {
|
||||||
|
root /var/lib/mnml/public/;
|
||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen [::]:443;
|
|
||||||
ssl on;
|
|
||||||
listen 443 ssl; # managed by Certbot
|
|
||||||
ssl_certificate /etc/letsencrypt/live/dev.mnml.gg/fullchain.pem; # managed by Certbot
|
|
||||||
ssl_certificate_key /etc/letsencrypt/live/dev.mnml.gg/privkey.pem; # managed by Certbot
|
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
|
||||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
|
||||||
|
|
||||||
location /api/ws {
|
location /api/ws {
|
||||||
proxy_pass http://mnml_dev;
|
proxy_pass http://mnml_ws;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection $connection_upgrade;
|
proxy_set_header Connection $connection_upgrade;
|
||||||
@ -35,15 +34,35 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://mnml_dev;
|
proxy_pass http://mnml_http;
|
||||||
proxy_read_timeout 600s;
|
proxy_read_timeout 600s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/mnml.gg/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/mnml.gg/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
server_name grep.live;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /var/lib/mnml/public/current/;
|
||||||
|
index acp.html;
|
||||||
|
try_files $uri $uri/ acp.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/grep.live/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/grep.live/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
}
|
}
|
||||||
|
|
||||||
# http -> https
|
# http -> https
|
||||||
server {
|
server {
|
||||||
server_name dev.mnml.gg;
|
|
||||||
return 301 https://$host$request_uri;
|
return 301 https://$host$request_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ iron = "0.6"
|
|||||||
bodyparser = "0.8"
|
bodyparser = "0.8"
|
||||||
persistent = "0.4"
|
persistent = "0.4"
|
||||||
router = "0.6"
|
router = "0.6"
|
||||||
|
mount = "0.4"
|
||||||
cookie = "0.12"
|
cookie = "0.12"
|
||||||
crossbeam-channel = "0.3"
|
crossbeam-channel = "0.3"
|
||||||
ws = "0.8"
|
ws = "0.8"
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use serde_cbor::{from_slice};
|
|||||||
|
|
||||||
use postgres::transaction::Transaction;
|
use postgres::transaction::Transaction;
|
||||||
|
|
||||||
use net::MnmlHttpError;
|
use http::MnmlHttpError;
|
||||||
use names::{name as generate_name};
|
use names::{name as generate_name};
|
||||||
use construct::{Construct, construct_recover, construct_spawn};
|
use construct::{Construct, construct_recover, construct_spawn};
|
||||||
use instance::{Instance, instance_delete};
|
use instance::{Instance, instance_delete};
|
||||||
@ -29,6 +29,23 @@ pub struct Account {
|
|||||||
pub subscribed: bool,
|
pub subscribed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<postgres::rows::Row<'a>> for Account {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(row: postgres::rows::Row) -> Result<Self, Error> {
|
||||||
|
let id: Uuid = row.get("id");
|
||||||
|
|
||||||
|
let db_balance: i64 = row.get("balance");
|
||||||
|
let balance = u32::try_from(db_balance)
|
||||||
|
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
|
||||||
|
|
||||||
|
let subscribed: bool = row.get("subscribed");
|
||||||
|
let name: String = row.get("name");
|
||||||
|
|
||||||
|
Ok(Account { id, name, balance, subscribed })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select(db: &Db, id: Uuid) -> Result<Account, Error> {
|
pub fn select(db: &Db, id: Uuid) -> Result<Account, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT id, name, balance, subscribed
|
SELECT id, name, balance, subscribed
|
||||||
@ -42,13 +59,23 @@ pub fn select(db: &Db, id: Uuid) -> Result<Account, Error> {
|
|||||||
let row = result.iter().next()
|
let row = result.iter().next()
|
||||||
.ok_or(format_err!("account not found {:?}", id))?;
|
.ok_or(format_err!("account not found {:?}", id))?;
|
||||||
|
|
||||||
let db_balance: i64 = row.get(2);
|
Account::try_from(row)
|
||||||
let balance = u32::try_from(db_balance)
|
}
|
||||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
|
|
||||||
|
|
||||||
let subscribed: bool = row.get(3);
|
pub fn select_name(db: &Db, name: &String) -> Result<Account, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT id, name, balance, subscribed
|
||||||
|
FROM accounts
|
||||||
|
WHERE name = $1;
|
||||||
|
";
|
||||||
|
|
||||||
Ok(Account { id, name: row.get(1), balance, subscribed })
|
let result = db
|
||||||
|
.query(query, &[&name])?;
|
||||||
|
|
||||||
|
let row = result.iter().next()
|
||||||
|
.ok_or(format_err!("account not found name={:?}", name))?;
|
||||||
|
|
||||||
|
Account::try_from(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_token(db: &Db, token: String) -> Result<Account, Error> {
|
pub fn from_token(db: &Db, token: String) -> Result<Account, Error> {
|
||||||
@ -65,15 +92,7 @@ pub fn from_token(db: &Db, token: String) -> Result<Account, Error> {
|
|||||||
let row = result.iter().next()
|
let row = result.iter().next()
|
||||||
.ok_or(err_msg("invalid token"))?;
|
.ok_or(err_msg("invalid token"))?;
|
||||||
|
|
||||||
let id: Uuid = row.get(0);
|
Account::try_from(row)
|
||||||
let name: String = row.get(1);
|
|
||||||
let subscribed: bool = row.get(2);
|
|
||||||
let db_balance: i64 = row.get(3);
|
|
||||||
|
|
||||||
let balance = u32::try_from(db_balance)
|
|
||||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
|
|
||||||
|
|
||||||
Ok(Account { id, name, balance, subscribed })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<Account, MnmlHttpError> {
|
pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<Account, MnmlHttpError> {
|
||||||
@ -101,23 +120,16 @@ pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<A
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let id: Uuid = row.get(0);
|
|
||||||
let hash: String = row.get(1);
|
let hash: String = row.get(1);
|
||||||
let name: String = row.get(2);
|
|
||||||
let db_balance: i64 = row.get(3);
|
|
||||||
let subscribed: bool = row.get(4);
|
|
||||||
|
|
||||||
if !verify(password, &hash)? {
|
if !verify(password, &hash)? {
|
||||||
return Err(MnmlHttpError::PasswordNotMatch);
|
return Err(MnmlHttpError::PasswordNotMatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
let balance = u32::try_from(db_balance)
|
Account::try_from(row)
|
||||||
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
|
.or(Err(MnmlHttpError::ServerError))
|
||||||
|
|
||||||
Ok(Account { id, name, balance, subscribed })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, Error> {
|
pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, MnmlHttpError> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
let token: String = iter::repeat(())
|
let token: String = iter::repeat(())
|
||||||
.map(|()| rng.sample(Alphanumeric))
|
.map(|()| rng.sample(Alphanumeric))
|
||||||
@ -136,11 +148,73 @@ pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, Error> {
|
|||||||
.query(query, &[&token, &id])?;
|
.query(query, &[&token, &id])?;
|
||||||
|
|
||||||
result.iter().next()
|
result.iter().next()
|
||||||
.ok_or(format_err!("account not updated {:?}", id))?;
|
.ok_or(MnmlHttpError::Unauthorized)?;
|
||||||
|
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_password(tx: &mut Transaction, id: Uuid, current: &String, password: &String) -> Result<String, MnmlHttpError> {
|
||||||
|
if password.len() < PASSWORD_MIN_LEN {
|
||||||
|
return Err(MnmlHttpError::PasswordUnacceptable);
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
SELECT id, password
|
||||||
|
FROM accounts
|
||||||
|
WHERE id = $1
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&id])?;
|
||||||
|
|
||||||
|
let row = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => {
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let garbage: String = iter::repeat(())
|
||||||
|
.map(|()| rng.sample(Alphanumeric))
|
||||||
|
.take(64)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// verify garbage to prevent timing attacks
|
||||||
|
verify(garbage.clone(), &garbage).ok();
|
||||||
|
return Err(MnmlHttpError::AccountNotFound);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let id: Uuid = row.get(0);
|
||||||
|
let db_pw: String = row.get(1);
|
||||||
|
|
||||||
|
if !verify(current, &db_pw)? {
|
||||||
|
return Err(MnmlHttpError::PasswordNotMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rounds = 8;
|
||||||
|
let password = hash(&password, rounds)?;
|
||||||
|
|
||||||
|
let query = "
|
||||||
|
UPDATE accounts
|
||||||
|
SET password = $1, updated_at = now()
|
||||||
|
WHERE id = $2
|
||||||
|
RETURNING id, name;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = tx
|
||||||
|
.query(query, &[&password, &id])?;
|
||||||
|
|
||||||
|
let row = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => return Err(MnmlHttpError::DbError),
|
||||||
|
};
|
||||||
|
|
||||||
|
let name: String = row.get(1);
|
||||||
|
|
||||||
|
info!("password updated name={:?} id={:?}", name, id);
|
||||||
|
|
||||||
|
new_token(tx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn credit(tx: &mut Transaction, id: Uuid, credits: i64) -> Result<String, Error> {
|
pub fn credit(tx: &mut Transaction, id: Uuid, credits: i64) -> Result<String, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
UPDATE accounts
|
UPDATE accounts
|
||||||
|
|||||||
125
server/src/acp.rs
Normal file
125
server/src/acp.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
use iron::prelude::*;
|
||||||
|
use iron::status;
|
||||||
|
|
||||||
|
use iron::{BeforeMiddleware};
|
||||||
|
use persistent::Read;
|
||||||
|
use router::Router;
|
||||||
|
|
||||||
|
use serde::{Deserialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use account;
|
||||||
|
use game;
|
||||||
|
|
||||||
|
use http::{State, MnmlHttpError, json_object};
|
||||||
|
|
||||||
|
struct AcpMiddleware;
|
||||||
|
impl BeforeMiddleware for AcpMiddleware {
|
||||||
|
fn before(&self, req: &mut Request) -> IronResult<()> {
|
||||||
|
match req.extensions.get::<account::Account>() {
|
||||||
|
Some(a) => {
|
||||||
|
if ["ntr", "mashy"].contains(&a.name.to_ascii_lowercase().as_ref()) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(MnmlHttpError::Unauthorized.into());
|
||||||
|
},
|
||||||
|
None => Err(MnmlHttpError::Unauthorized.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,Deserialize)]
|
||||||
|
struct GetUser {
|
||||||
|
name: Option<String>,
|
||||||
|
id: Option<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acp_user(req: &mut Request) -> IronResult<Response> {
|
||||||
|
let state = req.get::<Read<State>>().unwrap();
|
||||||
|
let params = match req.get::<bodyparser::Struct<GetUser>>() {
|
||||||
|
Ok(Some(b)) => b,
|
||||||
|
_ => return Err(MnmlHttpError::BadRequest.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
|
let user = match params.id {
|
||||||
|
Some(id) => account::select(&db, id)
|
||||||
|
.or(Err(MnmlHttpError::NotFound))?,
|
||||||
|
|
||||||
|
None => match params.name {
|
||||||
|
Some(n) => account::select_name(&db, &n)
|
||||||
|
.or(Err(MnmlHttpError::NotFound))?,
|
||||||
|
None => return Err(MnmlHttpError::BadRequest.into()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(json_object(status::Ok, serde_json::to_string(&user).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,Deserialize)]
|
||||||
|
struct GetGame {
|
||||||
|
id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acp_game(req: &mut Request) -> IronResult<Response> {
|
||||||
|
let state = req.get::<Read<State>>().unwrap();
|
||||||
|
let params = match req.get::<bodyparser::Struct<GetGame>>() {
|
||||||
|
Ok(Some(b)) => b,
|
||||||
|
_ => return Err(MnmlHttpError::BadRequest.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
|
let game = game::select(&db, params.id)
|
||||||
|
.or(Err(MnmlHttpError::NotFound))?;
|
||||||
|
|
||||||
|
Ok(json_object(status::Ok, serde_json::to_string(&game).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,Deserialize)]
|
||||||
|
struct GameList {
|
||||||
|
number: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_list(req: &mut Request) -> IronResult<Response> {
|
||||||
|
let state = req.get::<Read<State>>().unwrap();
|
||||||
|
let params = match req.get::<bodyparser::Struct<GameList>>() {
|
||||||
|
Ok(Some(b)) => b,
|
||||||
|
_ => return Err(MnmlHttpError::BadRequest.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
|
let list = game::list(&db, params.number)
|
||||||
|
.or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
|
Ok(json_object(status::Ok, serde_json::to_string(&list).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn game_open(req: &mut Request) -> IronResult<Response> {
|
||||||
|
let state = req.get::<Read<State>>().unwrap();
|
||||||
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
|
let list = game::games_need_upkeep(&mut tx)
|
||||||
|
.or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
|
tx.commit()
|
||||||
|
.or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
|
Ok(json_object(status::Ok, serde_json::to_string(&list).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn acp_mount() -> Chain {
|
||||||
|
let mut router = Router::new();
|
||||||
|
router.post("user", acp_user, "acp_user");
|
||||||
|
router.post("game", acp_game, "acp_game");
|
||||||
|
router.post("game/list", game_list, "acp_game_list");
|
||||||
|
router.post("game/open", game_open, "acp_game_open");
|
||||||
|
|
||||||
|
let mut chain = Chain::new(router);
|
||||||
|
chain.link_before(AcpMiddleware);
|
||||||
|
chain
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ use failure::Error;
|
|||||||
use failure::err_msg;
|
use failure::err_msg;
|
||||||
|
|
||||||
use account::Account;
|
use account::Account;
|
||||||
|
use pg::Db;
|
||||||
|
|
||||||
use construct::{Construct};
|
use construct::{Construct};
|
||||||
use skill::{Skill, Cast, Resolution, Event, resolution_steps};
|
use skill::{Skill, Cast, Resolution, Event, resolution_steps};
|
||||||
@ -655,6 +656,55 @@ pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result<Game, Error> {
|
|||||||
return Ok(game);
|
return Ok(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn select(db: &Db, id: Uuid) -> Result<Game, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT *
|
||||||
|
FROM games
|
||||||
|
WHERE id = $1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = db
|
||||||
|
.query(query, &[&id])?;
|
||||||
|
|
||||||
|
let returned = match result.iter().next() {
|
||||||
|
Some(row) => row,
|
||||||
|
None => return Err(err_msg("game not found")),
|
||||||
|
};
|
||||||
|
|
||||||
|
// tells from_slice to cast into a construct
|
||||||
|
let game_bytes: Vec<u8> = returned.get("data");
|
||||||
|
let game = from_slice::<Game>(&game_bytes)?;
|
||||||
|
|
||||||
|
return Ok(game);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(db: &Db, number: u32) -> Result<Vec<Game>, Error> {
|
||||||
|
let query = "
|
||||||
|
SELECT data
|
||||||
|
FROM games
|
||||||
|
ORDER BY created_at
|
||||||
|
LIMIT $1;
|
||||||
|
";
|
||||||
|
|
||||||
|
let result = db
|
||||||
|
.query(query, &[&number])?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
for row in result.into_iter() {
|
||||||
|
let bytes: Vec<u8> = row.get(0);
|
||||||
|
|
||||||
|
match from_slice::<Game>(&bytes) {
|
||||||
|
Ok(i) => list.push(i),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("{:?}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn games_need_upkeep(tx: &mut Transaction) -> Result<Vec<Game>, Error> {
|
pub fn games_need_upkeep(tx: &mut Transaction) -> Result<Vec<Game>, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT data, id
|
SELECT data, id
|
||||||
|
|||||||
@ -9,15 +9,17 @@ use iron::mime::Mime;
|
|||||||
use iron::{typemap, BeforeMiddleware,AfterMiddleware};
|
use iron::{typemap, BeforeMiddleware,AfterMiddleware};
|
||||||
use persistent::Read;
|
use persistent::Read;
|
||||||
use router::Router;
|
use router::Router;
|
||||||
|
use mount::{Mount};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use acp;
|
||||||
use account;
|
use account;
|
||||||
use pg::PgPool;
|
use pg::PgPool;
|
||||||
use payments::{stripe};
|
use payments::{stripe};
|
||||||
|
|
||||||
pub const TOKEN_HEADER: &str = "x-auth-token";
|
pub const TOKEN_HEADER: &str = "x-auth-token";
|
||||||
pub const AUTH_CLEAR: &str =
|
pub const AUTH_CLEAR: &str =
|
||||||
"x-auth-token=; HttpOnly; SameSite=Strict; Max-Age=-1;";
|
"x-auth-token=; HttpOnly; SameSite=Strict; Path=/; Max-Age=-1;";
|
||||||
|
|
||||||
#[derive(Clone, Copy, Fail, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Fail, Debug, Serialize, Deserialize)]
|
||||||
pub enum MnmlHttpError {
|
pub enum MnmlHttpError {
|
||||||
@ -30,6 +32,8 @@ pub enum MnmlHttpError {
|
|||||||
Unauthorized,
|
Unauthorized,
|
||||||
#[fail(display="bad request")]
|
#[fail(display="bad request")]
|
||||||
BadRequest,
|
BadRequest,
|
||||||
|
#[fail(display="not found")]
|
||||||
|
NotFound,
|
||||||
#[fail(display="account name taken or invalid")]
|
#[fail(display="account name taken or invalid")]
|
||||||
AccountNameNotProvided,
|
AccountNameNotProvided,
|
||||||
#[fail(display="account name not provided")]
|
#[fail(display="account name not provided")]
|
||||||
@ -58,42 +62,40 @@ impl From<postgres::Error> for MnmlHttpError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<r2d2::Error> for MnmlHttpError {
|
||||||
|
fn from(_err: r2d2::Error) -> Self {
|
||||||
|
MnmlHttpError::DbError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<failure::Error> for MnmlHttpError {
|
impl From<failure::Error> for MnmlHttpError {
|
||||||
fn from(_err: failure::Error) -> Self {
|
fn from(err: failure::Error) -> Self {
|
||||||
|
warn!("{:?}", err);
|
||||||
MnmlHttpError::ServerError
|
MnmlHttpError::ServerError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct JsonResponse {
|
#[serde(rename_all(serialize = "lowercase"))]
|
||||||
response: Option<String>,
|
pub enum Json {
|
||||||
success: bool,
|
Error(String),
|
||||||
error_message: Option<String>
|
Message(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonResponse {
|
pub fn json_response(status: status::Status, response: Json) -> Response {
|
||||||
fn success(response: String) -> Self {
|
|
||||||
JsonResponse { response: Some(response), success: true, error_message: None }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error(msg: String) -> Self {
|
|
||||||
JsonResponse { response: None, success: false, error_message: Some(msg) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iron_response (status: status::Status, message: String) -> Response {
|
|
||||||
let content_type = "application/json".parse::<Mime>().unwrap();
|
let content_type = "application/json".parse::<Mime>().unwrap();
|
||||||
let msg = match status {
|
let json = serde_json::to_string(&response).unwrap();
|
||||||
status::Ok => JsonResponse::success(message),
|
return Response::with((content_type, status, json));
|
||||||
_ => JsonResponse::error(message)
|
}
|
||||||
};
|
|
||||||
let msg_out = serde_json::to_string(&msg).unwrap();
|
pub fn json_object(status: status::Status, object: String) -> Response {
|
||||||
return Response::with((content_type, status, msg_out));
|
let content_type = "application/json".parse::<Mime>().unwrap();
|
||||||
|
return Response::with((content_type, status, object));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<MnmlHttpError> for IronError {
|
impl From<MnmlHttpError> for IronError {
|
||||||
fn from(m_err: MnmlHttpError) -> Self {
|
fn from(m_err: MnmlHttpError) -> Self {
|
||||||
let (err, res) = match m_err {
|
let (err, status) = match m_err {
|
||||||
MnmlHttpError::ServerError |
|
MnmlHttpError::ServerError |
|
||||||
MnmlHttpError::DbError => (m_err.compat(), status::InternalServerError),
|
MnmlHttpError::DbError => (m_err.compat(), status::InternalServerError),
|
||||||
|
|
||||||
@ -107,8 +109,12 @@ impl From<MnmlHttpError> for IronError {
|
|||||||
MnmlHttpError::InvalidCode |
|
MnmlHttpError::InvalidCode |
|
||||||
MnmlHttpError::TokenDoesNotMatch |
|
MnmlHttpError::TokenDoesNotMatch |
|
||||||
MnmlHttpError::Unauthorized => (m_err.compat(), status::Unauthorized),
|
MnmlHttpError::Unauthorized => (m_err.compat(), status::Unauthorized),
|
||||||
|
|
||||||
|
MnmlHttpError::NotFound => (m_err.compat(), status::NotFound),
|
||||||
};
|
};
|
||||||
IronError { error: Box::new(err), response: iron_response(res, m_err.to_string()) }
|
|
||||||
|
let response = json_response(status, Json::Error(m_err.to_string()));
|
||||||
|
IronError { error: Box::new(err), response }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +169,15 @@ fn token_res(token: String) -> Response {
|
|||||||
let v = Cookie::build(TOKEN_HEADER, token)
|
let v = Cookie::build(TOKEN_HEADER, token)
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.same_site(SameSite::Strict)
|
.same_site(SameSite::Strict)
|
||||||
|
.path("/")
|
||||||
.max_age(Duration::weeks(1)) // 1 week aligns with db set
|
.max_age(Duration::weeks(1)) // 1 week aligns with db set
|
||||||
.finish();
|
.finish();
|
||||||
|
|
||||||
let mut res = iron_response(status::Ok, "token_res".to_string());
|
let mut res = json_response(
|
||||||
|
status::Ok,
|
||||||
|
Json::Message("authenticated".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
res.headers.set(SetCookie(vec![v.to_string()]));
|
res.headers.set(SetCookie(vec![v.to_string()]));
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@ -220,7 +231,7 @@ fn login(req: &mut Request) -> IronResult<Response> {
|
|||||||
|
|
||||||
match account::login(&mut tx, ¶ms.name, ¶ms.password) {
|
match account::login(&mut tx, ¶ms.name, ¶ms.password) {
|
||||||
Ok(a) => {
|
Ok(a) => {
|
||||||
let token = account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::ServerError))?;
|
let token = account::new_token(&mut tx, a.id)?;
|
||||||
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||||
Ok(token_res(token))
|
Ok(token_res(token))
|
||||||
},
|
},
|
||||||
@ -238,11 +249,11 @@ fn logout(req: &mut Request) -> IronResult<Response> {
|
|||||||
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
account::new_token(&mut tx, a.id).or(Err(MnmlHttpError::Unauthorized))?;
|
account::new_token(&mut tx, a.id)?;
|
||||||
|
|
||||||
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
let mut res = iron_response(status::Ok, "logout".to_string());
|
let mut res = json_response(status::Ok, Json::Message("logged out".to_string()));
|
||||||
res.headers.set(SetCookie(vec![AUTH_CLEAR.to_string()]));
|
res.headers.set(SetCookie(vec![AUTH_CLEAR.to_string()]));
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|
||||||
@ -251,6 +262,33 @@ fn logout(req: &mut Request) -> IronResult<Response> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,Deserialize)]
|
||||||
|
struct SetPassword {
|
||||||
|
current: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_password(req: &mut Request) -> IronResult<Response> {
|
||||||
|
let state = req.get::<Read<State>>().unwrap();
|
||||||
|
let params = match req.get::<bodyparser::Struct<SetPassword>>() {
|
||||||
|
Ok(Some(b)) => b,
|
||||||
|
_ => return Err(IronError::from(MnmlHttpError::BadRequest)),
|
||||||
|
};
|
||||||
|
|
||||||
|
match req.extensions.get::<account::Account>() {
|
||||||
|
Some(a) => {
|
||||||
|
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
|
||||||
|
|
||||||
|
let token = account::set_password(&mut tx, a.id, ¶ms.current, ¶ms.password)?;
|
||||||
|
|
||||||
|
tx.commit().or(Err(MnmlHttpError::ServerError))?;
|
||||||
|
|
||||||
|
Ok(token_res(token))
|
||||||
|
},
|
||||||
|
None => Err(IronError::from(MnmlHttpError::Unauthorized)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10;
|
const MAX_BODY_LENGTH: usize = 1024 * 1024 * 10;
|
||||||
|
|
||||||
@ -261,18 +299,33 @@ pub struct State {
|
|||||||
|
|
||||||
impl Key for State { type Value = State; }
|
impl Key for State { type Value = State; }
|
||||||
|
|
||||||
pub fn start(pool: PgPool) {
|
fn account_mount() -> Router {
|
||||||
let mut router = Router::new();
|
let mut router = Router::new();
|
||||||
|
|
||||||
// auth
|
router.post("login", login, "login");
|
||||||
router.post("/api/login", login, "login");
|
router.post("logout", logout, "logout");
|
||||||
router.post("/api/logout", logout, "logout");
|
router.post("register", register, "register");
|
||||||
router.post("/api/register", register, "register");
|
router.post("password", set_password, "set_password");
|
||||||
|
router.post("email", logout, "email");
|
||||||
|
|
||||||
// payments
|
router
|
||||||
router.post("/api/payments/stripe", stripe, "stripe");
|
}
|
||||||
|
|
||||||
let mut chain = Chain::new(router);
|
fn payment_mount() -> Router {
|
||||||
|
let mut router = Router::new();
|
||||||
|
router.post("stripe", stripe, "stripe");
|
||||||
|
|
||||||
|
router
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(pool: PgPool) {
|
||||||
|
let mut mounts = Mount::new();
|
||||||
|
|
||||||
|
mounts.mount("/api/account/", account_mount());
|
||||||
|
mounts.mount("/api/payments/", payment_mount());
|
||||||
|
mounts.mount("/api/acp/", acp::acp_mount());
|
||||||
|
|
||||||
|
let mut chain = Chain::new(mounts);
|
||||||
chain.link(Read::<State>::both(State { pool }));
|
chain.link(Read::<State>::both(State { pool }));
|
||||||
chain.link_before(Read::<bodyparser::MaxBodyLength>::one(MAX_BODY_LENGTH));
|
chain.link_before(Read::<bodyparser::MaxBodyLength>::one(MAX_BODY_LENGTH));
|
||||||
chain.link_before(AuthMiddleware);
|
chain.link_before(AuthMiddleware);
|
||||||
@ -396,9 +396,11 @@ impl Instance {
|
|||||||
|
|
||||||
// if you don't win, you lose
|
// if you don't win, you lose
|
||||||
// ties can happen if both players forfeit
|
// ties can happen if both players forfeit
|
||||||
|
// in this case we just finish the game and
|
||||||
|
// dock them 10k mmr
|
||||||
let winner_id = match game.winner() {
|
let winner_id = match game.winner() {
|
||||||
Some(w) => w.id,
|
Some(w) => w.id,
|
||||||
None => Uuid::nil(),
|
None => return Ok(self.finish()),
|
||||||
};
|
};
|
||||||
|
|
||||||
for player in game.players.iter() {
|
for player in game.players.iter() {
|
||||||
|
|||||||
@ -23,12 +23,14 @@ extern crate iron;
|
|||||||
extern crate bodyparser;
|
extern crate bodyparser;
|
||||||
extern crate persistent;
|
extern crate persistent;
|
||||||
extern crate router;
|
extern crate router;
|
||||||
|
extern crate mount;
|
||||||
extern crate cookie;
|
extern crate cookie;
|
||||||
|
|
||||||
extern crate ws;
|
extern crate ws;
|
||||||
extern crate crossbeam_channel;
|
extern crate crossbeam_channel;
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
|
mod acp;
|
||||||
mod construct;
|
mod construct;
|
||||||
mod effect;
|
mod effect;
|
||||||
mod game;
|
mod game;
|
||||||
@ -38,7 +40,7 @@ mod img;
|
|||||||
mod mob;
|
mod mob;
|
||||||
mod mtx;
|
mod mtx;
|
||||||
mod names;
|
mod names;
|
||||||
mod net;
|
mod http;
|
||||||
mod payments;
|
mod payments;
|
||||||
mod pg;
|
mod pg;
|
||||||
mod player;
|
mod player;
|
||||||
@ -98,7 +100,7 @@ fn main() {
|
|||||||
|
|
||||||
let pg_pool = pool.clone();
|
let pg_pool = pool.clone();
|
||||||
|
|
||||||
spawn(move || net::start(http_pool));
|
spawn(move || http::start(http_pool));
|
||||||
spawn(move || warden.listen());
|
spawn(move || warden.listen());
|
||||||
spawn(move || warden::upkeep_tick(warden_tick_tx));
|
spawn(move || warden::upkeep_tick(warden_tick_tx));
|
||||||
spawn(move || pg::listen(pg_pool, pg_events_tx));
|
spawn(move || pg::listen(pg_pool, pg_events_tx));
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rand::{thread_rng};
|
use rand::{thread_rng};
|
||||||
|
|
||||||
const FIRSTS: [&'static str; 47] = [
|
const FIRSTS: [&'static str; 50] = [
|
||||||
"artificial",
|
"artificial",
|
||||||
"ambient",
|
"ambient",
|
||||||
"borean",
|
"borean",
|
||||||
@ -20,10 +20,13 @@ const FIRSTS: [&'static str; 47] = [
|
|||||||
"fierce",
|
"fierce",
|
||||||
"fossilised",
|
"fossilised",
|
||||||
"frozen",
|
"frozen",
|
||||||
|
"gravitational",
|
||||||
|
"jovian",
|
||||||
"inverted",
|
"inverted",
|
||||||
"leafy",
|
"leafy",
|
||||||
"lurking",
|
"lurking",
|
||||||
"limitless",
|
"limitless",
|
||||||
|
"magnetic",
|
||||||
"metallic",
|
"metallic",
|
||||||
"mossy",
|
"mossy",
|
||||||
"mighty",
|
"mighty",
|
||||||
@ -37,6 +40,7 @@ const FIRSTS: [&'static str; 47] = [
|
|||||||
"oxygenated",
|
"oxygenated",
|
||||||
"oscillating",
|
"oscillating",
|
||||||
"ossified",
|
"ossified",
|
||||||
|
"orbiting",
|
||||||
"piscine",
|
"piscine",
|
||||||
"purified",
|
"purified",
|
||||||
"recalcitrant",
|
"recalcitrant",
|
||||||
@ -46,18 +50,18 @@ const FIRSTS: [&'static str; 47] = [
|
|||||||
"supercooled",
|
"supercooled",
|
||||||
"subsonic",
|
"subsonic",
|
||||||
"synthetic",
|
"synthetic",
|
||||||
"sweet",
|
|
||||||
"terrestrial",
|
"terrestrial",
|
||||||
"weary",
|
"weary",
|
||||||
];
|
];
|
||||||
|
|
||||||
const LASTS: [&'static str; 52] = [
|
const LASTS: [&'static str; 55] = [
|
||||||
"artifact",
|
"artifact",
|
||||||
"assembly",
|
"assembly",
|
||||||
"carbon",
|
"carbon",
|
||||||
"console",
|
"console",
|
||||||
"construct",
|
"construct",
|
||||||
"craft",
|
"craft",
|
||||||
|
"core",
|
||||||
"design",
|
"design",
|
||||||
"drone",
|
"drone",
|
||||||
"distortion",
|
"distortion",
|
||||||
@ -77,6 +81,8 @@ const LASTS: [&'static str; 52] = [
|
|||||||
"lifeform",
|
"lifeform",
|
||||||
"landmass",
|
"landmass",
|
||||||
"lens",
|
"lens",
|
||||||
|
"mantle",
|
||||||
|
"magnetism",
|
||||||
"mechanism",
|
"mechanism",
|
||||||
"mountain",
|
"mountain",
|
||||||
"nectar",
|
"nectar",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use net::State;
|
use http::State;
|
||||||
use iron::prelude::*;
|
use iron::prelude::*;
|
||||||
use iron::response::HttpResponse;
|
use iron::response::HttpResponse;
|
||||||
use iron::status;
|
use iron::status;
|
||||||
@ -14,7 +14,7 @@ use failure::err_msg;
|
|||||||
|
|
||||||
use stripe::{Event, EventObject, CheckoutSession, SubscriptionStatus};
|
use stripe::{Event, EventObject, CheckoutSession, SubscriptionStatus};
|
||||||
|
|
||||||
use net::{MnmlHttpError};
|
use http::{MnmlHttpError};
|
||||||
use pg::{PgPool};
|
use pg::{PgPool};
|
||||||
use account;
|
use account;
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ use pg::{Db};
|
|||||||
use pg::{PgPool};
|
use pg::{PgPool};
|
||||||
use skill::{Skill, dev_resolve, Resolutions};
|
use skill::{Skill, dev_resolve, Resolutions};
|
||||||
use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip};
|
use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip};
|
||||||
use net::{AUTH_CLEAR, TOKEN_HEADER};
|
use http::{AUTH_CLEAR, TOKEN_HEADER};
|
||||||
|
|
||||||
#[derive(Debug,Clone,Serialize,Deserialize)]
|
#[derive(Debug,Clone,Serialize,Deserialize)]
|
||||||
pub enum RpcMessage {
|
pub enum RpcMessage {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user