tag', () => {
8 | const renderedComponent = shallow(
);
9 | expect(renderedComponent.type()).toEqual('div');
10 | });
11 |
12 | it('should have a className attribute', () => {
13 | const renderedComponent = shallow(
);
14 | expect(renderedComponent.prop('className')).toBeDefined();
15 | });
16 |
17 | it('should adopt a valid attribute', () => {
18 | const id = 'test';
19 | const renderedComponent = shallow(
);
20 | expect(renderedComponent.prop('id')).toEqual(id);
21 | });
22 |
23 | it('should not adopt an invalid attribute', () => {
24 | const renderedComponent = shallow(
);
25 | expect(renderedComponent.prop('attribute')).toBeUndefined();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/app/containers/LoginProgressModal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { formValueSelector } from 'redux-form/immutable';
5 | import Radial from '../../components/RadialProgress';
6 |
7 | export function LoginProgressWrapper(props) {
8 | return (
9 |
10 |
Waiting to login ...
11 |
12 |
13 | );
14 | }
15 |
16 | LoginProgressWrapper.propTypes = {
17 | progress: PropTypes.any,
18 | };
19 |
20 | const selector = formValueSelector('login');
21 | const mapStateToProps = (state) => ({
22 | progress: selector(state, 'workerProgress'),
23 | });
24 |
25 | export default connect(mapStateToProps)(LoginProgressWrapper);
26 |
--------------------------------------------------------------------------------
/app/containers/LogoutDialog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 |
5 | import Input from '../../components/Input';
6 | import CopyInput from '../../components/CopyInput';
7 | import SubmitButton from '../../components/SubmitButton';
8 |
9 | import { modalDismiss } from '../../containers/App/actions';
10 |
11 | function LogoutDialog(props) {
12 | return (
13 |
14 |
15 |
21 |
22 |
23 |
24 | OK
25 |
26 |
27 | );
28 | }
29 |
30 | LogoutDialog.propTypes = {
31 | modalDismiss: PropTypes.func,
32 | wallet: PropTypes.object,
33 | };
34 |
35 | export default connect(undefined, { modalDismiss })(LogoutDialog);
36 |
--------------------------------------------------------------------------------
/app/containers/Modal/constants.js:
--------------------------------------------------------------------------------
1 | export const CONFIRM_DIALOG = 'CONFIRM_DIALOG';
2 | export const INVITE_DIALOG = 'INVITE_DIALOG';
3 | export const JOIN_DIALOG = 'JOIN_DIALOG';
4 | export const IMPORT_DIALOG = 'IMPORT_DIALOG';
5 | export const EXPORT_DIALOG = 'EXPORT_DIALOG';
6 | export const LOGOUT_DIALOG = 'LOGOUT_DIALOG';
7 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | */
6 |
7 | import React from 'react';
8 | import { FormattedMessage } from 'react-intl';
9 | import styled from 'styled-components';
10 |
11 | import H1 from '../../components/H1';
12 |
13 | import { backgroundBoxed } from '../../variables';
14 |
15 | import messages from './messages';
16 |
17 |
18 | const Wrapper = styled.div`
19 | position: fixed;
20 | top: 0;
21 | left: 0;
22 | width: 100%;
23 | height: 100%;
24 | background-color: ${backgroundBoxed};
25 | `;
26 |
27 | export default function NotFound() {
28 | return (
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NotFoundPage Messages
3 | *
4 | * This contains all the text for the NotFoundPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'boilerplate.containers.NotFoundPage.header',
11 | defaultMessage: 'Page not found.',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/app/containers/NotFoundPage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Testing the NotFoundPage
3 | */
4 |
5 | import React from 'react';
6 | import { shallow } from 'enzyme';
7 | import { FormattedMessage } from 'react-intl';
8 |
9 | import H1 from 'components/H1';
10 | import NotFound from '../index';
11 |
12 | describe('
', () => {
13 | it('should render the Page Not Found text', () => {
14 | const renderedComponent = shallow(
15 |
16 | );
17 | expect(renderedComponent.contains(
18 |
19 |
23 |
)).toEqual(true);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/app/containers/Notifications/actions.js:
--------------------------------------------------------------------------------
1 | export const NOTIFY_ADD = 'acebusters/Notify/ADD';
2 | export const NOTIFY_DELETE = 'acebusters/Notify/DELETE';
3 | export const NOTIFY_REMOVE = 'acebusters/Notify/REMOVE';
4 | export const NOTIFY_REMOVING = 'acebusters/Notify/REMOVING';
5 |
6 | export const notifyAdd = (notification) => ({
7 | type: NOTIFY_ADD,
8 | notification,
9 | });
10 |
11 | export const notifyDelete = (txId) => ({
12 | type: NOTIFY_DELETE,
13 | txId,
14 | });
15 |
16 | export const notifyRemove = (txId) => ({
17 | type: NOTIFY_REMOVE,
18 | txId,
19 | });
20 |
21 | export const notifyRemoving = (txId) => ({
22 | type: NOTIFY_REMOVING,
23 | txId,
24 | });
25 |
--------------------------------------------------------------------------------
/app/containers/Notifications/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { createStructuredSelector } from 'reselect';
5 |
6 | import { notifyRemove } from './actions';
7 | import { selectNotifications } from './selectors';
8 | import { makeSelectLoggedIn } from '../AccountProvider/selectors';
9 |
10 | import Notifications from '../../components/Notifications';
11 |
12 | const NotificationsContainer = ({ location: { pathname }, ...props }) => {
13 | const showNotifications = pathname.match(/table|lobby|dashboard/);
14 | const isTable = pathname.match('table');
15 | if (showNotifications) {
16 | return
;
17 | }
18 | return null;
19 | };
20 | NotificationsContainer.propTypes = {
21 | location: PropTypes.shape({ pathname: PropTypes.string.isRequired }),
22 | };
23 |
24 | const mapStateToProps = createStructuredSelector({
25 | notifications: selectNotifications(),
26 | loggedIn: makeSelectLoggedIn(),
27 | });
28 |
29 | export default connect(
30 | mapStateToProps,
31 | { notifyRemove },
32 | )(NotificationsContainer);
33 |
--------------------------------------------------------------------------------
/app/containers/Notifications/reducer.js:
--------------------------------------------------------------------------------
1 | import { List, Map } from 'immutable';
2 | import * as types from './actions';
3 | import { SET_AUTH } from '../AccountProvider/actions';
4 |
5 | export const initialState = List([]);
6 |
7 | export default function notificationsReducer(state = initialState, action) {
8 | switch (action.type) {
9 | case types.NOTIFY_ADD:
10 | return state
11 | .filter((note) => note.get('txId') !== action.notification.txId) // prevent duplicates
12 | .push(Map(action.notification));
13 |
14 | case types.NOTIFY_DELETE:
15 | return state.filter((note) => note.get('txId') !== action.txId);
16 |
17 | case types.NOTIFY_REMOVING:
18 | return state.map(
19 | (note) => note.set('removing', note.get('txId') === action.txId)
20 | );
21 |
22 | case SET_AUTH:
23 | return !action.newAuthState.loggedIn ? state.clear() : state;
24 |
25 | default:
26 | return state;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/containers/Notifications/sagas/accountNotificationsSagas.js:
--------------------------------------------------------------------------------
1 | import { put } from 'redux-saga/effects';
2 |
3 | import { notifyDelete } from '../actions';
4 | import { noWeb3Danger } from '../constants';
5 |
6 | export function* injectedWeb3NotificationDismiss({ payload: injected }) {
7 | if (typeof injected === 'string') {
8 | yield put(notifyDelete(noWeb3Danger.txId));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/containers/Notifications/sagas/connectionNotificationsSaga.js:
--------------------------------------------------------------------------------
1 | import { delay } from 'redux-saga';
2 | import { take, takeEvery, race } from 'redux-saga/effects';
3 | import { createPersistNotification, removeNotification } from './utils';
4 | import { noConnectionDanger } from '../constants';
5 |
6 | import { WEB3_CONNECTED, WEB3_DISCONNECTED } from '../../AccountProvider/actions';
7 |
8 | function* addConnectionNotification() {
9 | // prevent notification flickering on bad connection
10 | const { expired } = yield race({
11 | expired: delay(1000),
12 | connected: take(WEB3_CONNECTED),
13 | });
14 |
15 | if (expired) {
16 | yield* createPersistNotification(noConnectionDanger);
17 | }
18 | }
19 |
20 | function* removeConnectionNotification() {
21 | yield* removeNotification({ txId: noConnectionDanger.txId });
22 | }
23 |
24 | export function* connectionNotifications() {
25 | yield takeEvery(WEB3_DISCONNECTED, addConnectionNotification);
26 | yield takeEvery(WEB3_CONNECTED, removeConnectionNotification);
27 | }
28 |
--------------------------------------------------------------------------------
/app/containers/Notifications/sagas/index.js:
--------------------------------------------------------------------------------
1 | import { takeEvery, fork } from 'redux-saga/effects';
2 |
3 | import { INJECT_ACCOUNT_UPDATE, CONTRACT_TX_SEND } from '../../AccountProvider/actions';
4 |
5 | import { NOTIFY_REMOVE } from '../actions';
6 |
7 | import { removeNotification } from './utils';
8 | import { injectedWeb3NotificationDismiss } from './accountNotificationsSagas';
9 | import { tableNotifications } from './tableNotificationsSaga';
10 | import { connectionNotifications } from './connectionNotificationsSaga';
11 |
12 | export function* notificationsSaga() {
13 | yield takeEvery(INJECT_ACCOUNT_UPDATE, injectedWeb3NotificationDismiss);
14 | yield takeEvery(NOTIFY_REMOVE, removeNotification);
15 | yield takeEvery(CONTRACT_TX_SEND, tableNotifications);
16 |
17 | yield fork(connectionNotifications);
18 | }
19 |
20 | export default [
21 | notificationsSaga,
22 | ];
23 |
--------------------------------------------------------------------------------
/app/containers/Notifications/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 | import sortBy from 'lodash/sortBy';
3 |
4 | export const selectNotifications = () => createSelector(
5 | (state) => state.get('notifications'),
6 | (notifications) => sortBy(notifications.toJS(), ['dismissable', 'date']),
7 | );
8 |
--------------------------------------------------------------------------------
/app/containers/Seat/utils.js:
--------------------------------------------------------------------------------
1 | export function getPosCoords(seatCoords, lineupSize, pos) {
2 | const cY = seatCoords[0][1];
3 | const coords = seatCoords.slice(0, lineupSize).sort((a, b) => {
4 | if (a[1] <= cY && b[1] <= cY) {
5 | return Math.sign(a[0] - b[0]);
6 | } else if (a[1] > cY && b[1] > cY) {
7 | return Math.sign(b[0] - a[0]);
8 | }
9 |
10 | return Math.sign(a[1] - b[1]);
11 | });
12 |
13 | return coords[pos];
14 | }
15 |
--------------------------------------------------------------------------------
/app/containers/Table/messages.js:
--------------------------------------------------------------------------------
1 | import { defineMessages } from 'react-intl';
2 |
3 | export default defineMessages({
4 | ok: {
5 | id: 'app.containers.Table.ok',
6 | defaultMessage: 'Ok',
7 | },
8 | opponentCallSent: {
9 | id: 'app.containers.Table.opponentCallSent',
10 | defaultMessage: 'Request sent. Wait for an opponent several minutes',
11 | },
12 | confirmLeave: {
13 | id: 'app.containers.Table.confirmLeave',
14 | defaultMessage: 'You will lose your hand if you leave the table now',
15 | },
16 | leave: {
17 | id: 'app.containers.Table.leave',
18 | defaultMessage: 'Leave table',
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/handRequestSaga.js:
--------------------------------------------------------------------------------
1 | import Raven from 'raven-js';
2 | import { put, call } from 'redux-saga/effects';
3 |
4 | import { getHand } from '../../../services/tableService';
5 |
6 | import { updateReceived } from '../actions';
7 |
8 | export function* handRequest(action) {
9 | try {
10 | const tableState = yield call(getHand, action.tableAddr, action.handId);
11 | yield put(updateReceived(action.tableAddr, tableState));
12 | } catch (err) {
13 | Raven.captureException(err, { tags: {
14 | tableAddr: action.tableAddr,
15 | handId: action.handId,
16 | } });
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/performBetSaga.js:
--------------------------------------------------------------------------------
1 | import { put } from 'redux-saga/effects';
2 | import Raven from 'raven-js';
3 |
4 | import TableService from '../../../services/tableService';
5 |
6 | import { setCards } from '../actions';
7 |
8 | export function* performBet(action) {
9 | const table = new TableService(action.tableAddr, action.privKey);
10 | try {
11 | const { cards } = yield table.bet(action.handId, action.amount);
12 | if (cards) {
13 | yield put(setCards(action.tableAddr, action.handId, cards));
14 | }
15 | } catch (err) {
16 | Raven.captureException(err, { tags: {
17 | tableAddr: action.tableAddr,
18 | handId: action.handId,
19 | } });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/performShowSaga.js:
--------------------------------------------------------------------------------
1 | import Raven from 'raven-js';
2 |
3 | import TableService from '../../../services/tableService';
4 |
5 | export function* performShow(action) {
6 | const table = new TableService(action.tableAddr, action.privKey);
7 | try {
8 | yield table.show(action.handId, action.amount, action.holeCards);
9 | } catch (err) {
10 | Raven.captureException(err, { tags: {
11 | tableAddr: action.tableAddr,
12 | handId: action.handId,
13 | } });
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/reservationSaga.js:
--------------------------------------------------------------------------------
1 | import { takeEvery, take, call } from 'redux-saga/effects';
2 |
3 | import * as reservationService from '../../../services/reservationService';
4 | import { CONTRACT_TX_APPEARED } from '../../AccountProvider/actions';
5 |
6 | import { RESERVE_SEAT } from '../actions';
7 |
8 | function* reserve({ payload: { tableAddr, pos, signerAddr, txHash, amount } }) {
9 | yield take((action) => action.type === CONTRACT_TX_APPEARED && action.meta && action.meta.txHash === txHash);
10 | yield call(reservationService.reserve, tableAddr, pos, signerAddr, txHash, amount);
11 | }
12 |
13 | export function* reservationSaga() {
14 | yield takeEvery(RESERVE_SEAT, reserve);
15 | }
16 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/sendMessageSaga.js:
--------------------------------------------------------------------------------
1 | import Raven from 'raven-js';
2 |
3 | import TableService from '../../../services/tableService';
4 |
5 | export function* sendMessage(action) {
6 | const table = new TableService(action.tableAddr, action.privKey);
7 | try {
8 | yield table.sendMessage(action.message);
9 | } catch (err) {
10 | Raven.captureException(err, { tags: {
11 | tableAddr: action.tableAddr,
12 | } });
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/sitoutFlowSaga.js:
--------------------------------------------------------------------------------
1 | import { put, take } from 'redux-saga/effects';
2 | import Raven from 'raven-js';
3 |
4 | import TableService from '../../../services/tableService';
5 |
6 | import { sitOutToggle } from '../actions';
7 |
8 | export function* sitoutFlow() {
9 | while (true) { //eslint-disable-line
10 | const req = yield take(sitOutToggle.REQUEST);
11 | const action = req.payload;
12 | try {
13 | const table = new TableService(action.tableAddr, action.privKey);
14 | const receipt = yield table.sitOut(action.handId, action.amount);
15 | yield put({ type: sitOutToggle.SUCCESS, payload: receipt });
16 | } catch (err) {
17 | Raven.captureException(err, {
18 | tags: {
19 | tableAddr: action.tableAddr,
20 | handId: action.handId,
21 | },
22 | });
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/containers/Table/sagas/submitSignedNettingSaga.js:
--------------------------------------------------------------------------------
1 | import ethUtil from 'ethereumjs-util';
2 | import Raven from 'raven-js';
3 |
4 | import TableService from '../../../services/tableService';
5 |
6 | export function* submitSignedNetting(action) {
7 | try {
8 | // TODO(ab): check against actual balances here
9 |
10 | // sign balances here
11 | let payload = new Buffer(action.balances.replace('0x', ''), 'hex');
12 | const priv = new Buffer(action.privKey.replace('0x', ''), 'hex');
13 | const hash = ethUtil.sha3(payload);
14 | const sig = ethUtil.ecsign(hash, priv);
15 | payload = `0x${sig.v.toString(16)}${sig.r.toString('hex')}${sig.s.toString('hex')}`;
16 | const table = new TableService(action.tableAddr, action.privKey);
17 | yield table.net(action.handId, payload);
18 | } catch (err) {
19 | Raven.captureException(err, { tags: {
20 | tableAddr: action.tableAddr,
21 | handId: action.handId,
22 | } });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/containers/Table/tests/consts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by helge on 27.01.17.
3 | */
4 |
5 |
6 | // secretSeed: 'rural tent tests net drip fatigue uncle action repeat couple lawn rival'
7 | export const PLAYER1 = {
8 | address: '0x6d2f2c0fa568243d2def3e999a791a6df45d816e',
9 | key: '0x2e39143576f97f6ecd7439a0678f330d7144110cdc58b6476687cc243d7753ca',
10 | };
11 |
12 | export const PLAYER2 = {
13 | address: '0x1c5a1730ffc44ac21700bb85bf0ceefd12ce71d7',
14 | key: '0x99e69145c6e7f44ba04d579faac9ef4ce5e942dc02b96a9d42b5fcb03e508729',
15 | };
16 |
17 | export const PLAYER3 = {
18 | address: '0xdd7acad75b52bd206777a36bc41a3b65ad1c44fc',
19 | key: '0x33de976dfb8bdf2dc3115801e514b902c4c913c351b6549947758a8b9d981722',
20 | };
21 |
22 | export const PLAYER4 = {
23 | address: '0x0dfbfdf730c7d3612cf605e6629be369aa4eceeb',
24 | key: '0xa803ed744543e69b5e4816c5fc7539427a2928e78d729c87712f180fae52fcc9',
25 | };
26 |
27 | export const PLAYER_EMPTY = {
28 | address: '0x0000000000000000000000000000000000000000',
29 | };
30 |
--------------------------------------------------------------------------------
/app/containers/TableDebug/requestStat.js:
--------------------------------------------------------------------------------
1 | import { requestApi } from '../../services/api';
2 | import { conf } from '../../app.config';
3 |
4 | const request = requestApi(conf().gasStatUrl);
5 |
6 | export const requestStat = () => request('get', 'stat');
7 |
--------------------------------------------------------------------------------
/app/containers/TableDebug/utils.js:
--------------------------------------------------------------------------------
1 | import { Receipt, Type } from 'poker-helper';
2 | import { formatNtz } from '../../utils/amountFormatter';
3 |
4 | export function receiptStringType(type) {
5 | return Object.keys(Type).find((key) => Type[key] === type);
6 | }
7 |
8 | export function parseDistributionReceipt(distribution, lineup) {
9 | if (!distribution) {
10 | return {};
11 | }
12 |
13 | const { outs } = Receipt.parse(distribution);
14 |
15 | return lineup.reduce((memo, seat, pos) => ({
16 | ...memo,
17 | [seat.address]: outs[pos],
18 | }), {});
19 | }
20 |
21 | export function renderNtz(amount) {
22 | if (amount) {
23 | return formatNtz(amount, 1);
24 | }
25 |
26 | return (amount === null || amount === undefined) ? '-' : amount;
27 | }
28 |
--------------------------------------------------------------------------------
/app/containers/TableMenu/actions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jzobro on 20170606
3 | */
4 | export const MENU_TOGGLE_OPEN = 'acebusters/TableMenu/MENU_TOGGLE_OPEN';
5 | export const MENU_TOGGLE_ACTIVE = 'acebusters/TableMenu/MENU_TOGGLE_ACTIVE';
6 | export const MUTE = 'acebusters/TableMenu/MUTE';
7 | export const UNMUTE = 'acebusters/TableMenu/UNMUTE';
8 |
9 | export function toggleMenuOpen() {
10 | return { type: MENU_TOGGLE_OPEN };
11 | }
12 |
13 | export function toggleMenuActive() {
14 | return { type: MENU_TOGGLE_ACTIVE };
15 | }
16 |
17 | export function mute() {
18 | return { type: MUTE };
19 | }
20 |
21 | export function unmute() {
22 | return { type: UNMUTE };
23 | }
24 |
--------------------------------------------------------------------------------
/app/containers/TableMenu/reducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by jzobro on 20170606
3 | */
4 | import { fromJS } from 'immutable';
5 | import * as types from './actions';
6 | import * as storageService from '../../services/localStorage';
7 |
8 | export const initialState = fromJS({
9 | open: false,
10 | active: false,
11 | muted: storageService.getItem('muted') || false,
12 | });
13 |
14 | export default function tableMenuReducer(state = initialState, action) {
15 | switch (action.type) {
16 |
17 | case types.MENU_TOGGLE_OPEN: {
18 | return state.set('open', !state.get('open'));
19 | }
20 |
21 | case types.MENU_TOGGLE_ACTIVE: {
22 | return state.set('active', !state.get('active'));
23 | }
24 |
25 | case types.MUTE: {
26 | return state.set('muted', true);
27 | }
28 |
29 | case types.UNMUTE: {
30 | return state.set('muted', false);
31 | }
32 |
33 | default: {
34 | return state;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/containers/TableMenu/sagas.js:
--------------------------------------------------------------------------------
1 | import { takeEvery } from 'redux-saga/effects';
2 | import { MUTE, UNMUTE } from './actions';
3 | import * as storageService from '../../services/localStorage';
4 |
5 | function* muteScanner() {
6 | storageService.setItem('muted', true);
7 | }
8 |
9 | function* unmuteScanner() {
10 | storageService.removeItem('muted');
11 | }
12 |
13 | export function* tableMenuSaga() {
14 | yield takeEvery(MUTE, muteScanner);
15 | yield takeEvery(UNMUTE, unmuteScanner);
16 | }
17 |
18 | export default [
19 | tableMenuSaga,
20 | ];
21 |
--------------------------------------------------------------------------------
/app/containers/TableMenu/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the state domain
5 | */
6 | const selectTableMenu = (state) => state.get('tableMenu');
7 |
8 | /**
9 | * Other specific selectors
10 | */
11 | export const makeSelectOpen = () => createSelector(
12 | selectTableMenu,
13 | (tableMenu) => tableMenu.get('open'),
14 | );
15 |
16 | export const makeSelectActive = () => createSelector(
17 | selectTableMenu,
18 | (tableMenu) => tableMenu.get('active'),
19 | );
20 |
21 | export const makeSelectIsMuted = () => createSelector(
22 | selectTableMenu,
23 | (tableMenu) => tableMenu.get('muted'),
24 | );
25 |
--------------------------------------------------------------------------------
/app/containers/TableMenu/tests/reducer.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 | //
3 | import reducer from '../reducer';
4 | import {
5 | toggleMenuActive,
6 | toggleMenuOpen,
7 | } from '../actions';
8 |
9 | describe('containers.TableMenu.reducer', () => {
10 | describe('toggleMenuActive', () => {
11 | it('should toggle active value', () => {
12 | const before = fromJS({
13 | active: false,
14 | });
15 | const nextState = reducer(before, toggleMenuActive());
16 | expect(nextState.get('active')).toEqual(true);
17 | });
18 | });
19 |
20 | describe('toggleMenuOpen', () => {
21 | it('should toggle open value', () => {
22 | const before = fromJS({
23 | open: false,
24 | });
25 | const nextState = reducer(before, toggleMenuOpen());
26 | expect(nextState.get('open')).toEqual(true);
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/app/containers/TableMenu/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 | import {
3 | makeSelectOpen,
4 | makeSelectActive,
5 | } from '../selectors';
6 |
7 | describe('containers.TableMenu.selectors', () => {
8 | it('should select the tableMenu active state', () => {
9 | const mockedState = fromJS({
10 | tableMenu: {
11 | active: true,
12 | },
13 | });
14 | const statusSelector = makeSelectActive();
15 | expect(statusSelector(mockedState)).toEqual(true);
16 | });
17 | it('should select the tableMenu open state', () => {
18 | const mockedState = fromJS({
19 | tableMenu: {
20 | open: true,
21 | },
22 | });
23 | const statusSelector = makeSelectOpen();
24 | expect(statusSelector(mockedState)).toEqual(true);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/containers/TxSubmit/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ButtonContainer = styled.div`
4 | display: flex;
5 |
6 | & > * {
7 | flex: 1;
8 | }
9 |
10 | & > * + * {
11 | margin-left: 10px;
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/NoInjected.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Wrapper } from './styles';
3 |
4 | const NoInjected = () => (
5 |
6 | Account doesn´t exists or locked
7 |
8 | Please, create or unlock MetaMask account
9 |
10 |
11 | );
12 |
13 | export default NoInjected;
14 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/NoWeb3.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import A from '../../components/A';
3 | import { Wrapper } from './styles';
4 |
5 | const NoWeb3Alert = () => (
6 |
7 | Please install MetaMask
8 |
9 | In order to use our app you need to install the MetaMask Extension to make your browser smart contract enabled.
10 | Also, you can use Ethereum enabled browser like Mist or
11 | Parity.
12 |
13 |
14 | );
15 |
16 | export default NoWeb3Alert;
17 |
18 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/NotConnected.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Wrapper } from './styles';
3 |
4 | const NotConnected = () => (
5 |
6 | Connection Lost
7 |
8 | Please check your connection or try to refresh page
9 |
10 |
11 | );
12 |
13 | export default NotConnected;
14 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/Paused.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Wrapper } from './styles';
3 |
4 | const Paused = () => (
5 |
6 | Contracts under maintenance
7 |
8 | Currently we are unable to send transactions. Please try later
9 |
10 |
11 | );
12 |
13 | export default Paused;
14 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/UnsupportedNetwork.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Wrapper } from './styles';
3 |
4 | import { conf } from '../../app.config';
5 |
6 | const UnsupportedNetworkAlert = () => (
7 |
8 | Unsupported network
9 |
10 | You can`t send transactions on this network. You must be on {conf().networkName}.
11 |
12 |
13 | );
14 |
15 | export default UnsupportedNetworkAlert;
16 |
17 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/WrongInjected.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { createStructuredSelector } from 'reselect';
5 | import { Wrapper } from './styles';
6 | import { makeSelectOwner } from '../../containers/AccountProvider/selectors';
7 |
8 | const WrongInjectedAlert = ({ owner }) => (
9 |
10 | Wrong account
11 |
12 | You can`t send transactions with this account. You should switch to {owner}.
13 |
14 |
15 | );
16 |
17 | WrongInjectedAlert.propTypes = {
18 | owner: PropTypes.string,
19 | };
20 |
21 | const mapStateToProps = createStructuredSelector({
22 | owner: makeSelectOwner(),
23 | });
24 |
25 | export default connect(mapStateToProps)(WrongInjectedAlert);
26 |
27 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { createStructuredSelector } from 'reselect';
5 |
6 | import {
7 | makeSelectHasWeb3,
8 | makeSelectNetworkSupported,
9 | makeSelectWrongInjected,
10 | makeSelectIsLocked,
11 | makeSelectWeb3MethodValue,
12 | makeSelectIsWeb3Connected,
13 | } from '../../containers/AccountProvider/selectors';
14 |
15 | import PausedMessage from '../Web3Alerts/Paused';
16 | import NotConnectedMessage from '../Web3Alerts/NotConnected';
17 |
18 | import { conf } from '../../app.config';
19 |
20 | function Web3Alerts({ isConnected, paused }) {
21 | if (paused) {
22 | return
;
23 | }
24 |
25 | if (!isConnected) {
26 | return
;
27 | }
28 |
29 | return null;
30 | }
31 |
32 | Web3Alerts.propTypes = {
33 | paused: PropTypes.bool,
34 | isConnected: PropTypes.bool,
35 | };
36 |
37 | const mapStateToProps = createStructuredSelector({
38 | hasWeb3: makeSelectHasWeb3(),
39 | networkSupported: makeSelectNetworkSupported(),
40 | wrongInjected: makeSelectWrongInjected(),
41 | isLocked: makeSelectIsLocked(),
42 | isConnected: makeSelectIsWeb3Connected(),
43 | paused: makeSelectWeb3MethodValue(conf().contrAddr, 'paused'),
44 | });
45 |
46 | export default connect(mapStateToProps)(Web3Alerts);
47 |
--------------------------------------------------------------------------------
/app/containers/Web3Alerts/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import Alert from '../../components/Alert';
4 |
5 | export const Wrapper = styled(Alert)`
6 | max-width: 700px;
7 | padding: 15px;
8 | margin: 20px 0;
9 |
10 | h2 {
11 | font-size: 16px;
12 | margin: 0;
13 | }
14 |
15 | p {
16 | margin: 0;
17 | font-size: 14px;
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/app/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/favicon.png
--------------------------------------------------------------------------------
/app/global-styles.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from 'styled-components';
2 |
3 | /* eslint no-unused-expressions: 0 */
4 | injectGlobal`
5 |
6 | html {
7 | height: 100%;
8 | width: 100%;
9 | font-family: sans-serif;
10 | font-size: 10px;
11 | -webkit-tap-highlight-color: rgba(0,0,0,0);
12 | -webkit-text-size-adjust: 100%;
13 | -ms-text-size-adjust: 100%;
14 | }
15 |
16 | body {
17 | height: 100%;
18 | width: 100%;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-osx-font-smoothing: grayscale;
21 | display: block;
22 | margin: 0;
23 | line-height: 1.42857143;
24 | color: #333;
25 | background-color: #fff;
26 | font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;
27 | font-weight: 400;
28 | font-size: 14px;
29 | overflow-x: hidden;
30 | overflow-y: auto;
31 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
32 | }
33 |
34 | body:after, body:before {
35 | -webkit-box-sizing: border-box;
36 | -moz-box-sizing: border-box;
37 | box-sizing: border-box;
38 | }
39 |
40 | body.fontLoaded {
41 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
42 | }
43 |
44 | #app {
45 | background-color: #fafafa;
46 | min-height: 100%;
47 | min-width: 100%;
48 | }
49 |
50 | * {
51 | box-sizing: border-box;
52 | }
53 |
54 | `;
55 |
--------------------------------------------------------------------------------
/app/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * i18n.js
3 | *
4 | * This will setup the i18n language files and locale data for your app.
5 | *
6 | */
7 | import { addLocaleData } from 'react-intl';
8 | import enLocaleData from 'react-intl/locale-data/en';
9 | import deLocaleData from 'react-intl/locale-data/de';
10 |
11 | import { DEFAULT_LOCALE } from '../app/containers/App/actions';
12 |
13 | import enTranslationMessages from './translations/en.json';
14 | import deTranslationMessages from './translations/de.json';
15 |
16 | addLocaleData(enLocaleData);
17 | addLocaleData(deLocaleData);
18 |
19 | export const appLocales = [
20 | 'en',
21 | 'de',
22 | ];
23 |
24 | export const formatTranslationMessages = (locale, messages) => {
25 | const defaultFormattedMessages = locale !== DEFAULT_LOCALE
26 | ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages)
27 | : {};
28 | return Object.keys(messages).reduce((formattedMessages, key) => {
29 | const formattedMessage = !messages[key] && locale !== DEFAULT_LOCALE
30 | ? defaultFormattedMessages[key]
31 | : messages[key];
32 | return Object.assign(formattedMessages, { [key]: formattedMessage });
33 | }, {});
34 | };
35 |
36 | export const translationMessages = {
37 | en: formatTranslationMessages('en', enTranslationMessages),
38 | de: formatTranslationMessages('de', deTranslationMessages),
39 | };
40 |
--------------------------------------------------------------------------------
/app/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Acebusters - Blockchain Poker!",
3 | "icons": [
4 | {
5 | "src": "favicon.png",
6 | "sizes": "48x48",
7 | "type": "image/png",
8 | "density": 1.0
9 | },
10 | {
11 | "src": "favicon.png",
12 | "sizes": "96x96",
13 | "type": "image/png",
14 | "density": 2.0
15 | },
16 | {
17 | "src": "favicon.png",
18 | "sizes": "144x144",
19 | "type": "image/png",
20 | "density": 3.0
21 | },
22 | {
23 | "src": "favicon.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "density": 4.0
27 | }
28 | ],
29 | "start_url": "index.html",
30 | "display": "standalone",
31 | "orientation": "portrait",
32 | "background_color": "#FFFFFF"
33 | }
34 |
--------------------------------------------------------------------------------
/app/og-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/og-image.png
--------------------------------------------------------------------------------
/app/services/api.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 |
3 | class RequestError extends Error {
4 | constructor(response) {
5 | super(response.statusText);
6 | this.status = response.status;
7 | this.response = response;
8 | }
9 | }
10 |
11 | export const requestApi = (apiUrl) => async (method, path, params, headers = {}) => {
12 | const options = {
13 | method,
14 | headers: {
15 | Accept: 'application/json',
16 | 'Content-Type': 'application/json',
17 | ...headers,
18 | },
19 | body: params && JSON.stringify(params),
20 | };
21 |
22 | const response = await fetch(`${apiUrl}/${path}`, options);
23 | if (response.status >= 200 && response.status < 300) {
24 | const json = await response.json();
25 | return json;
26 | }
27 |
28 | if (response.status < 500) {
29 | throw new RequestError(response);
30 | }
31 |
32 | throw new Error('Server error');
33 | };
34 |
35 |
--------------------------------------------------------------------------------
/app/services/expiringLocalStorage.js:
--------------------------------------------------------------------------------
1 | import * as storageService from './localStorage';
2 |
3 | const storageKey = 'exp';
4 | let storage = {};
5 | let loaded = false;
6 |
7 | function check() {
8 | const now = Date.now();
9 | const keys = Object.keys(storage);
10 | for (let i = 0, il = keys.length; i < il; i += 1) {
11 | const key = keys[i];
12 | const value = storage[key];
13 | if (value.exp <= now) {
14 | delete storage[key];
15 | }
16 | }
17 | }
18 |
19 | function firstCall() {
20 | if (loaded) {
21 | return;
22 | }
23 |
24 | loaded = true;
25 | Object.assign(storage, storageService.getItem(storageKey));
26 | check();
27 | }
28 |
29 | export function setItem(key, value, seconds) {
30 | firstCall();
31 | storage[key] = { value, exp: Date.now() + (seconds * 1000) };
32 | check();
33 | storageService.setItem(storageKey, storage);
34 | }
35 |
36 | export function getItem(key) {
37 | firstCall();
38 | check();
39 | return storage[key] ? storage[key].value : undefined;
40 | }
41 |
42 | export function removeItem(key) {
43 | firstCall();
44 | check();
45 | delete storage[key];
46 | storageService.setItem(storageKey, storage);
47 | }
48 |
49 | export function clearExpiringStorage() {
50 | loaded = false;
51 | storage = {};
52 | storageService.removeItem(storageKey);
53 | }
54 |
--------------------------------------------------------------------------------
/app/services/localStorage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by helge on 24.01.17.
3 | */
4 |
5 | const storageFallback = {};
6 |
7 | export function setItem(key, value) {
8 | const item = JSON.stringify(value);
9 | if (window && window.localStorage) {
10 | window.localStorage.setItem(key, item);
11 | } else {
12 | storageFallback[key] = item;
13 | }
14 | }
15 |
16 | export function getItem(key) {
17 | if (window && window.localStorage) {
18 | const val = window.localStorage.getItem(key);
19 | if (val) {
20 | try {
21 | return JSON.parse(val);
22 | } catch (err) {
23 | window.localStorage.removeItem(key);
24 | return err;
25 | }
26 | }
27 | } else {
28 | const val = storageFallback[key];
29 | if (val) {
30 | try {
31 | return JSON.parse(val);
32 | } catch (err) {
33 | delete storageFallback[key];
34 | return err;
35 | }
36 | }
37 | }
38 | return undefined;
39 | }
40 |
41 | export function removeItem(key) {
42 | if (window && window.localStorage) {
43 | window.localStorage.removeItem(key);
44 | } else {
45 | delete storageFallback[key];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/services/reservationService.js:
--------------------------------------------------------------------------------
1 | import { conf } from '../app.config';
2 | import { requestApi } from './api';
3 |
4 | const request = requestApi(conf().reservationUrl);
5 |
6 | export function reserve(tableAddr, pos, signerAddr, txHash, amount) {
7 | return request('post', `reserve/table/${tableAddr}/${pos}`, {
8 | signerAddr,
9 | txHash,
10 | amount,
11 | });
12 | }
13 |
14 | export function lineup(tableAddr) {
15 | return request('get', `lineup/${tableAddr}`);
16 | }
17 |
--------------------------------------------------------------------------------
/app/services/sessionStorage.js:
--------------------------------------------------------------------------------
1 | const storageFallback = {};
2 |
3 | export function setItem(key, value) {
4 | const item = JSON.stringify(value);
5 | if (window && window.sessionStorage) {
6 | window.sessionStorage.setItem(key, item);
7 | } else {
8 | storageFallback[key] = item;
9 | }
10 | }
11 |
12 | export function getItem(key) {
13 | if (window && window.sessionStorage) {
14 | const val = window.sessionStorage.getItem(key);
15 | if (val) {
16 | try {
17 | return JSON.parse(val);
18 | } catch (err) {
19 | window.sessionStorage.removeItem(key);
20 | return err;
21 | }
22 | }
23 | } else {
24 | const val = storageFallback[key];
25 | if (val) {
26 | try {
27 | return JSON.parse(val);
28 | } catch (err) {
29 | delete storageFallback[key];
30 | return err;
31 | }
32 | }
33 | }
34 | return undefined;
35 | }
36 |
37 | export function removeItem(key) {
38 | if (window && window.sessionStorage) {
39 | window.sessionStorage.removeItem(key);
40 | } else {
41 | delete storageFallback[key];
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/services/transactions.js:
--------------------------------------------------------------------------------
1 | import { conf } from '../app.config';
2 | import { requestApi } from './api';
3 |
4 | const request = requestApi(conf().accountUrl);
5 |
6 | export function sendTx(forwardReceipt, resetConfReceipt) {
7 | return request('post', 'forward', { forwardReceipt, resetConfReceipt });
8 | }
9 |
--------------------------------------------------------------------------------
/app/sounds/assets/191678__porphyr__waterdrop.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/191678__porphyr__waterdrop.wav
--------------------------------------------------------------------------------
/app/sounds/assets/219069__annabloom__click1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/219069__annabloom__click1.wav
--------------------------------------------------------------------------------
/app/sounds/assets/232209__timbre__indicator-announcement-140404.flac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/232209__timbre__indicator-announcement-140404.flac
--------------------------------------------------------------------------------
/app/sounds/assets/232210__timbre__bing-bong-bong-bing-like-peripheral-plugged-unplugged.flac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/232210__timbre__bing-bong-bong-bing-like-peripheral-plugged-unplugged.flac
--------------------------------------------------------------------------------
/app/sounds/assets/235911__thegertz__notification-sound.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/235911__thegertz__notification-sound.wav
--------------------------------------------------------------------------------
/app/sounds/assets/238995__qubodup__beep-ping.flac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/238995__qubodup__beep-ping.flac
--------------------------------------------------------------------------------
/app/sounds/assets/277031__headphaze__ui-completed-status-alert-notification-sfx003.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/277031__headphaze__ui-completed-status-alert-notification-sfx003.wav
--------------------------------------------------------------------------------
/app/sounds/assets/321104__nsstudios__blip2.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/321104__nsstudios__blip2.wav
--------------------------------------------------------------------------------
/app/sounds/assets/33788__jobro__5-beep-b.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/33788__jobro__5-beep-b.wav
--------------------------------------------------------------------------------
/app/sounds/assets/342749__rhodesmas__notification-01.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/342749__rhodesmas__notification-01.wav
--------------------------------------------------------------------------------
/app/sounds/assets/350867__cabled-mess__blip-c-05.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/350867__cabled-mess__blip-c-05.wav
--------------------------------------------------------------------------------
/app/sounds/assets/372200__original-sound__error-bleep-1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/372200__original-sound__error-bleep-1.mp3
--------------------------------------------------------------------------------
/app/sounds/assets/380482__josepharaoh99__chime-notification.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/380482__josepharaoh99__chime-notification.wav
--------------------------------------------------------------------------------
/app/sounds/assets/388402__parabolix__namaste-mother-fucker.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/acebusters/acebusters-frontend/be82711fe155cf1b15d7bc63274c644fb814f36a/app/sounds/assets/388402__parabolix__namaste-mother-fucker.wav
--------------------------------------------------------------------------------
/app/sounds/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const isPlayerTurn = new Audio(require('./assets/33788__jobro__5-beep-b.wav'));
3 | const actionBarClick = new Audio(require('./assets/219069__annabloom__click1.wav'));
4 |
5 | export function playIsPlayerTurn() {
6 | isPlayerTurn.play();
7 | }
8 |
9 | export function playActionBarClick() {
10 | actionBarClick.volume = 0.2;
11 | actionBarClick.play();
12 | }
13 |
--------------------------------------------------------------------------------
/app/tests/i18n.test.js:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LOCALE } from '../containers/App/actions';
2 | import { formatTranslationMessages } from '../i18n';
3 |
4 | jest.mock('../translations/en.json', () => (
5 | {
6 | message1: 'default message',
7 | message2: 'default message 2',
8 | }
9 | ));
10 |
11 | const esTranslationMessages = {
12 | message1: 'mensaje predeterminado',
13 | message2: '',
14 | };
15 |
16 | describe('formatTranslationMessages', () => {
17 | it('should build only defaults when DEFAULT_LOCALE', () => {
18 | const result = formatTranslationMessages(DEFAULT_LOCALE, { a: 'a' });
19 |
20 | expect(result).toEqual({ a: 'a' });
21 | });
22 |
23 |
24 | it('should combine default locale and current locale when not DEFAULT_LOCALE', () => {
25 | const result = formatTranslationMessages('', esTranslationMessages);
26 |
27 | expect(result).toEqual({
28 | message1: 'mensaje predeterminado',
29 | message2: 'default message 2',
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/app/tests/store.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test store addons
3 | */
4 |
5 | import { browserHistory } from 'react-router';
6 | import configureStore from '../store';
7 |
8 | describe('configureStore', () => {
9 | let store;
10 |
11 | beforeAll(() => {
12 | store = configureStore({}, browserHistory);
13 | });
14 |
15 | describe('asyncReducers', () => {
16 | it('should contain an object for async reducers', () => {
17 | expect(typeof store.asyncReducers).toBe('object');
18 | });
19 | });
20 |
21 | describe('runSaga', () => {
22 | it('should contain a hook for `sagaMiddleware.run`', () => {
23 | expect(typeof store.runSaga).toBe('function');
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/app/utils/composeReducers.js:
--------------------------------------------------------------------------------
1 | const identity = (a) => a;
2 |
3 | /**
4 | * Composes given reducers into one
5 | *
6 | * @param {...Function} reducers
7 | * @returns {Function} composeReducers(a, b, c)(state, action) === a(b(c(state, action), action), action)
8 | */
9 |
10 | export function composeReducers(...reducers) {
11 | if (reducers.length === 0) {
12 | return identity;
13 | }
14 |
15 | if (reducers.length === 1) {
16 | return reducers[0];
17 | }
18 |
19 | return reducers.reduce((a, b) => (state, action) => a(b(state, action), action));
20 | }
21 |
--------------------------------------------------------------------------------
/app/utils/index.js:
--------------------------------------------------------------------------------
1 | export const identity = (a) => a;
2 |
3 | export const last = (arr, n = 1) => arr[arr.length - n];
4 |
5 | export const not = (fn) => (...args) => !fn(...args);
6 |
7 | export function round(n, prec) {
8 | const dec = 10 ** prec;
9 | return Math.round(n * dec) / dec;
10 | }
11 |
--------------------------------------------------------------------------------
/app/utils/makeCancelable.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Based on https://github.com/facebook/react/issues/5465#issuecomment-157888325
3 | */
4 |
5 | export const makeCancelable = (promise) => {
6 | let hasCanceled = false;
7 |
8 | const wrappedPromise = new Promise((resolve, reject) => {
9 | promise.then(
10 | (val) => hasCanceled ? reject({ isCanceled: true }) : resolve(val),
11 | (error) => hasCanceled ? reject({ isCanceled: true }) : reject(error)
12 | );
13 | });
14 |
15 | wrappedPromise.cancel = () => {
16 | hasCanceled = true;
17 | };
18 |
19 | return wrappedPromise;
20 | };
21 |
--------------------------------------------------------------------------------
/app/utils/promisifyWeb3Call.js:
--------------------------------------------------------------------------------
1 | export function promisifyWeb3Call(method) {
2 | return (...args) => new Promise((resolve, reject) => {
3 | method(
4 | ...args,
5 | (err, result) => {
6 | if (err) {
7 | reject(err);
8 | } else {
9 | resolve(result);
10 | }
11 | }
12 | );
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/app/utils/tests/amountFormatter.test.js:
--------------------------------------------------------------------------------
1 | import BigNumber from 'bignumber.js';
2 | import { formatAmount, ETH_DECIMALS } from '../amountFormatter';
3 |
4 | describe('formatAmount', () => {
5 | it('should convert value according decimals and format this amount', () => {
6 | expect(formatAmount(ETH_DECIMALS, new BigNumber(1).mul(ETH_DECIMALS))).toEqual('1');
7 | expect(formatAmount(ETH_DECIMALS, new BigNumber(1000).mul(ETH_DECIMALS))).toEqual('1,000');
8 | expect(formatAmount(ETH_DECIMALS, new BigNumber(1000).mul(ETH_DECIMALS).toString())).toEqual('1,000');
9 | expect(formatAmount(ETH_DECIMALS, new BigNumber(1000.54).mul(ETH_DECIMALS).toString())).toEqual('1,000.54');
10 | expect(formatAmount(ETH_DECIMALS, new BigNumber(1000.55).mul(ETH_DECIMALS).toString(), 1)).toEqual('1,000.6');
11 | });
12 |
13 | it('should returns 0 for null or undefined amount', () => {
14 | expect(formatAmount(ETH_DECIMALS, null)).toEqual('0');
15 | expect(formatAmount(ETH_DECIMALS)).toEqual('0');
16 | expect(formatAmount(ETH_DECIMALS, null, 2)).toEqual('0.00');
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/app/utils/tests/composeReducers.test.js:
--------------------------------------------------------------------------------
1 | import { composeReducers } from '../composeReducers';
2 |
3 | describe('composeReducers', () => {
4 | it('should return identity function when calling without args', () => {
5 | expect(composeReducers()(1)).toEqual(1);
6 | expect(composeReducers()('String')).toEqual('String');
7 | });
8 |
9 | it('should return first argument if called with only one argument', () => {
10 | const add = (a, b) => a + b;
11 | const mul = (a, b) => a * b;
12 | expect(composeReducers(add)(1, 2)).toEqual(3);
13 | expect(composeReducers(add)(5, 2)).toEqual(7);
14 | expect(composeReducers(mul)(1, 2)).toEqual(2);
15 | expect(composeReducers(mul)(2, 2)).toEqual(4);
16 | });
17 |
18 | it('should return new reducer that composes passed reducers and call it from right to left', () => {
19 | const add = (a, b) => a + b;
20 | const mul = (a, b) => a * b;
21 | expect(composeReducers(add, mul)(1, 2)).toEqual(4);
22 | expect(composeReducers(mul, add)(1, 2)).toEqual(6);
23 | expect(composeReducers(mul, mul)(1, 2)).toEqual(4);
24 | expect(composeReducers(add, add)(1, 2)).toEqual(5);
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | class {{ properCase name }} extends React.Component { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {{#if wantMessages}}
20 |
21 | {{/if}}
22 |
23 | );
24 | }
25 | }
26 |
27 | {{ properCase name }}.propTypes = {
28 |
29 | };
30 |
31 | export default {{ properCase name }};
32 |
--------------------------------------------------------------------------------
/internals/generators/component/es6.pure.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | class {{ properCase name }} extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
16 | render() {
17 | return (
18 |
19 | {{#if wantMessages}}
20 |
21 | {{/if}}
22 |
23 | );
24 | }
25 | }
26 |
27 | {{ properCase name }}.propTypes = {
28 |
29 | };
30 |
31 | export default {{ properCase name }};
32 |
--------------------------------------------------------------------------------
/internals/generators/component/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{ properCase name }} Messages
3 | *
4 | * This contains all the text for the {{ properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.{{ properCase name }}.header',
11 | defaultMessage: 'This is the {{ properCase name}} component !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/component/stateless.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase name }}
4 | *
5 | */
6 |
7 | import React from 'react';
8 | // import styled from 'styled-components';
9 |
10 | {{#if wantMessages}}
11 | import { FormattedMessage } from 'react-intl';
12 | import messages from './messages';
13 | {{/if}}
14 |
15 | function {{ properCase name }}() {
16 | return (
17 |
18 | {{#if wantMessages}}
19 |
20 | {{/if}}
21 |
22 | );
23 | }
24 |
25 | {{ properCase name }}.propTypes = {
26 |
27 | };
28 |
29 | export default {{ properCase name }};
30 |
--------------------------------------------------------------------------------
/internals/generators/component/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import { shallow } from 'enzyme';
3 |
4 | // import {{ properCase name }} from '../index';
5 |
6 | describe('<{{ properCase name }} />', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/container/actions.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} actions
4 | *
5 | */
6 |
7 | import {
8 | DEFAULT_ACTION,
9 | } from './constants';
10 |
11 | export function defaultAction() {
12 | return {
13 | type: DEFAULT_ACTION,
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/internals/generators/container/actions.test.js.hbs:
--------------------------------------------------------------------------------
1 |
2 | import {
3 | defaultAction,
4 | } from '../actions';
5 | import {
6 | DEFAULT_ACTION,
7 | } from '../constants';
8 |
9 | describe('{{ properCase name }} actions', () => {
10 | describe('Default Action', () => {
11 | it('has a type of DEFAULT_ACTION', () => {
12 | const expected = {
13 | type: DEFAULT_ACTION,
14 | };
15 | expect(defaultAction()).toEqual(expected);
16 | });
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/internals/generators/container/constants.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} constants
4 | *
5 | */
6 |
7 | export const DEFAULT_ACTION = 'app/{{ properCase name }}/DEFAULT_ACTION';
8 |
--------------------------------------------------------------------------------
/internals/generators/container/messages.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | * {{properCase name }} Messages
3 | *
4 | * This contains all the text for the {{properCase name }} component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.containers.{{properCase name }}.header',
11 | defaultMessage: 'This is {{properCase name}} container !',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/generators/container/reducer.js.hbs:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * {{ properCase name }} reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 | import {
9 | DEFAULT_ACTION,
10 | } from './constants';
11 |
12 | const initialState = fromJS({});
13 |
14 | function {{ camelCase name }}Reducer(state = initialState, action) {
15 | switch (action.type) {
16 | case DEFAULT_ACTION:
17 | return state;
18 | default:
19 | return state;
20 | }
21 | }
22 |
23 | export default {{ camelCase name }}Reducer;
24 |
--------------------------------------------------------------------------------
/internals/generators/container/reducer.test.js.hbs:
--------------------------------------------------------------------------------
1 |
2 | import { fromJS } from 'immutable';
3 | import {{ camelCase name }}Reducer from '../reducer';
4 |
5 | describe('{{ camelCase name }}Reducer', () => {
6 | it('returns the initial state', () => {
7 | expect({{ camelCase name }}Reducer(undefined, {})).toEqual(fromJS({}));
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/internals/generators/container/sagas.js.hbs:
--------------------------------------------------------------------------------
1 | // import { take, call, put, select } from 'redux-saga/effects';
2 |
3 | // Individual exports for testing
4 | export function* defaultSaga() {
5 | // See example in containers/HomePage/sagas.js
6 | }
7 |
8 | // All sagas to be loaded
9 | export default [
10 | defaultSaga,
11 | ];
12 |
--------------------------------------------------------------------------------
/internals/generators/container/sagas.test.js.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | * Test sagas
3 | */
4 |
5 | /* eslint-disable redux-saga/yield-effects */
6 | // import { take, call, put, select } from 'redux-saga/effects';
7 | // import { defaultSaga } from '../sagas';
8 |
9 | // const generator = defaultSaga();
10 |
11 | describe('defaultSaga Saga', () => {
12 | it('Expect to have unit tests specified', () => {
13 | expect(true).toEqual(false);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.js.hbs:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the {{ camelCase name }} state domain
5 | */
6 | const select{{ properCase name }}Domain = () => (state) => state.get('{{ camelCase name }}');
7 |
8 | /**
9 | * Other specific selectors
10 | */
11 |
12 |
13 | /**
14 | * Default selector used by {{ properCase name }}
15 | */
16 |
17 | const makeSelect{{ properCase name }} = () => createSelector(
18 | select{{ properCase name }}Domain(),
19 | (substate) => substate.toJS()
20 | );
21 |
22 | export default makeSelect{{ properCase name }};
23 | export {
24 | select{{ properCase name }}Domain,
25 | };
26 |
--------------------------------------------------------------------------------
/internals/generators/container/selectors.test.js.hbs:
--------------------------------------------------------------------------------
1 | // import { fromJS } from 'immutable';
2 | // import { makeSelect{{ properCase name }}Domain } from '../selectors';
3 |
4 | // const selector = makeSelect{{ properCase name}}Domain();
5 |
6 | describe('makeSelect{{ properCase name }}Domain', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/container/test.js.hbs:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | // import { shallow } from 'enzyme';
3 |
4 | // import { {{ properCase name }} } from '../index';
5 |
6 | describe('<{{ properCase name }} />', () => {
7 | it('Expect to have unit tests specified', () => {
8 | expect(true).toEqual(false);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/internals/generators/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * generator/index.js
3 | *
4 | * Exports the generators so plop knows them
5 | */
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const componentGenerator = require('./component/index.js');
10 | const containerGenerator = require('./container/index.js');
11 | const routeGenerator = require('./route/index.js');
12 | const languageGenerator = require('./language/index.js');
13 |
14 | module.exports = (plop) => {
15 | plop.setGenerator('component', componentGenerator);
16 | plop.setGenerator('container', containerGenerator);
17 | plop.setGenerator('route', routeGenerator);
18 | plop.setGenerator('language', languageGenerator);
19 | plop.addHelper('directory', (comp) => {
20 | try {
21 | fs.accessSync(path.join(__dirname, `../../app/containers/${comp}`), fs.F_OK);
22 | return `containers/${comp}`;
23 | } catch (e) {
24 | return `components/${comp}`;
25 | }
26 | });
27 | plop.addHelper('curly', (object, open) => (open ? '{' : '}'));
28 | };
29 |
--------------------------------------------------------------------------------
/internals/generators/language/add-locale-data.hbs:
--------------------------------------------------------------------------------
1 | $1addLocaleData({{language}}LocaleData);
2 |
--------------------------------------------------------------------------------
/internals/generators/language/app-locale.hbs:
--------------------------------------------------------------------------------
1 | $1 '{{language}}',
2 |
--------------------------------------------------------------------------------
/internals/generators/language/format-translation-messages.hbs:
--------------------------------------------------------------------------------
1 | $1 {{language}}: formatTranslationMessages('{{language}}', {{language}}TranslationMessages),
2 |
--------------------------------------------------------------------------------
/internals/generators/language/intl-locale-data.hbs:
--------------------------------------------------------------------------------
1 | $1import {{language}}LocaleData from 'react-intl/locale-data/{{language}}';
2 |
--------------------------------------------------------------------------------
/internals/generators/language/polyfill-intl-locale.hbs:
--------------------------------------------------------------------------------
1 | $1 import('intl/locale-data/jsonp/{{language}}.js'),
2 |
--------------------------------------------------------------------------------
/internals/generators/language/translation-messages.hbs:
--------------------------------------------------------------------------------
1 | $1import {{language}}TranslationMessages from './translations/{{language}}.json';
2 |
--------------------------------------------------------------------------------
/internals/generators/language/translations-json.hbs:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/internals/generators/route/route.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(location, cb) {
5 | import('{{{directory (properCase component)}}}')
6 | .then(loadModule(cb))
7 | .catch(errorLoading);
8 | },
9 | },$1
10 |
--------------------------------------------------------------------------------
/internals/generators/route/routeWithReducer.hbs:
--------------------------------------------------------------------------------
1 | {
2 | path: '{{ path }}',
3 | name: '{{ camelCase component }}',
4 | getComponent(nextState, cb) {
5 | const importModules = Promise.all([
6 | import('containers/{{ properCase component }}/reducer'),
7 | {{#if useSagas}}
8 | import('containers/{{ properCase component }}/sagas'),
9 | {{/if}}
10 | import('containers/{{ properCase component }}'),
11 | ]);
12 |
13 | const renderRoute = loadModule(cb);
14 |
15 | importModules.then(([reducer,{{#if useSagas}} sagas,{{/if}} component]) => {
16 | injectReducer('{{ camelCase component }}', reducer.default);
17 | {{#if useSagas}}
18 | injectSagas(sagas.default);
19 | {{/if}}
20 | renderRoute(component);
21 | });
22 |
23 | importModules.catch(errorLoading);
24 | },
25 | },$1
26 |
--------------------------------------------------------------------------------
/internals/generators/utils/componentExists.js:
--------------------------------------------------------------------------------
1 | /**
2 | * componentExists
3 | *
4 | * Check whether the given component exist in either the components or containers directory
5 | */
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const pageComponents = fs.readdirSync(path.join(__dirname, '../../../app/components'));
10 | const pageContainers = fs.readdirSync(path.join(__dirname, '../../../app/containers'));
11 | const components = pageComponents.concat(pageContainers);
12 |
13 | function componentExists(comp) {
14 | return components.indexOf(comp) >= 0;
15 | }
16 |
17 | module.exports = componentExists;
18 |
--------------------------------------------------------------------------------
/internals/mocks/cssModule.js:
--------------------------------------------------------------------------------
1 | module.exports = 'CSS_MODULE';
2 |
--------------------------------------------------------------------------------
/internals/mocks/image.js:
--------------------------------------------------------------------------------
1 | module.exports = 'IMAGE_MOCK';
2 |
--------------------------------------------------------------------------------
/internals/scripts/analyze.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const shelljs = require('shelljs');
4 | const animateProgress = require('./helpers/progress');
5 | const chalk = require('chalk');
6 | const addCheckMark = require('./helpers/checkmark');
7 |
8 | const progress = animateProgress('Generating stats');
9 |
10 | // Generate stats.json file with webpack
11 | shelljs.exec(
12 | 'webpack --config internals/webpack/webpack.prod.babel.js --profile --json > stats.json',
13 | addCheckMark.bind(null, callback) // Output a checkmark on completion
14 | );
15 |
16 | // Called after webpack has finished generating the stats.json file
17 | function callback() {
18 | clearInterval(progress);
19 | process.stdout.write(
20 | '\n\nOpen ' + chalk.magenta('http://webpack.github.io/analyse/') + ' in your browser and upload the stats.json file!' +
21 | chalk.blue('\n(Tip: ' + chalk.italic('CMD + double-click') + ' the link!)\n\n')
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/checkmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark check symbol
5 | */
6 | function addCheckMark(callback) {
7 | process.stdout.write(chalk.green(' ✓'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addCheckMark;
12 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/progress.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const readline = require('readline');
4 |
5 | /**
6 | * Adds an animated progress indicator
7 | *
8 | * @param {string} message The message to write next to the indicator
9 | * @param {number} amountOfDots The amount of dots you want to animate
10 | */
11 | function animateProgress(message, amountOfDots) {
12 | if (typeof amountOfDots !== 'number') {
13 | amountOfDots = 3;
14 | }
15 |
16 | let i = 0;
17 | return setInterval(function() {
18 | readline.cursorTo(process.stdout, 0);
19 | i = (i + 1) % (amountOfDots + 1);
20 | const dots = new Array(i + 1).join('.');
21 | process.stdout.write(message + dots);
22 | }, 500);
23 | }
24 |
25 | module.exports = animateProgress;
26 |
--------------------------------------------------------------------------------
/internals/scripts/helpers/xmark.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | /**
4 | * Adds mark cross symbol
5 | */
6 | function addXMark(callback) {
7 | process.stdout.write(chalk.red(' ✘'));
8 | if (callback) callback();
9 | }
10 |
11 | module.exports = addXMark;
12 |
--------------------------------------------------------------------------------
/internals/scripts/npmcheckversion.js:
--------------------------------------------------------------------------------
1 | const exec = require('child_process').exec;
2 | exec('npm -v', function (err, stdout, stderr) {
3 | if (err) throw err;
4 | if (parseFloat(stdout) < 3) {
5 | throw new Error('[ERROR: React Boilerplate] You need npm version @>=3');
6 | process.exit(1);
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * AppConstants
3 | * Each action has a corresponding type, which the reducer knows and picks up on.
4 | * To avoid weird typos between the reducer and the actions, we save them as
5 | * constants here. We prefix them with 'yourproject/YourComponent' so we avoid
6 | * reducers accidentally picking up actions they shouldn't.
7 | *
8 | * Follow this format:
9 | * export const YOUR_ACTION_CONSTANT = 'yourproject/YourContainer/YOUR_ACTION_CONSTANT';
10 | */
11 |
12 | export const DEFAULT_LOCALE = 'en';
13 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * App.react.js
4 | *
5 | * This component is the skeleton around the actual pages, and should only
6 | * contain code that should be seen on all pages. (e.g. navigation bar)
7 | *
8 | * NOTE: while this component should technically be a stateless functional
9 | * component (SFC), hot reloading does not currently support SFCs. If hot
10 | * reloading is not a necessity for you then you can refactor it and remove
11 | * the linting exception.
12 | */
13 |
14 | import React from 'react';
15 | import PropTypes from 'prop-types';
16 |
17 | export default class App extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
18 |
19 | static propTypes = {
20 | children: PropTypes.node,
21 | };
22 |
23 | render() {
24 | return (
25 |
26 | {React.Children.toArray(this.props.children)}
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/selectors.js:
--------------------------------------------------------------------------------
1 | // makeSelectLocationState expects a plain JS object for the routing state
2 | const makeSelectLocationState = () => {
3 | let prevRoutingState;
4 | let prevRoutingStateJS;
5 |
6 | return (state) => {
7 | const routingState = state.get('route'); // or state.route
8 |
9 | if (!routingState.equals(prevRoutingState)) {
10 | prevRoutingState = routingState;
11 | prevRoutingStateJS = routingState.toJS();
12 | }
13 |
14 | return prevRoutingStateJS;
15 | };
16 | };
17 |
18 | export {
19 | makeSelectLocationState,
20 | };
21 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { shallow } from 'enzyme';
3 |
4 | import App from '../index';
5 |
6 | describe('
', () => {
7 | it('should render its children', () => {
8 | const children = (
Test
);
9 | const renderedComponent = shallow(
10 |
11 | {children}
12 |
13 | );
14 | expect(renderedComponent.contains(children)).toBe(true);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/internals/templates/containers/App/tests/selectors.test.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from 'immutable';
2 |
3 | import { makeSelectLocationState } from 'containers/App/selectors';
4 |
5 | describe('makeSelectLocationState', () => {
6 | it('should select the route as a plain JS object', () => {
7 | const route = fromJS({
8 | locationBeforeTransitions: null,
9 | });
10 | const mockedState = fromJS({
11 | route,
12 | });
13 | expect(makeSelectLocationState()(mockedState)).toEqual(route.toJS());
14 | });
15 |
16 | it('should return cached js routeState for same concurrent calls', () => {
17 | const route = fromJS({
18 | locationBeforeTransitions: null,
19 | });
20 | const mockedState = fromJS({
21 | route,
22 | });
23 | const selectLocationState = makeSelectLocationState();
24 |
25 | const firstRouteStateJS = selectLocationState(mockedState);
26 | expect(selectLocationState(mockedState)).toBe(firstRouteStateJS);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/internals/templates/containers/HomePage/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage
3 | *
4 | * This is the first thing users see of our App, at the '/' route
5 | *
6 | * NOTE: while this component should technically be a stateless functional
7 | * component (SFC), hot reloading does not currently support SFCs. If hot
8 | * reloading is not a necessity for you then you can refactor it and remove
9 | * the linting exception.
10 | */
11 |
12 | import React from 'react';
13 | import { FormattedMessage } from 'react-intl';
14 | import messages from './messages';
15 |
16 | export default class HomePage extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
17 | render() {
18 | return (
19 |
20 |
21 |
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/internals/templates/containers/HomePage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HomePage Messages
3 | *
4 | * This contains all the text for the HomePage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.HomePage.header',
11 | defaultMessage: 'This is HomePage component!',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/templates/containers/HomePage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { shallow } from 'enzyme';
4 |
5 | import HomePage from '../index';
6 | import messages from '../messages';
7 |
8 | describe('
', () => {
9 | it('should render the page message', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.contains(
14 |
15 | )).toEqual(true);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider actions
4 | *
5 | */
6 |
7 | import {
8 | CHANGE_LOCALE,
9 | } from './constants';
10 |
11 | export function changeLocale(languageLocale) {
12 | return {
13 | type: CHANGE_LOCALE,
14 | locale: languageLocale,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider constants
4 | *
5 | */
6 |
7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE';
8 | export const DEFAULT_LOCALE = 'en';
9 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider
4 | *
5 | * this component connects the redux state language locale to the
6 | * IntlProvider component and i18n messages (loaded from `app/translations`)
7 | */
8 |
9 | import React from 'react';
10 | import PropTypes from 'prop-types';
11 | import { connect } from 'react-redux';
12 | import { createSelector } from 'reselect';
13 | import { IntlProvider } from 'react-intl';
14 |
15 | import { makeSelectLocale } from './selectors';
16 |
17 | export class LanguageProvider extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
18 | render() {
19 | return (
20 |
21 | {React.Children.only(this.props.children)}
22 |
23 | );
24 | }
25 | }
26 |
27 | LanguageProvider.propTypes = {
28 | locale: PropTypes.string,
29 | messages: PropTypes.object,
30 | children: PropTypes.element.isRequired,
31 | };
32 |
33 |
34 | const mapStateToProps = createSelector(
35 | makeSelectLocale(),
36 | (locale) => ({ locale })
37 | );
38 |
39 | function mapDispatchToProps(dispatch) {
40 | return {
41 | dispatch,
42 | };
43 | }
44 |
45 | export default connect(mapStateToProps, mapDispatchToProps)(LanguageProvider);
46 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/reducer.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * LanguageProvider reducer
4 | *
5 | */
6 |
7 | import { fromJS } from 'immutable';
8 |
9 | import {
10 | CHANGE_LOCALE,
11 | } from './constants';
12 | import {
13 | DEFAULT_LOCALE,
14 | } from '../App/constants'; // eslint-disable-line
15 |
16 | const initialState = fromJS({
17 | locale: DEFAULT_LOCALE,
18 | });
19 |
20 | function languageProviderReducer(state = initialState, action) {
21 | switch (action.type) {
22 | case CHANGE_LOCALE:
23 | return state
24 | .set('locale', action.locale);
25 | default:
26 | return state;
27 | }
28 | }
29 |
30 | export default languageProviderReducer;
31 |
--------------------------------------------------------------------------------
/internals/templates/containers/LanguageProvider/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | /**
4 | * Direct selector to the languageToggle state domain
5 | */
6 | const selectLanguage = (state) => state.get('language');
7 |
8 | /**
9 | * Select the language locale
10 | */
11 |
12 | const makeSelectLocale = () => createSelector(
13 | selectLanguage,
14 | (languageState) => languageState.get('locale')
15 | );
16 |
17 | export {
18 | selectLanguage,
19 | makeSelectLocale,
20 | };
21 |
--------------------------------------------------------------------------------
/internals/templates/containers/NotFoundPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * NotFoundPage
3 | *
4 | * This is the page we show when the user visits a url that doesn't have a route
5 | *
6 | * NOTE: while this component should technically be a stateless functional
7 | * component (SFC), hot reloading does not currently support SFCs. If hot
8 | * reloading is not a necessity for you then you can refactor it and remove
9 | * the linting exception.
10 | */
11 |
12 | import React from 'react';
13 | import { FormattedMessage } from 'react-intl';
14 |
15 | import messages from './messages';
16 |
17 | export default class NotFound extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
18 | render() {
19 | return (
20 |
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/internals/templates/containers/NotFoundPage/messages.js:
--------------------------------------------------------------------------------
1 | /*
2 | * NotFoundPage Messages
3 | *
4 | * This contains all the text for the NotFoundPage component.
5 | */
6 | import { defineMessages } from 'react-intl';
7 |
8 | export default defineMessages({
9 | header: {
10 | id: 'app.components.NotFoundPage.header',
11 | defaultMessage: 'This is NotFoundPage component!',
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/internals/templates/containers/NotFoundPage/tests/index.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FormattedMessage } from 'react-intl';
3 | import { shallow } from 'enzyme';
4 |
5 | import NotFoundPage from '../index';
6 | import messages from '../messages';
7 |
8 | describe('
', () => {
9 | it('should render the page message', () => {
10 | const renderedComponent = shallow(
11 |
12 | );
13 | expect(renderedComponent.contains(
14 |
15 | )).toEqual(true);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/internals/templates/global-styles.js:
--------------------------------------------------------------------------------
1 | import { injectGlobal } from 'styled-components';
2 |
3 | /* eslint no-unused-expressions: 0 */
4 | injectGlobal`
5 | html,
6 | body {
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | body {
12 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
13 | }
14 |
15 | body.fontLoaded {
16 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
17 | }
18 |
19 | #app {
20 | background-color: #fafafa;
21 | min-height: 100%;
22 | min-width: 100%;
23 | }
24 |
25 | p,
26 | label {
27 | font-family: Georgia, Times, 'Times New Roman', serif;
28 | line-height: 1.5em;
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/internals/templates/i18n.js:
--------------------------------------------------------------------------------
1 | /**
2 | * i18n.js
3 | *
4 | * This will setup the i18n language files and locale data for your app.
5 | *
6 | */
7 | import { addLocaleData } from 'react-intl';
8 | import enLocaleData from 'react-intl/locale-data/en';
9 |
10 | import { DEFAULT_LOCALE } from './containers/App/constants'; // eslint-disable-line
11 | import enTranslationMessages from './translations/en.json';
12 |
13 | export const appLocales = [
14 | 'en',
15 | ];
16 |
17 | addLocaleData(enLocaleData);
18 |
19 | export const formatTranslationMessages = (locale, messages) => {
20 | const defaultFormattedMessages = locale !== DEFAULT_LOCALE
21 | ? formatTranslationMessages(DEFAULT_LOCALE, enTranslationMessages)
22 | : {};
23 | return Object.keys(messages).reduce((formattedMessages, key) => {
24 | let message = messages[key];
25 | if (!message && locale !== DEFAULT_LOCALE) {
26 | message = defaultFormattedMessages[key];
27 | }
28 | return Object.assign(formattedMessages, { [key]: message });
29 | }, {});
30 | };
31 |
32 | export const translationMessages = {
33 | en: formatTranslationMessages('en', enTranslationMessages),
34 | };
35 |
--------------------------------------------------------------------------------
/internals/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
React.js Boilerplate
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/internals/templates/tests/store.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test store addons
3 | */
4 |
5 | import { browserHistory } from 'react-router';
6 | import configureStore from '../store';
7 |
8 | describe('configureStore', () => {
9 | let store;
10 |
11 | beforeAll(() => {
12 | store = configureStore({}, browserHistory);
13 | });
14 |
15 | describe('asyncReducers', () => {
16 | it('should contain an object for async reducers', () => {
17 | expect(typeof store.asyncReducers).toEqual('object');
18 | });
19 | });
20 |
21 | describe('runSaga', () => {
22 | it('should contain a hook for `sagaMiddleware.run`', () => {
23 | expect(typeof store.runSaga).toEqual('function');
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/internals/templates/translations/en.json:
--------------------------------------------------------------------------------
1 | []
2 |
--------------------------------------------------------------------------------
/internals/testing/test-bundler.js:
--------------------------------------------------------------------------------
1 | // needed for regenerator-runtime
2 | // (ES7 generator support is required by redux-saga)
3 | import 'babel-polyfill';
4 | import 'raf/polyfill';
5 |
6 | import { configure } from 'enzyme';
7 | import Adapter from 'enzyme-adapter-react-15';
8 |
9 | configure({ adapter: new Adapter() });
10 |
11 |
--------------------------------------------------------------------------------
/internals/webpack/webpack.dll.babel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * WEBPACK DLL GENERATOR
3 | *
4 | * This profile is used to cache webpack's module
5 | * contexts for external library and framework type
6 | * dependencies which will usually not change often enough
7 | * to warrant building them from scratch every time we use
8 | * the webpack process.
9 | */
10 |
11 | const { join } = require('path');
12 | const defaults = require('lodash/defaultsDeep');
13 | const webpack = require('webpack');
14 | const pkg = require(join(process.cwd(), 'package.json'));
15 | const dllPlugin = require('../config').dllPlugin;
16 |
17 | if (!pkg.dllPlugin) { process.exit(0); }
18 |
19 | const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults);
20 | const outputPath = join(process.cwd(), dllConfig.path);
21 |
22 | module.exports = require('./webpack.base.babel')({
23 | context: process.cwd(),
24 | entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg),
25 | devtool: 'eval',
26 | output: {
27 | filename: '[name].dll.js',
28 | path: outputPath,
29 | library: '[name]',
30 | },
31 | plugins: [
32 | new webpack.DllPlugin({ name: '[name]', path: join(outputPath, '[name].json') }), // eslint-disable-line no-new
33 | ],
34 | performance: {
35 | hints: false,
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/scripts/deploy_production.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | declare -A lambdas
6 |
7 | lambdas["pr-account-service"]="account-service"
8 | lambdas["pr-contract-scanner"]="contract-scanner"
9 | lambdas["pr-event-worker"]="event-worker"
10 | lambdas["pr-inverval-scanner-2"]="interval-scanner"
11 | lambdas["pr-oracle-cashgame"]="oracle"
12 | lambdas["pr-seat-reservation"]="reserve-service"
13 | lambdas["pr-stream-scanner"]="stream-scanner"
14 |
15 | for function_name in ${!lambdas[@]}; do
16 | zip_name=${lambdas[${function_name}]}
17 |
18 | # Update Staging lambda with sandbox snapshot
19 | aws lambda update-function-code --function-name ${function_name} --s3-bucket builds.acebusters --s3-key staging/${zip_name}.zip
20 |
21 | # Copy staging snapshot as a Production snapshot
22 | aws s3 cp s3://builds.acebusters/staging/${zip_name}.zip s3://builds.acebusters/production/${zip_name}.zip
23 | done
24 |
25 | # Download sandbox frontend snapshot
26 | aws s3 cp s3://builds.acebusters/staging/ab-web-frontend.zip ab-web-frontend.zip
27 | unzip ab-web-frontend.zip -d build_production
28 |
29 | # Sync it to s3://staging.acebusters.com/
30 | aws s3 sync build_production/ s3://dapp.acebusters.com/ --delete --acl public-read
31 | rm -r build_production
32 |
33 | # Invalidate staging.acebusters.com CloudFront distribution
34 | aws cloudfront create-invalidation --distribution-id E3LJB6X8QH2BTQ --paths '/*'
35 |
--------------------------------------------------------------------------------
/scripts/deploy_staging.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | declare -A lambdas
6 |
7 | lambdas["st-account-service"]="account-service"
8 | lambdas["st-contract-scanner"]="contract-scanner"
9 | lambdas["st-event-worker"]="event-worker"
10 | lambdas["st-interval-scanner"]="interval-scanner"
11 | lambdas["st-oracle-cashgame"]="oracle"
12 | lambdas["st-seat-reservation"]="reserve-service"
13 | lambdas["st-stream-scanner"]="stream-scanner"
14 |
15 | for function_name in ${!lambdas[@]}; do
16 | zip_name=${lambdas[${function_name}]}
17 |
18 | # Update Staging lambda with sandbox snapshot
19 | aws lambda update-function-code --function-name ${function_name} --s3-bucket builds.acebusters --s3-key sandbox/${zip_name}.zip
20 |
21 | # Copy sandbox snapshot as a Staging snapshot
22 | aws s3 cp s3://builds.acebusters/sandbox/${zip_name}.zip s3://builds.acebusters/staging/${zip_name}.zip
23 | done
24 |
25 | # Download sandbox frontend snapshot
26 | aws s3 cp s3://builds.acebusters/sandbox/ab-web-frontend.zip ab-web-frontend.zip
27 | unzip ab-web-frontend.zip -d build_staging
28 |
29 | # Sync it to s3://staging.acebusters.com/
30 | aws s3 sync build_staging/ s3://staging.acebusters.com/ --delete --acl public-read
31 | rm -r build_staging
32 |
33 | # Invalidate staging.acebusters.com CloudFront distribution
34 | aws cloudfront create-invalidation --distribution-id E2IMWXGWRO29FC --paths '/*'
35 |
--------------------------------------------------------------------------------
/server/logger.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const chalk = require('chalk');
4 | const ip = require('ip');
5 |
6 | const divider = chalk.gray('\n-----------------------------------');
7 |
8 | /**
9 | * Logger middleware, you can customize it to make messages more personal
10 | */
11 | const logger = {
12 |
13 | // Called whenever there's an error on the server we want to print
14 | error: (err) => {
15 | console.error(chalk.red(err));
16 | },
17 |
18 | // Called when express.js app starts on given port w/o errors
19 | appStarted: (port, host, tunnelStarted) => {
20 | console.log(`Server started ! ${chalk.green('✓')}`);
21 |
22 | // If the tunnel started, log that and the URL it's available at
23 | if (tunnelStarted) {
24 | console.log(`Tunnel initialised ${chalk.green('✓')}`);
25 | }
26 |
27 | console.log(`
28 | ${chalk.bold('Access URLs:')}${divider}
29 | Localhost: ${chalk.magenta(`http://${host}:${port}`)}
30 | LAN: ${chalk.magenta(`http://${ip.address()}:${port}`) +
31 | (tunnelStarted ? `\n Proxy: ${chalk.magenta(tunnelStarted)}` : '')}${divider}
32 | ${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
33 | `);
34 | },
35 | };
36 |
37 | module.exports = logger;
38 |
--------------------------------------------------------------------------------