├── .editorconfig ├── .gitattributes ├── .gitignore ├── .storybook ├── addons.js ├── config.js ├── preview-head.html └── webpack.config.js ├── LICENSE ├── README.md ├── app ├── .htaccess ├── app.config.js ├── app.js ├── components │ ├── A │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── ActionBar │ │ ├── ActionButton.js │ │ ├── ControlBetRaise.js │ │ ├── ControlBlank.js │ │ ├── ControlCheckCall.js │ │ ├── ControlFold.js │ │ ├── FlagAmountBet.js │ │ ├── FlagAmountCall.js │ │ ├── FlagButton.js │ │ ├── index.js │ │ ├── styles.js │ │ └── tests │ │ │ ├── index.test.js │ │ │ ├── stories │ │ │ └── index.js │ │ │ └── tests.js │ ├── Alert │ │ └── index.js │ ├── AmountField │ │ └── index.js │ ├── App │ │ └── index.js │ ├── Blocky │ │ └── index.js │ ├── Button │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Card │ │ ├── BoardCard.js │ │ ├── BoardCards.js │ │ ├── HoleCard.js │ │ ├── HoleCards.js │ │ ├── index.js │ │ └── styles.js │ ├── Chat │ │ └── index.js │ ├── Container │ │ └── index.js │ ├── Content │ │ └── index.js │ ├── CopyInput │ │ ├── index.js │ │ └── styles.js │ ├── Curtain │ │ ├── CurtainToggler.js │ │ ├── index.js │ │ └── tests │ │ │ └── CurtainToggler.test.js │ ├── Dashboard │ │ ├── Advanced.js │ │ ├── Balances.js │ │ ├── GloryNumber.js │ │ ├── Overview.js │ │ ├── Tabs.js │ │ └── styles.js │ ├── FieldIntl │ │ └── index.js │ ├── Footer │ │ ├── Wrapper.js │ │ ├── index.js │ │ ├── messages.js │ │ └── tests │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── Form │ │ ├── FormField.js │ │ ├── FormGroup.js │ │ ├── FormInline.js │ │ ├── Slider.js │ │ └── styles.js │ ├── FormMessages │ │ └── index.js │ ├── Frames │ │ ├── Dashboard.js │ │ ├── Tables.js │ │ └── styles.js │ ├── H1 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── H2 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── H3 │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Header │ │ ├── Logo.js │ │ ├── NavItem.js │ │ ├── Navbar.js │ │ ├── ToggleButton.js │ │ ├── index.js │ │ ├── messages.js │ │ └── styles.js │ ├── Img │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── Input │ │ └── index.js │ ├── IssueIcon │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── JoinDialog │ │ ├── index.js │ │ └── styles.js │ ├── Label │ │ └── index.js │ ├── Link │ │ └── index.js │ ├── List │ │ └── index.js │ ├── ListItem │ │ └── index.js │ ├── LoadingButton │ │ └── index.js │ ├── LoadingIndicator │ │ ├── Circle.js │ │ ├── Wrapper.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Circle.test.js │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── Lobby │ │ ├── index.js │ │ ├── styles.js │ │ └── tests │ │ │ └── index.test.js │ ├── Logo │ │ ├── Ethereum.js │ │ ├── Nutz.js │ │ └── index.js │ ├── Modal │ │ ├── ConfirmDialog.js │ │ ├── ContainerTransitionGroup.js │ │ ├── DialogTransitionGroup.js │ │ ├── ModalsTransitionGroup.js │ │ ├── XButton.js │ │ ├── index.js │ │ └── styles.js │ ├── Notifications │ │ ├── Notification.js │ │ ├── index.js │ │ └── styles.js │ ├── Pot │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ ├── ProgressBar │ │ ├── Percent.js │ │ ├── ProgressBar.js │ │ ├── Wrapper.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Percent.test.js │ │ │ ├── ProgressBar.test.js │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── RadialProgress │ │ ├── enhancer.js │ │ └── index.js │ ├── Seat │ │ ├── ButtonInvite.js │ │ ├── ButtonJoinSeat.js │ │ ├── ButtonOpenSeat.js │ │ ├── Seat.js │ │ ├── SeatInfo.js │ │ ├── SeatTimer.js │ │ ├── SitoutTimer.js │ │ ├── StatusAction.js │ │ ├── index.js │ │ ├── styles.js │ │ └── tests │ │ │ ├── ButtonJoinSeat.test.js │ │ │ ├── Seat.test.js │ │ │ ├── SeatComponent.test.js │ │ │ ├── SeatInfo.test.js │ │ │ ├── SeatTimer.test.js │ │ │ └── StatusAction.test.js │ ├── Slider │ │ ├── index.js │ │ └── tests │ │ │ ├── index.test.js │ │ │ └── stories │ │ │ └── index.js │ ├── Slides │ │ └── index.js │ ├── SubmitButton │ │ └── index.js │ ├── Table │ │ ├── index.js │ │ ├── styles.js │ │ ├── tableBG.svg │ │ └── tests │ │ │ └── index.test.js │ ├── TableMenu │ │ ├── MenuHeader.js │ │ ├── MenuItem.js │ │ ├── index.js │ │ ├── stories │ │ │ └── index.js │ │ ├── styles.js │ │ └── tests │ │ │ ├── MenuHeader.test.js │ │ │ ├── MenuItem.test.js │ │ │ └── index.test.js │ ├── Timed │ │ └── index.js │ ├── Toggle │ │ ├── Select.js │ │ ├── index.js │ │ └── tests │ │ │ ├── Select.test.js │ │ │ └── index.test.js │ ├── ToggleOption │ │ ├── index.js │ │ └── tests │ │ │ └── index.test.js │ └── WithLoading │ │ └── index.js ├── containers │ ├── AccountProvider │ │ ├── actions.js │ │ ├── generateContractApi.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── sagas │ │ │ ├── ethEventListenerSaga.js │ │ │ ├── index.js │ │ │ ├── injectedWeb3ListenerSaga.js │ │ │ ├── persistentTxSaga.js │ │ │ ├── txMonitoringSaga.js │ │ │ ├── txSagas.js │ │ │ ├── unsupportedNetworkDetectSaga.js │ │ │ ├── walletSagas.js │ │ │ ├── web3CallsSagas.js │ │ │ ├── web3ConnectSaga.js │ │ │ └── websocketSaga.js │ │ ├── selectors.js │ │ ├── tests │ │ │ ├── reducer.test.js │ │ │ └── selectors.test.js │ │ ├── utils.js │ │ └── web3Connect.js │ ├── ActionBar │ │ ├── actions.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── sagas.js │ │ ├── selectors.js │ │ └── tests │ │ │ ├── reducer.test.js │ │ │ └── selectors.test.js │ ├── App │ │ ├── actions.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── sagas │ │ │ ├── balancesLoadingSaga.js │ │ │ ├── gtmSagas.js │ │ │ └── index.js │ │ ├── selectors.js │ │ └── tests │ │ │ ├── reducer.test.js │ │ │ └── selectors.test.js │ ├── Chat │ │ ├── index.js │ │ ├── message-box.js │ │ └── message-list.js │ ├── Curtain │ │ ├── index.js │ │ └── messages.js │ ├── Dashboard │ │ ├── Advanced.js │ │ ├── Overview.js │ │ ├── actions.js │ │ ├── constants.js │ │ ├── index.js │ │ ├── messages.js │ │ ├── reducer.js │ │ ├── sagas │ │ │ ├── errorModalSaga.js │ │ │ ├── eventsSaga.js │ │ │ └── index.js │ │ ├── selectors.js │ │ ├── styles.js │ │ ├── tests │ │ │ └── reducer.test.js │ │ ├── txnsToList.js │ │ └── utils.js │ ├── ExportDialog │ │ └── index.js │ ├── GTM │ │ └── index.js │ ├── Header │ │ ├── actions.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── selectors.js │ │ └── tests │ │ │ ├── reducer.test.js │ │ │ └── selectors.test.js │ ├── ImportDialog │ │ └── index.js │ ├── InviteDialog │ │ ├── index.js │ │ └── messages.js │ ├── JoinDialog │ │ ├── index.js │ │ └── messages.js │ ├── LanguageProvider │ │ ├── actions.js │ │ ├── constants.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── selectors.js │ │ └── tests │ │ │ ├── actions.test.js │ │ │ ├── index.test.js │ │ │ ├── reducer.test.js │ │ │ └── selectors.test.js │ ├── Lobby │ │ ├── index.js │ │ └── selectors.js │ ├── LobbyItem │ │ ├── index.js │ │ └── selectors.js │ ├── LobbyMessage │ │ └── index.js │ ├── LocaleToggle │ │ ├── Wrapper.js │ │ ├── index.js │ │ ├── messages.js │ │ └── tests │ │ │ ├── Wrapper.test.js │ │ │ └── index.test.js │ ├── LoginProgressModal │ │ └── index.js │ ├── LogoutDialog │ │ └── index.js │ ├── Modal │ │ ├── constants.js │ │ └── index.js │ ├── NotFoundPage │ │ ├── index.js │ │ ├── messages.js │ │ └── tests │ │ │ └── index.test.js │ ├── Notifications │ │ ├── actions.js │ │ ├── constants.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── sagas │ │ │ ├── accountNotificationsSagas.js │ │ │ ├── connectionNotificationsSaga.js │ │ │ ├── index.js │ │ │ ├── tableNotificationsSaga.js │ │ │ └── utils.js │ │ └── selectors.js │ ├── Seat │ │ ├── index.js │ │ ├── selectors.js │ │ ├── tests │ │ │ └── selectors.test.js │ │ └── utils.js │ ├── Table │ │ ├── actions.js │ │ ├── index.js │ │ ├── messages.js │ │ ├── reducer.js │ │ ├── sagas │ │ │ ├── handRequestSaga.js │ │ │ ├── index.js │ │ │ ├── lineupScannerSaga.js │ │ │ ├── loadTableSaga.js │ │ │ ├── payFlowSaga.js │ │ │ ├── performBetSaga.js │ │ │ ├── performShowSaga.js │ │ │ ├── reservationSaga.js │ │ │ ├── sendMessageSaga.js │ │ │ ├── sitoutFlowSaga.js │ │ │ ├── submitSignedNettingSaga.js │ │ │ └── updateScannerSaga.js │ │ ├── selectors.js │ │ └── tests │ │ │ ├── consts.js │ │ │ ├── reducer.test.js │ │ │ ├── sagas.test.js │ │ │ └── selectors.test.js │ ├── TableDebug │ │ ├── index.js │ │ ├── loadContractData.js │ │ ├── requestStat.js │ │ ├── styles.js │ │ └── utils.js │ ├── TableMenu │ │ ├── actions.js │ │ ├── index.js │ │ ├── reducer.js │ │ ├── sagas.js │ │ ├── selectors.js │ │ └── tests │ │ │ ├── reducer.test.js │ │ │ └── selectors.test.js │ ├── TxSubmit │ │ ├── index.js │ │ └── styles.js │ └── Web3Alerts │ │ ├── NoInjected.js │ │ ├── NoWeb3.js │ │ ├── NotConnected.js │ │ ├── Paused.js │ │ ├── UnsupportedNetwork.js │ │ ├── WrongInjected.js │ │ ├── index.js │ │ └── styles.js ├── favicon.png ├── global-styles.js ├── i18n.js ├── index.html ├── manifest.json ├── og-image.png ├── reducers.js ├── routes.js ├── services │ ├── account.js │ ├── api.js │ ├── blockies.js │ ├── errors.js │ ├── expiringLocalStorage.js │ ├── localStorage.js │ ├── nicknames.js │ ├── reduxFormSaga.js │ ├── reservationService.js │ ├── sessionStorage.js │ ├── tableNames.js │ ├── tableService.js │ ├── transactions.js │ └── wsProvider.js ├── sounds │ ├── assets │ │ ├── 191678__porphyr__waterdrop.wav │ │ ├── 219069__annabloom__click1.wav │ │ ├── 232209__timbre__indicator-announcement-140404.flac │ │ ├── 232210__timbre__bing-bong-bong-bing-like-peripheral-plugged-unplugged.flac │ │ ├── 235911__thegertz__notification-sound.wav │ │ ├── 238995__qubodup__beep-ping.flac │ │ ├── 277031__headphaze__ui-completed-status-alert-notification-sfx003.wav │ │ ├── 321104__nsstudios__blip2.wav │ │ ├── 33788__jobro__5-beep-b.wav │ │ ├── 342749__rhodesmas__notification-01.wav │ │ ├── 350867__cabled-mess__blip-c-05.wav │ │ ├── 372200__original-sound__error-bleep-1.mp3 │ │ ├── 380482__josepharaoh99__chime-notification.wav │ │ └── 388402__parabolix__namaste-mother-fucker.wav │ └── index.js ├── store.js ├── tests │ ├── i18n.test.js │ └── store.test.js ├── translations │ ├── de.json │ └── en.json ├── utils │ ├── amountFormatter.js │ ├── asyncInjectors.js │ ├── composeReducers.js │ ├── index.js │ ├── inputValidators.js │ ├── makeCancelable.js │ ├── promisifyWeb3Call.js │ ├── styleUtils.js │ ├── tests │ │ ├── amountFormatter.test.js │ │ ├── asyncInjectors.test.js │ │ └── composeReducers.test.js │ └── waitForTx.js └── variables.js ├── appveyor.yml ├── internals ├── config.js ├── generators │ ├── component │ │ ├── es6.js.hbs │ │ ├── es6.pure.js.hbs │ │ ├── index.js │ │ ├── messages.js.hbs │ │ ├── stateless.js.hbs │ │ └── test.js.hbs │ ├── container │ │ ├── actions.js.hbs │ │ ├── actions.test.js.hbs │ │ ├── constants.js.hbs │ │ ├── index.js │ │ ├── index.js.hbs │ │ ├── messages.js.hbs │ │ ├── reducer.js.hbs │ │ ├── reducer.test.js.hbs │ │ ├── sagas.js.hbs │ │ ├── sagas.test.js.hbs │ │ ├── selectors.js.hbs │ │ ├── selectors.test.js.hbs │ │ └── test.js.hbs │ ├── index.js │ ├── language │ │ ├── add-locale-data.hbs │ │ ├── app-locale.hbs │ │ ├── format-translation-messages.hbs │ │ ├── index.js │ │ ├── intl-locale-data.hbs │ │ ├── polyfill-intl-locale.hbs │ │ ├── translation-messages.hbs │ │ └── translations-json.hbs │ ├── route │ │ ├── index.js │ │ ├── route.hbs │ │ └── routeWithReducer.hbs │ └── utils │ │ └── componentExists.js ├── mocks │ ├── cssModule.js │ └── image.js ├── scripts │ ├── analyze.js │ ├── clean.js │ ├── dependencies.js │ ├── extract-intl.js │ ├── generate-templates-for-linting.js │ ├── helpers │ │ ├── checkmark.js │ │ ├── progress.js │ │ └── xmark.js │ └── npmcheckversion.js ├── templates │ ├── app.js │ ├── containers │ │ ├── App │ │ │ ├── constants.js │ │ │ ├── index.js │ │ │ ├── selectors.js │ │ │ └── tests │ │ │ │ ├── index.test.js │ │ │ │ └── selectors.test.js │ │ ├── HomePage │ │ │ ├── index.js │ │ │ ├── messages.js │ │ │ └── tests │ │ │ │ └── index.test.js │ │ ├── LanguageProvider │ │ │ ├── actions.js │ │ │ ├── constants.js │ │ │ ├── index.js │ │ │ ├── reducer.js │ │ │ └── selectors.js │ │ └── NotFoundPage │ │ │ ├── index.js │ │ │ ├── messages.js │ │ │ └── tests │ │ │ └── index.test.js │ ├── global-styles.js │ ├── i18n.js │ ├── index.html │ ├── reducers.js │ ├── routes.js │ ├── store.js │ ├── tests │ │ └── store.test.js │ ├── translations │ │ └── en.json │ └── utils │ │ ├── asyncInjectors.js │ │ └── tests │ │ └── asyncInjectors.test.js ├── testing │ └── test-bundler.js └── webpack │ ├── webpack.base.babel.js │ ├── webpack.dev.babel.js │ ├── webpack.dll.babel.js │ └── webpack.prod.babel.js ├── package.json ├── scripts ├── deploy_production.sh └── deploy_staging.sh ├── server ├── index.js ├── logger.js └── middlewares │ └── frontendMiddleware.js ├── shippable.yml └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | build 4 | node_modules 5 | ab-web-frontend.zip 6 | stats.json 7 | 8 | # Cruft 9 | .DS_Store 10 | npm-debug.log 11 | .idea 12 | -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register'; 2 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | function loadStories() { 4 | require('../app/components/TableMenu/stories'); 5 | require('../app/components/ActionBar/tests/stories'); 6 | require('../app/components/Slider/tests/stories'); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | // you can use this file to add your custom webpack plugins, loaders and anything you like. 2 | // This is just the basic way to add additional webpack configurations. 3 | // For more information refer the docs: https://storybook.js.org/docs/react-storybook/configurations/custom-webpack-config 4 | 5 | // IMPORTANT 6 | // When you add this file, we won't add the default configurations which is similar 7 | // to "React Create App". This only has babel loader to load JavaScript. 8 | // module.exports = { 9 | // plugins: [ 10 | // // your custom plugins 11 | // ], 12 | // module: { 13 | // loaders: [ 14 | // // add your custom loaders. 15 | // ], 16 | // }, 17 | // }; 18 | 19 | const path = require('path'); 20 | 21 | // Export a function. Accept the base config as the only param. 22 | module.exports = (storybookBaseConfig, configType) => { 23 | // configType has a value of 'DEVELOPMENT' or 'PRODUCTION' 24 | // You can change the configuration based on that. 25 | // 'PRODUCTION' is used when building the static version of storybook. 26 | 27 | // Make whatever fine-grained changes you need 28 | storybookBaseConfig.module.rules.push({ 29 | test: /\.css$/, 30 | loaders: ['style-loader', 'css-loader'], 31 | include: path.resolve(__dirname, '../'), 32 | }); 33 | 34 | // Return the altered config 35 | return storybookBaseConfig; 36 | }; 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Acebusters Web App 2 | 3 | Web app to play a fair and secure game of poker. 4 | 5 | ## Quick Start 6 | 7 | 1. Clone this repo using `git@github.com:parsec-labs/acebusters-frontend.git` 8 | 2. Run `npm start` to see the app at `http://localhost:3000`. 9 | 10 | Now you're ready to rumble! 11 | 12 | ## How to Contribute 13 | 14 | Read [Fork & Pull Workflow](https://github.com/acebusters/ab-web/wiki/Standard-Fork-&-Pull-Request-Workflow). 15 | 16 | ## Developer Onboarding 17 | 18 | Read this [Development Onboarding](https://github.com/acebusters/ab-web/wiki/Developer-Onboarding) Page. 19 | -------------------------------------------------------------------------------- /app/components/A/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A link to a certain page, an anchor tag 3 | */ 4 | 5 | import styled from 'styled-components'; 6 | 7 | const A = styled.a` 8 | color: #41addd; 9 | cursor: pointer; 10 | &:hover { 11 | color: #6cc0e5; 12 | } 13 | `; 14 | 15 | export default A; 16 | -------------------------------------------------------------------------------- /app/components/ActionBar/ControlBlank.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { ActionButtonWrapper } from './styles'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /app/components/ActionBar/ControlCheckCall.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import ActionButton from './ActionButton'; 5 | import ControlBlank from './ControlBlank'; 6 | 7 | import { CALL, CHECK } from '../../containers/ActionBar/actions'; 8 | 9 | const ControlCheckCall = (props) => { 10 | const { 11 | amountToCall, 12 | myStack, 13 | } = props; 14 | if (amountToCall > myStack) { 15 | return ; 16 | } 17 | if (amountToCall > 0) { 18 | return ( 19 | 25 | ); 26 | } 27 | if (amountToCall === 0) { 28 | return ( 29 | 35 | ); 36 | } 37 | return ; 38 | }; 39 | ControlCheckCall.propTypes = { 40 | amountToCall: PropTypes.number, 41 | myStack: PropTypes.number, 42 | }; 43 | 44 | export default ControlCheckCall; 45 | -------------------------------------------------------------------------------- /app/components/ActionBar/ControlFold.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import ActionButton from './ActionButton'; 5 | import ControlBlank from './ControlBlank'; 6 | import { FOLD } from '../../containers/ActionBar/actions'; 7 | 8 | const ControlFold = (props) => { 9 | const { 10 | amountToCall, 11 | } = props; 12 | if (amountToCall > 0) { 13 | return ( 14 | 20 | ); 21 | } 22 | return ; 23 | }; 24 | ControlFold.propTypes = { 25 | amountToCall: PropTypes.number, 26 | }; 27 | 28 | export default ControlFold; 29 | -------------------------------------------------------------------------------- /app/components/ActionBar/FlagAmountCall.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { FlagCall } from './styles'; 5 | import { formatNtz } from '../../utils/amountFormatter'; 6 | 7 | const FlagAmountCall = ({ 8 | active, 9 | amountToCall, 10 | sliderOpen, 11 | myStack, 12 | }) => { 13 | // hide flag if only option is to 'check' or 'all-in' 14 | const hide = amountToCall === 0 || amountToCall > myStack || !active; 15 | return ( 16 | 17 | {formatNtz(amountToCall)} 18 | 19 | ); 20 | }; 21 | FlagAmountCall.propTypes = { 22 | active: PropTypes.bool, 23 | amountToCall: PropTypes.number, 24 | myStack: PropTypes.number, 25 | sliderOpen: PropTypes.bool, 26 | }; 27 | 28 | export default FlagAmountCall; 29 | -------------------------------------------------------------------------------- /app/components/ActionBar/FlagButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { FlagButtonWrapper } from './styles'; 5 | 6 | const FlagButton = ({ 7 | value, 8 | label, 9 | minRaise, 10 | sliderOpen, 11 | updateAmount, 12 | }) => { 13 | const isDisabled = value <= minRaise; 14 | return ( 15 | updateAmount(value)} 17 | sliderOpen={sliderOpen} 18 | name="flag-button" 19 | disabled={isDisabled} 20 | > 21 | {label} 22 | 23 | ); 24 | }; 25 | FlagButton.propTypes = { 26 | value: PropTypes.number, 27 | label: PropTypes.string, 28 | minRaise: PropTypes.number, 29 | sliderOpen: PropTypes.bool, 30 | updateAmount: PropTypes.func, 31 | }; 32 | 33 | export default FlagButton; 34 | -------------------------------------------------------------------------------- /app/components/Alert/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | import { gray } from '../../variables'; 4 | 5 | const borderColors = { 6 | info: '#eee', 7 | danger: '#dcb1b1', 8 | warning: '#ccbb99', 9 | success: '#d4e7c4', 10 | none: '#999', 11 | }; 12 | 13 | const bgColors = { 14 | info: '#f4f7f9', 15 | danger: '#ffcaca', 16 | warning: '#ffee99', 17 | success: '#e7efe4', 18 | none: 'none', 19 | }; 20 | 21 | const textColors = { 22 | info: gray, 23 | danger: '#634a49', 24 | warning: '#634a33', 25 | success: '#3a6536', 26 | none: 'none', 27 | }; 28 | 29 | const Alert = styled.div` 30 | padding: 5px 10px; 31 | margin: 10px 0; 32 | border: 1px solid ${(props) => borderColors[props.theme]}; 33 | border-radius: 4px; 34 | color: ${(props) => textColors[props.theme]}; 35 | background-color: ${(props) => bgColors[props.theme]}; 36 | `; 37 | 38 | export default Alert; 39 | 40 | Alert.propTypes = { 41 | theme: PropTypes.oneOf(['info', 'success', 'warning', 'danger', 'none']), 42 | }; 43 | 44 | Alert.defaultProps = { 45 | theme: 'success', 46 | }; 47 | -------------------------------------------------------------------------------- /app/components/AmountField/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Field } from 'redux-form/immutable'; 4 | import BigNumber from 'bignumber.js'; 5 | 6 | const AmountField = ({ 7 | maxAmount, 8 | minAmount = 0, 9 | ...props 10 | }) => { 11 | const limitAmount = (value) => BigNumber.min( 12 | BigNumber.max(minAmount, value || 0), 13 | maxAmount, 14 | ).toNumber(); 15 | 16 | return ( 17 | 21 | ); 22 | }; 23 | 24 | AmountField.propTypes = { 25 | maxAmount: PropTypes.object, 26 | minAmount: PropTypes.oneOfType([PropTypes.number, PropTypes.object]), 27 | }; 28 | 29 | export default AmountField; 30 | -------------------------------------------------------------------------------- /app/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Content from 'components/Content'; 5 | import GoogleTagManager from 'containers/GTM'; 6 | import Modal from 'containers/Modal'; 7 | import { conf } from '../../app.config'; 8 | 9 | const App = (props) => ( 10 |
11 | 12 | 13 | 14 | {React.Children.toArray(props.children)} 15 | 16 | 17 | 18 |
19 | ); 20 | App.defaultProps = { 21 | fixed: false, 22 | initialCollapse: true, 23 | }; 24 | App.propTypes = { 25 | children: PropTypes.node, 26 | fixed: PropTypes.bool, 27 | }; 28 | 29 | export default App; 30 | -------------------------------------------------------------------------------- /app/components/Blocky/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import styled from 'styled-components'; 3 | 4 | const Blocky = styled.div` 5 | width: 64px; 6 | height: 64px; 7 | background-size: cover; 8 | background-repeat: no-repeat; 9 | border-radius: 50%; 10 | background-image: url(${(props) => props.blocky}); 11 | box-shadow: inset rgba(255, 255, 255, 0.6) 0 2px 2px, inset rgba(0, 0, 0, 0.3) 0 -2px 6px; 12 | `; 13 | 14 | Blocky.propTypes = { 15 | blocky: PropTypes.string, 16 | }; 17 | 18 | export default Blocky; 19 | -------------------------------------------------------------------------------- /app/components/Button/tests/index.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Testing our Button component 3 | */ 4 | 5 | import React from 'react'; 6 | import { mount } from 'enzyme'; 7 | 8 | import Button from '../index'; 9 | 10 | const handleRoute = () => {}; 11 | const children = (

Test

); 12 | const renderComponent = (props = {}) => mount( 13 | 16 | ); 17 | 18 | describe(' 36 | 37 | ); 38 | } 39 | 40 | SubmitButton.propTypes = { 41 | disabled: bool, 42 | submitting: bool, 43 | children: any, 44 | }; 45 | 46 | export default SubmitButton; 47 | -------------------------------------------------------------------------------- /app/components/Table/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Table from 'components/Table'; 4 | import { Winner } from '../styles'; 5 | 6 | // secretSeed: 'rural tent tests net drip fatigue uncle action repeat couple lawn rival' 7 | const P1_ADDR = '0x6d2f2c0fa568243d2def3e999a791a6df45d816e'; 8 | const P2_ADDR = '0xA7Ba18a03393E8Aa141c69B3619b35676bd5a72d'; 9 | 10 | describe('ActionBar', () => { 11 | it('should render winners', () => { 12 | const props = { 13 | state: 'showdown', 14 | isTaken: () => {}, 15 | board: [], // showdown should require to show all cards? 16 | params: { tableAddr: '0x123' }, 17 | seats: [ 18 | { address: P1_ADDR, cards: [1, 2] }, 19 | { address: P2_ADDR, cards: [-1, -1] }, 20 | ], 21 | lineup: [{ 22 | address: P1_ADDR, 23 | cards: [1, 2], 24 | }], 25 | me: {}, 26 | sb: 1, 27 | winners: [ 28 | { 29 | addr: '0x00', 30 | amount: 0, 31 | maxBet: 0, 32 | }, 33 | ], 34 | }; 35 | const table = shallow(); 36 | expect(table.find(Winner).length).toBeGreaterThan(0); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /app/components/TableMenu/MenuHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { 5 | Hamburger, 6 | Identicon, 7 | ItemTitle, 8 | MenuHeader as HeaderStyle, 9 | Patty, 10 | } from './styles'; 11 | 12 | const MenuHeader = ({ 13 | active, 14 | blocky, 15 | toggleMenuOpen, 16 | toggleMenuActive, 17 | nickName, 18 | open, 19 | }) => { 20 | const handleOnLeave = () => active ? toggleMenuActive() : null; 21 | return ( 22 | 29 | 30 | {nickName} 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | MenuHeader.propTypes = { 41 | active: PropTypes.bool, 42 | blocky: PropTypes.string, 43 | nickName: PropTypes.string, 44 | toggleMenuActive: PropTypes.func, 45 | toggleMenuOpen: PropTypes.func, 46 | open: PropTypes.bool, 47 | }; 48 | 49 | export default MenuHeader; 50 | -------------------------------------------------------------------------------- /app/components/TableMenu/MenuItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Link from '../Link'; 4 | import WithLoading from '../WithLoading'; 5 | 6 | import { 7 | ItemWrapper, 8 | ItemIcon, 9 | ItemTitle, 10 | LinkWrapper, 11 | } from './styles'; 12 | 13 | const MenuItem = ({ item, ...props }) => { 14 | const Component = item.onClick ? ItemWrapper : Link; 15 | 16 | const handleClick = () => { 17 | // if the menu is open, close it 18 | if (props.open) { 19 | props.toggleMenuOpen(); 20 | } 21 | if (item.onClick) { 22 | item.onClick(); 23 | } 24 | }; 25 | 26 | return ( 27 | 34 | {item.pending && 35 | 36 | } 37 | {!item.pending && 38 | 39 | } 40 | 41 | {item.title} 42 | 43 | 44 | ); 45 | }; 46 | MenuItem.propTypes = { 47 | item: PropTypes.object, 48 | open: PropTypes.bool, 49 | toggleMenuOpen: PropTypes.func, 50 | }; 51 | 52 | export default MenuItem; 53 | -------------------------------------------------------------------------------- /app/components/TableMenu/stories/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import { withKnobs, text, boolean/* number */ } from '@storybook/addon-knobs'; 4 | 5 | import TableMenu from '../index'; 6 | 7 | import { blocky } from '../../../app.config'; 8 | 9 | const stories = storiesOf('TableMenu', module); 10 | 11 | stories.addDecorator(withKnobs); 12 | 13 | stories.add('guest menu', () => { 14 | const guestClose = { 15 | blocky: null, 16 | loggedIn: false, 17 | nickName: 'Guest', 18 | open: boolean('open', false), 19 | }; 20 | return ; 21 | }); 22 | 23 | stories.add('user menu', () => { 24 | const guestClose = { 25 | blocky, 26 | loggedIn: true, 27 | nickName: text('nickName', 'DAWN'), 28 | open: boolean('open', false), 29 | }; 30 | return ; 31 | }); 32 | -------------------------------------------------------------------------------- /app/components/Timed/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class Timed extends React.Component { 5 | 6 | static propTypes = { 7 | until: PropTypes.number.isRequired, 8 | children: PropTypes.any, 9 | }; 10 | 11 | constructor(props) { 12 | super(props); 13 | this.setupTimeout(props.until); 14 | } 15 | 16 | componentWillReceiveProps(nextProps) { 17 | if (nextProps.until !== this.props.until) { 18 | this.setupTimeout(nextProps.until); 19 | } 20 | } 21 | 22 | componentWillUnmount() { 23 | clearInterval(this.timeout); 24 | } 25 | 26 | setupTimeout(until) { 27 | if (this.timeout) { 28 | clearInterval(this.timeout); 29 | } 30 | 31 | const delta = Math.ceil((until * 1000) - Date.now()); 32 | 33 | if (delta > 0) { 34 | this.timeout = setTimeout(() => { 35 | this.forceUpdate(); 36 | }, delta); 37 | } 38 | } 39 | 40 | render() { 41 | const disabled = this.props.until >= (Date.now() / 1000); 42 | 43 | if (typeof this.props.children === 'function') { 44 | return this.props.children(disabled); 45 | } else if (disabled) { 46 | return null; 47 | } 48 | 49 | return this.props.children; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/components/Toggle/Select.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Select = styled.select` 4 | line-height: 1em; 5 | height: 20px; 6 | `; 7 | 8 | export default Select; 9 | -------------------------------------------------------------------------------- /app/components/Toggle/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * LocaleToggle 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | 10 | import Select from './Select'; 11 | import ToggleOption from '../ToggleOption'; 12 | 13 | function Toggle(props) { 14 | let content = (); 15 | 16 | // If we have items, render them 17 | if (props.values) { 18 | content = props.values.map((value) => ( 19 | 20 | )); 21 | } 22 | 23 | return ( 24 | 27 | ); 28 | } 29 | 30 | Toggle.propTypes = { 31 | onToggle: PropTypes.func, 32 | values: PropTypes.array, 33 | value: PropTypes.string, 34 | messages: PropTypes.object, 35 | }; 36 | 37 | export default Toggle; 38 | -------------------------------------------------------------------------------- /app/components/Toggle/tests/Select.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Select from '../Select'; 5 | 6 | describe(' tag', () => { 8 | 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(); 25 | expect(renderedComponent.prop('attribute')).toBeUndefined(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/components/Toggle/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { IntlProvider, defineMessages } from 'react-intl'; 4 | 5 | import Toggle from '../index'; 6 | 7 | describe('', () => { 8 | it('should contain default text', () => { 9 | const defaultEnMessage = 'someContent'; 10 | const defaultDeMessage = 'someOtherContent'; 11 | const messages = defineMessages({ 12 | en: { 13 | id: 'boilerplate.containers.LocaleToggle.en', 14 | defaultMessage: defaultEnMessage, 15 | }, 16 | de: { 17 | id: 'boilerplate.containers.LocaleToggle.en', 18 | defaultMessage: defaultDeMessage, 19 | }, 20 | }); 21 | const renderedComponent = shallow( 22 | 23 | 24 | 25 | ); 26 | expect(renderedComponent.contains()).toBe(true); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /app/components/ToggleOption/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * ToggleOption 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { injectIntl, intlShape } from 'react-intl'; 10 | 11 | const ToggleOption = ({ value, message, intl }) => ( 12 | 15 | ); 16 | 17 | ToggleOption.propTypes = { 18 | value: PropTypes.string.isRequired, 19 | message: PropTypes.object, 20 | intl: intlShape.isRequired, 21 | }; 22 | 23 | export default injectIntl(ToggleOption); 24 | -------------------------------------------------------------------------------- /app/components/ToggleOption/tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import { IntlProvider, defineMessages } from 'react-intl'; 4 | 5 | import ToggleOption from '../index'; 6 | 7 | describe('', () => { 8 | it('should render default language messages', () => { 9 | const defaultEnMessage = 'someContent'; 10 | const message = defineMessages({ 11 | enMessage: { 12 | id: 'boilerplate.containers.LocaleToggle.en', 13 | defaultMessage: defaultEnMessage, 14 | }, 15 | }); 16 | const renderedComponent = shallow( 17 | 18 | 19 | 20 | ); 21 | expect(renderedComponent.contains()).toBe(true); 22 | }); 23 | 24 | it('should display `value`(two letter language code) when `message` is absent', () => { 25 | const renderedComponent = mount( 26 | 27 | 28 | 29 | ); 30 | expect(renderedComponent.text()).toBe('de'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/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 { makeSelectWeb3ErrMsg } from './selectors'; 7 | import { web3Connect } from './actions'; 8 | 9 | export class AccountProvider extends React.PureComponent { 10 | componentDidMount() { 11 | this.props.web3Connect(); // initiate web3 12 | } 13 | 14 | render() { 15 | return ( 16 |
17 | {React.Children.only(this.props.children)} 18 |
19 | ); 20 | } 21 | } 22 | 23 | AccountProvider.propTypes = { 24 | children: PropTypes.oneOfType([ 25 | PropTypes.arrayOf(PropTypes.node), 26 | PropTypes.node, 27 | ]), 28 | web3Connect: PropTypes.func, 29 | }; 30 | 31 | function mapDispatchToProps(dispatch) { 32 | return { 33 | web3Connect: () => dispatch(web3Connect()), 34 | }; 35 | } 36 | 37 | const mapStateToProps = createStructuredSelector({ 38 | web3ErrMsg: makeSelectWeb3ErrMsg(), 39 | }); 40 | 41 | export default connect(mapStateToProps, mapDispatchToProps)(AccountProvider); 42 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/sagas/ethEventListenerSaga.js: -------------------------------------------------------------------------------- 1 | import { select, put, take, call } from 'redux-saga/effects'; 2 | import { eventChannel, END } from 'redux-saga'; 3 | import { addEventsDate, isUserEvent } from '../utils'; 4 | import { contractEvents } from '../actions'; 5 | 6 | export const ethEvent = (contract) => eventChannel((emitter) => { 7 | const events = contract.allEvents({ fromBlock: 'latest' }); 8 | events.watch((error, results) => { 9 | if (error) { 10 | emitter(END); 11 | events.stopWatching(() => null); 12 | return; 13 | } 14 | emitter(results); 15 | }); 16 | return () => { 17 | events.stopWatching(() => null); 18 | }; 19 | }); 20 | 21 | export function* ethEventListenerSaga(contract) { 22 | const chan = yield call(ethEvent, contract); 23 | while (true) { // eslint-disable-line no-constant-condition 24 | try { 25 | const event = yield take(chan); 26 | const state = yield select(); 27 | const signerAddr = state.getIn(['account', 'signerAddr']); 28 | if (isUserEvent(signerAddr)(event)) { 29 | const events = yield call(addEventsDate, [event]); 30 | yield put(contractEvents(events)); 31 | } 32 | } catch (e) {} // eslint-disable-line no-empty 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/sagas/injectedWeb3ListenerSaga.js: -------------------------------------------------------------------------------- 1 | import { select, put, call } from 'redux-saga/effects'; 2 | import { delay } from 'redux-saga'; 3 | import { getWeb3 } from '../../AccountProvider/utils'; 4 | import { promisifyWeb3Call } from '../../../utils/promisifyWeb3Call'; 5 | 6 | import { updateInjectedAccount } from '../actions'; 7 | import { makeSelectInjected } from '../selectors'; 8 | 9 | export function* injectedWeb3ListenerSaga() { 10 | while (true) { // eslint-disable-line no-constant-condition 11 | if (window.web3) { 12 | const prevInjected = yield select(makeSelectInjected()); 13 | try { 14 | const [injected] = yield call(promisifyWeb3Call(getWeb3(true).eth.getAccounts)); 15 | if (prevInjected !== injected) { 16 | yield put(updateInjectedAccount(injected)); 17 | } 18 | } catch (e) {} // eslint-disable-line no-empty 19 | } 20 | 21 | yield call(delay, 2000); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/sagas/unsupportedNetworkDetectSaga.js: -------------------------------------------------------------------------------- 1 | import { put, call } from 'redux-saga/effects'; 2 | import { delay } from 'redux-saga'; 3 | import { conf } from '../../../app.config'; 4 | import { getWeb3 } from '../utils'; 5 | import { NETWORK_SUPPORT_UPDATE } from '../actions'; 6 | 7 | function getFirstBlockHash(web3) { 8 | return new Promise((resolve, reject) => { 9 | web3.eth.getBlock(0, (err, block) => { 10 | if (err) { 11 | reject(err); 12 | } else { 13 | resolve(block.hash); 14 | } 15 | }); 16 | }); 17 | } 18 | 19 | export function* unsupportedNetworkDetectSaga() { 20 | while (true) { // eslint-disable-line no-constant-condition 21 | const web3 = getWeb3(true); 22 | if (web3) { 23 | try { 24 | const hash = yield call(getFirstBlockHash, web3); 25 | yield put({ 26 | type: NETWORK_SUPPORT_UPDATE, 27 | payload: hash === conf().firstBlockHash, 28 | }); 29 | break; 30 | } catch (err) {} // eslint-disable-line no-empty 31 | } else { 32 | yield call(delay, 2000); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/sagas/web3CallsSagas.js: -------------------------------------------------------------------------------- 1 | import { put, call } from 'redux-saga/effects'; 2 | import { web3MethodSuccess, web3MethodError, contractMethodSuccess, contractMethodError } from '../actions'; 3 | 4 | // TODO handle errors 5 | function callMethod({ method, args }) { 6 | return new Promise((resolve, reject) => { 7 | try { 8 | method(...args, (err, value) => { 9 | if (err) { 10 | return reject(err); 11 | } 12 | return resolve(value); 13 | }); 14 | } catch (err) { 15 | reject(err); 16 | } 17 | }); 18 | } 19 | 20 | export function* web3MethodCallSaga({ payload: { method, args, key } }) { 21 | try { 22 | const value = yield call(callMethod, { method, args }); 23 | yield put(web3MethodSuccess({ key, payload: { value, updated: new Date() } })); 24 | } catch (err) { 25 | yield put(web3MethodError({ key, err })); 26 | } 27 | } 28 | 29 | export function* contractMethodCallSaga({ payload: { method, args, key, address } }) { 30 | try { 31 | const value = yield call(callMethod, { method, args }); 32 | yield put(contractMethodSuccess({ address, key, payload: { value, updated: new Date() } })); 33 | } catch (err) { 34 | yield put(contractMethodError({ address, key, err })); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/sagas/web3ConnectSaga.js: -------------------------------------------------------------------------------- 1 | import { put, fork, call } from 'redux-saga/effects'; 2 | import { delay } from 'redux-saga'; 3 | 4 | import { conf, ABI_TOKEN_CONTRACT } from '../../../app.config'; 5 | 6 | import { getWeb3 } from '../utils'; 7 | 8 | import { web3Connected, web3Disconnected } from '../actions'; 9 | import { ethEventListenerSaga } from './ethEventListenerSaga'; 10 | 11 | const getPeerCount = (web3) => ( 12 | new Promise((resolve, reject) => { 13 | web3.net.getPeerCount((error, result) => { 14 | if (error) { 15 | reject(error); 16 | } else { 17 | resolve(result); 18 | } 19 | }); 20 | }) 21 | ); 22 | 23 | export function* web3ConnectSaga() { 24 | try { 25 | yield getPeerCount(getWeb3()); 26 | yield put(web3Connected({ isConnected: true })); 27 | const tokenContract = getWeb3().eth.contract(ABI_TOKEN_CONTRACT).at(conf().ntzAddr); 28 | yield call(delay, 500); 29 | yield fork(ethEventListenerSaga, tokenContract); 30 | } catch (err) { 31 | yield put(web3Disconnected({ isConnected: false, error: err })); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/containers/AccountProvider/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import { selectAccount } from '../selectors'; 3 | 4 | describe('selectAccount', () => { 5 | it('should select account', () => { 6 | const globalState = fromJS({ loggedIn: false }); 7 | const mockedState = fromJS({ 8 | account: globalState, 9 | }); 10 | expect(selectAccount(mockedState)).toEqual(globalState); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /app/containers/ActionBar/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jzobro on 20170606 3 | */ 4 | // actions 5 | export const ACTIONBAR_SET_TURN_COMPLETE = 'acebusters/ActionBar/SET_TURN_COMPLETE'; 6 | export const ACTIONBAR_SET_MODE = 'acebusters/ActionBar/SET_MODE'; 7 | export const ACTIONBAR_SET_BUTTON_ACTIVE = 'acebusters/ActionBar/SET_BUTTON_ACTIVE'; 8 | export const HANDLE_CLICK_BUTTON = 'acebusters/ActionBar/HANDLE_CLICK_BUTTON'; 9 | export const UPDATE = 'acebusters/ActionBar/UPDATE'; 10 | 11 | // constants 12 | export const FOLD = 'fold'; 13 | export const CHECK = 'check'; 14 | export const CALL = 'call'; 15 | export const BET = 'bet'; 16 | export const ALL_IN = 'all-in'; 17 | // bet related 18 | export const BET_SET = 'bet-set'; 19 | 20 | // actionBar 21 | export function setActionBarTurnComplete(complete) { 22 | return { 23 | type: ACTIONBAR_SET_TURN_COMPLETE, 24 | complete, 25 | }; 26 | } 27 | 28 | export function setActionBarButtonActive(whichBtn) { 29 | return { 30 | type: ACTIONBAR_SET_BUTTON_ACTIVE, 31 | whichBtn, 32 | }; 33 | } 34 | 35 | export const handleClickButton = (type) => ({ 36 | type: HANDLE_CLICK_BUTTON, 37 | buttonType: type, 38 | }); 39 | 40 | export const updateActionBar = (payload) => ({ 41 | type: UPDATE, 42 | payload, 43 | }); 44 | -------------------------------------------------------------------------------- /app/containers/ActionBar/reducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jzobro on 20170608 3 | */ 4 | import { fromJS } from 'immutable'; 5 | import * as types from './actions'; 6 | 7 | export const initialState = fromJS({ 8 | buttonActive: '', // used for activeIndicator 9 | sliderOpen: false, // toggles slider open/closed 10 | turnComplete: false, 11 | mode: '', // tracks active button's life-cycle 12 | executeAction: false, 13 | }); 14 | 15 | export default function actionBarReducer(state = initialState, action) { 16 | switch (action.type) { 17 | 18 | case types.ACTIONBAR_SET_TURN_COMPLETE: { 19 | return state.set('turnComplete', action.complete); 20 | } 21 | 22 | case types.ACTIONBAR_SET_BUTTON_ACTIVE: { 23 | return state.set('buttonActive', action.whichBtn); 24 | } 25 | 26 | case types.UPDATE: { 27 | return state.merge(action.payload); 28 | } 29 | 30 | default: { 31 | return state; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/containers/ActionBar/sagas.js: -------------------------------------------------------------------------------- 1 | import { call, put, takeLatest, select } from 'redux-saga/effects'; 2 | import { delay } from 'redux-saga'; 3 | import { HANDLE_CLICK_BUTTON, BET_SET, updateActionBar } from './actions'; 4 | import { playActionBarClick } from '../../sounds'; 5 | import { makeSelectIsMuted } from '../TableMenu/selectors'; 6 | 7 | function* handleClickButton({ buttonType }) { 8 | const isMuted = yield select(makeSelectIsMuted()); 9 | if (!isMuted) { 10 | yield call(playActionBarClick); 11 | } 12 | yield delay(200); 13 | const update = {}; 14 | // tracks onMouseDown 15 | update.buttonActive = buttonType; 16 | // tracks bet/raise process (BET_SET > BET) 17 | update.mode = buttonType; 18 | 19 | // have container call action method for specific buttonTypes 20 | if (buttonType === BET_SET) { 21 | update.sliderOpen = true; 22 | } else { 23 | update.executeAction = true; 24 | update.sliderOpen = false; 25 | } 26 | yield put(updateActionBar(update)); 27 | } 28 | 29 | export function* actionBarSaga() { 30 | yield takeLatest(HANDLE_CLICK_BUTTON, handleClickButton); 31 | } 32 | 33 | export default [ 34 | actionBarSaga, 35 | ]; 36 | -------------------------------------------------------------------------------- /app/containers/App/actions.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_LOCALE = 'en'; 2 | export const MODAL_ADD = 'acebusters/App/MODAL_ADD'; 3 | export const MODAL_DISMISS = 'acebusters/App/MODAL_DISMISS'; 4 | export const GLOBAL_PROGRESS = 'acebusters/App/GLOBAL_PROGRESS'; 5 | 6 | export const modalAdd = ({ modalType, modalProps, closeHandler, backdrop } = {}) => ({ 7 | type: MODAL_ADD, 8 | payload: { modalType, modalProps, closeHandler, backdrop }, 9 | }); 10 | 11 | export function modalDismiss() { 12 | return { type: MODAL_DISMISS }; 13 | } 14 | 15 | export function setProgress(progress) { 16 | return { type: GLOBAL_PROGRESS, data: progress }; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { createStructuredSelector } from 'reselect'; 3 | 4 | import App from 'components/App'; 5 | import withProgressBar from 'components/ProgressBar'; 6 | 7 | import { makeSelectLoggedIn } from '../AccountProvider/selectors'; 8 | 9 | import { 10 | makeSelectProgress, 11 | makeSelectTransferShow, 12 | selectWorkerProgress, 13 | } from './selectors'; 14 | 15 | const mapStateToProps = createStructuredSelector({ 16 | workerProgress: selectWorkerProgress, 17 | isModalOpen: makeSelectTransferShow(), 18 | progress: makeSelectProgress(), 19 | loggedIn: makeSelectLoggedIn(), 20 | }); 21 | 22 | export default connect(mapStateToProps)(withProgressBar(App)); 23 | -------------------------------------------------------------------------------- /app/containers/App/reducer.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | MODAL_ADD, 5 | MODAL_DISMISS, 6 | GLOBAL_PROGRESS, 7 | } from './actions'; 8 | 9 | // The initial state of the App 10 | const initialState = fromJS({ 11 | modalStack: [], 12 | wallet: null, 13 | progress: 0, 14 | }); 15 | 16 | function appReducer(state = initialState, action) { 17 | switch (action.type) { 18 | case MODAL_ADD: 19 | return state 20 | .set('modalStack', state.get('modalStack').push({ 21 | modalType: action.payload.modalType, 22 | modalProps: { 23 | ...action.payload.modalProps, 24 | closeHandler: action.payload.closeHandler, 25 | backdrop: action.payload.backdrop, 26 | }, 27 | })); 28 | 29 | case MODAL_DISMISS: 30 | return state 31 | .set('modalStack', state.get('modalStack').pop()); 32 | 33 | case GLOBAL_PROGRESS: 34 | return state.set('progress', action.data); 35 | 36 | default: 37 | return state; 38 | } 39 | } 40 | 41 | export default appReducer; 42 | -------------------------------------------------------------------------------- /app/containers/App/sagas/gtmSagas.js: -------------------------------------------------------------------------------- 1 | import { takeEvery } from 'redux-saga/effects'; 2 | import { LOCATION_CHANGE } from 'react-router-redux'; 3 | 4 | export function* updateGTMOnLocationChange(action) { 5 | window.dataLayer = window.dataLayer || []; 6 | 7 | window.dataLayer.push({ 8 | event: 'LOCATION_CHANGE', 9 | pathname: action.payload.pathname, 10 | }); 11 | } 12 | 13 | export default function* gtmSaga() { 14 | yield takeEvery(LOCATION_CHANGE, updateGTMOnLocationChange); 15 | } 16 | -------------------------------------------------------------------------------- /app/containers/App/sagas/index.js: -------------------------------------------------------------------------------- 1 | import { fork } from 'redux-saga/effects'; 2 | 3 | import gtmSaga from './gtmSagas'; 4 | import balancesLoadingSaga from './balancesLoadingSaga'; 5 | 6 | export function* appSaga(dispatch) { 7 | yield fork(gtmSaga); 8 | yield fork(balancesLoadingSaga, dispatch); 9 | } 10 | 11 | export default [appSaga]; 12 | -------------------------------------------------------------------------------- /app/containers/App/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import appReducer from '../reducer'; 4 | 5 | describe('appReducer', () => { 6 | let state; 7 | beforeEach(() => { 8 | state = fromJS({ 9 | modalStack: [], 10 | wallet: null, 11 | progress: 0, 12 | }); 13 | }); 14 | 15 | it('should return the initial state', () => { 16 | const expectedResult = state; 17 | expect(appReducer(undefined, {})).toEqual(expectedResult); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /app/containers/App/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | selectGlobal, 5 | makeSelectLocationState, 6 | } from '../selectors'; 7 | 8 | describe('selectGlobal', () => { 9 | it('should select the global state', () => { 10 | const globalState = fromJS({}); 11 | const mockedState = fromJS({ 12 | global: globalState, 13 | }); 14 | expect(selectGlobal(mockedState)).toEqual(globalState); 15 | }); 16 | }); 17 | 18 | describe('makeSelectLocationState', () => { 19 | const locationStateSelector = makeSelectLocationState(); 20 | it('should select the route as a plain JS object', () => { 21 | const route = fromJS({ 22 | locationBeforeTransitions: null, 23 | }); 24 | const mockedState = fromJS({ 25 | route, 26 | }); 27 | expect(locationStateSelector(mockedState)).toEqual(route.toJS()); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /app/containers/Chat/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { 5 | ChatPlaceholder, 6 | ChatContainer, 7 | ChatArea, 8 | ChatBox, 9 | } from '../../components/Chat'; 10 | import MessageList from './message-list'; 11 | import MessageBox from './message-box'; 12 | 13 | export class Chat extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 14 | constructor(props) { 15 | super(props); 16 | this.onAddMessage = this.onAddMessage.bind(this); 17 | } 18 | onAddMessage(message) { 19 | this.props.onAddMessage(message); 20 | } 21 | render() { 22 | const { readonly, messages, placeholder } = this.props; 23 | 24 | return ( 25 | 26 | 27 | {(messages && messages.length > 0) && 28 | } 29 | 30 | {!(messages && messages.length > 0) && 31 | {placeholder}} 32 | 33 | 34 | {!readonly && 35 | 36 | } 37 | 38 | 39 | ); 40 | } 41 | } 42 | 43 | Chat.propTypes = { 44 | messages: PropTypes.array, 45 | onAddMessage: PropTypes.func, 46 | readonly: PropTypes.bool, 47 | placeholder: PropTypes.any, 48 | }; 49 | 50 | export default Chat; 51 | -------------------------------------------------------------------------------- /app/containers/Chat/message-box.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class MessageBox extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 5 | 6 | constructor(props) { 7 | super(props); 8 | this.handleSend = this.handleSend.bind(this); 9 | } 10 | 11 | handleSend(event) { 12 | if (event.which === 13) { 13 | const text = this.input.value.trim(); 14 | event.preventDefault(); 15 | if (text.length > 0) { 16 | this.props.onAddMessage(text); 17 | this.input.value = ''; 18 | } 19 | } 20 | } 21 | 22 | render() { 23 | return ( 24 |
25 | { this.input = input; }} 29 | style={{ 30 | width: '100%', 31 | background: 'whitesmoke', 32 | boxShadow: 'inset black 0 0 3px 0px', 33 | padding: '10px', 34 | fontSize: 'large', 35 | }} 36 | /> 37 |
38 | ); 39 | } 40 | } 41 | 42 | MessageBox.propTypes = { 43 | onAddMessage: PropTypes.func, 44 | }; 45 | 46 | export default MessageBox; 47 | -------------------------------------------------------------------------------- /app/containers/Curtain/messages.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | placeholder: { 5 | id: 'app.containers.Curtain.placeholder', 6 | defaultMessage: `«{tableName}», hand {handId} in state {state} has {playerCount, plural, 7 | =0 {no players} 8 | one {# player} 9 | other {# players} 10 | }.`, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /app/containers/Dashboard/Advanced.js: -------------------------------------------------------------------------------- 1 | import web3Connect from 'containers/AccountProvider/web3Connect'; 2 | import { IMPORT_DIALOG, EXPORT_DIALOG } from 'containers/Modal/constants'; 3 | import { modalAdd } from 'containers/App/actions'; 4 | 5 | import Advanced from 'components/Dashboard/Advanced'; 6 | 7 | const mapDispatchToProps = () => ({ 8 | onImport: () => modalAdd({ modalType: IMPORT_DIALOG }), 9 | onExport: () => modalAdd({ modalType: EXPORT_DIALOG }), 10 | }); 11 | 12 | export default web3Connect( 13 | () => {}, 14 | mapDispatchToProps, 15 | )(Advanced); 16 | -------------------------------------------------------------------------------- /app/containers/Dashboard/actions.js: -------------------------------------------------------------------------------- 1 | // actions 2 | export const SET_ACTIVE_TAB = 'acebusters/Dashboard/SET_ACTIVE_TAB'; 3 | 4 | export const setActiveTab = (whichTab) => ({ 5 | type: SET_ACTIVE_TAB, 6 | whichTab, 7 | }); 8 | -------------------------------------------------------------------------------- /app/containers/Dashboard/constants.js: -------------------------------------------------------------------------------- 1 | // constants 2 | export const ADVANCED = 'advanced'; 3 | export const OVERVIEW = 'overview'; 4 | -------------------------------------------------------------------------------- /app/containers/Dashboard/sagas/errorModalSaga.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import { take, put } from 'redux-saga/effects'; 4 | 5 | import { CONFIRM_DIALOG } from '../../Modal/constants'; 6 | import { CONTRACT_TX_ERROR } from '../../AccountProvider/actions'; 7 | import { modalAdd, modalDismiss } from '../../App/actions'; 8 | import messages from '../messages'; 9 | 10 | export function* errorModalSaga(dispatch) { 11 | while (true) { // eslint-disable-line 12 | const { payload } = yield take(CONTRACT_TX_ERROR); 13 | 14 | yield put(modalAdd({ 15 | modalType: CONFIRM_DIALOG, 16 | modalProps: { 17 | title: , 18 | msg: formatTxErrorMessage(payload.error), 19 | onSubmit: () => dispatch(modalDismiss()), 20 | buttonText: , 21 | }, 22 | })); 23 | } 24 | } 25 | 26 | function formatTxErrorMessage(error) { 27 | if (typeof error === 'string' && error.indexOf('MetaMask Tx Signature') > -1) { 28 | return 'Transaction denied'; 29 | } 30 | 31 | return error; 32 | } 33 | -------------------------------------------------------------------------------- /app/containers/Dashboard/sagas/index.js: -------------------------------------------------------------------------------- 1 | import { eventsSaga } from './eventsSaga'; 2 | import { errorModalSaga } from './errorModalSaga'; 3 | 4 | export default [eventsSaga, errorModalSaga]; 5 | -------------------------------------------------------------------------------- /app/containers/Dashboard/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | function selectDashboard(state) { 4 | return state.get('dashboard'); 5 | } 6 | 7 | export const createDashboardTxsSelector = () => createSelector( 8 | selectDashboard, 9 | (dashboard) => dashboard.get('events') && dashboard.get('events').toList().toJS(), 10 | ); 11 | 12 | export const createPendingsSelector = () => createSelector( 13 | selectDashboard, 14 | (dashboard) => dashboard.get('events') && dashboard.get('events').filter((item) => item.get('pending')).toMap(), 15 | ); 16 | 17 | export const getActiveTab = () => createSelector( 18 | selectDashboard, 19 | (dashboard) => dashboard.get('activeTab'), 20 | ); 21 | -------------------------------------------------------------------------------- /app/containers/Dashboard/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const typeIcons = { 4 | income: '▲', 5 | outcome: '▼', 6 | }; 7 | 8 | export const TypeIcon = styled.i` 9 | color: ${(props) => props.children === typeIcons.income ? '#43ba67' : '#da0a16'}; 10 | `; 11 | 12 | export const Error = styled.span` 13 | color: red; 14 | `; 15 | 16 | export const ErrorIcon = styled.i` 17 | color: red; 18 | `; 19 | 20 | export const Icon = styled.i``; 21 | -------------------------------------------------------------------------------- /app/containers/Dashboard/utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedDate, FormattedTime } from 'react-intl'; 3 | 4 | export function formatDate(timestamp) { 5 | if (!timestamp) { 6 | return ''; 7 | } 8 | 9 | const date = new Date(timestamp * 1000); 10 | 11 | return ( 12 | 13 | ,  19 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /app/containers/GTM/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class GoogleTagManager extends React.PureComponent { 5 | componentDidMount() { 6 | const { gtmId, dataLayerName, additionalEvents } = this.props; 7 | 8 | const gtmData = { 9 | gtmId, 10 | dataLayerName: dataLayerName || 'dataLayer', 11 | additionalEvents: additionalEvents || {}, 12 | }; 13 | 14 | this.gtmInclude(gtmData.dataLayerName, gtmData.gtmId); 15 | } 16 | 17 | gtmInclude(l, i) { 18 | window[l] = window[l] || []; 19 | window[l].push({ 20 | 'gtm.start': new Date().getTime(), 21 | event: 'gtm.js', 22 | }); 23 | const f = document.getElementsByTagName('script')[0]; 24 | const j = document.createElement('script'); 25 | const dl = l !== 'dataLayer' ? `&l=${l}` : ''; 26 | j.async = true; 27 | j.src = `https://www.googletagmanager.com/gtm.js?id=${i}${dl}`; 28 | f.parentNode.insertBefore(j, f); 29 | } 30 | 31 | render() { 32 | return false; 33 | } 34 | } 35 | 36 | GoogleTagManager.propTypes = { 37 | gtmId: PropTypes.string.isRequired, 38 | dataLayerName: PropTypes.string, 39 | additionalEvents: PropTypes.object, 40 | }; 41 | 42 | export default GoogleTagManager; 43 | -------------------------------------------------------------------------------- /app/containers/Header/actions.js: -------------------------------------------------------------------------------- 1 | export const SET_COLLAPSED = 'acebusters/Header/SET_COLLAPSED'; 2 | 3 | export function setCollapsed(collapsed) { 4 | return { 5 | type: SET_COLLAPSED, 6 | collapsed, 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /app/containers/Header/index.js: -------------------------------------------------------------------------------- 1 | import { createStructuredSelector } from 'reselect'; 2 | import { 3 | makeBlockySelector, 4 | makeNickNameSelector, 5 | makeSelectLoggedIn, 6 | makeSignerAddrSelector, 7 | } from '../AccountProvider/selectors'; 8 | import web3Connect from '../AccountProvider/web3Connect'; 9 | 10 | import Header from '../../components/Header'; 11 | 12 | const mapStateToProps = createStructuredSelector({ 13 | loggedIn: makeSelectLoggedIn(), 14 | nickName: makeNickNameSelector(), 15 | signerAddr: makeSignerAddrSelector(), 16 | blocky: makeBlockySelector(), 17 | }); 18 | 19 | export default web3Connect( 20 | mapStateToProps, 21 | )(Header); 22 | -------------------------------------------------------------------------------- /app/containers/Header/reducer.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import * as types from './actions'; 3 | 4 | export const initialState = fromJS({ 5 | collapsed: true, 6 | }); 7 | 8 | export default function headerReducer(state = initialState, action) { 9 | switch (action.type) { 10 | 11 | case types.SET_COLLAPSED: { 12 | return state.set('collapsed', action.collapsed); 13 | } 14 | 15 | default: { 16 | return state; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/containers/Header/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const getHeaderCollapsed = () => createSelector( 4 | (state) => state.get('header'), 5 | (header) => header.get('collapsed'), 6 | ); 7 | -------------------------------------------------------------------------------- /app/containers/Header/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import reducer from '../reducer'; 3 | import { 4 | setCollapsed, 5 | } from '../actions'; 6 | 7 | describe('header reducer', () => { 8 | describe('setCollapsed', () => { 9 | it('should set collapsed depending on value', () => { 10 | const before = fromJS({ 11 | collapsed: false, 12 | }); 13 | const nextState = reducer(before, setCollapsed(true)); 14 | expect(nextState.get('collapsed')).toEqual(true); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /app/containers/Header/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import { 3 | getHeaderCollapsed, 4 | } from '../selectors'; 5 | 6 | describe('Header Selectors', () => { 7 | it('should getHeaderCollapsed state', () => { 8 | const mockedState = fromJS({ 9 | header: { collapsed: true }, 10 | }); 11 | const selector = getHeaderCollapsed(); 12 | expect(selector(mockedState)).toEqual(true); 13 | expect(selector(mockedState)).not.toEqual(false); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/containers/InviteDialog/messages.js: -------------------------------------------------------------------------------- 1 | import { defineMessages } from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | header: { 5 | id: 'app.containers.InviteDialog.header', 6 | defaultMessage: 'Invite a friend:', 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /app/containers/JoinDialog/index.js: -------------------------------------------------------------------------------- 1 | import JoinDialog from 'components/JoinDialog'; 2 | import { reduxForm, formValueSelector, change as changeFieldValue } from 'redux-form/immutable'; 3 | 4 | import { createStructuredSelector } from 'reselect'; 5 | 6 | import { modalDismiss } from '../App/actions'; 7 | import { makeSbSelector, makeTableStakesSelector } from '../Table/selectors'; 8 | import { makeSignerAddrSelector } from '../AccountProvider/selectors'; 9 | import web3Connect from '../AccountProvider/web3Connect'; 10 | 11 | const valueSelector = formValueSelector('join'); 12 | 13 | const mapDispatchToProps = (dispatch) => ({ 14 | modalDismiss: () => dispatch(modalDismiss()), 15 | changeFieldValue: (form, field, value) => dispatch(changeFieldValue(form, field, value)), 16 | }); 17 | 18 | const mapStateToProps = createStructuredSelector({ 19 | signerAddr: makeSignerAddrSelector(), 20 | amount: (state) => valueSelector(state, 'amount'), 21 | initialValues: (state, props) => ({ 22 | amount: makeSbSelector()(state, props) * 40, 23 | }), 24 | tableStakes: makeTableStakesSelector(), 25 | }); 26 | 27 | export default web3Connect(mapStateToProps, mapDispatchToProps)( 28 | reduxForm({ form: 'join' })(JoinDialog) 29 | ); 30 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageProvider constants 4 | * 5 | */ 6 | 7 | export const CHANGE_LOCALE = 'app/LanguageToggle/CHANGE_LOCALE'; 8 | -------------------------------------------------------------------------------- /app/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 | const mapStateToProps = createSelector( 34 | makeSelectLocale(), 35 | (locale) => ({ locale }) 36 | ); 37 | 38 | export default connect(mapStateToProps)(LanguageProvider); 39 | -------------------------------------------------------------------------------- /app/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/actions'; 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 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/actions.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | changeLocale, 3 | } from '../actions'; 4 | 5 | import { 6 | CHANGE_LOCALE, 7 | } from '../constants'; 8 | 9 | describe('LanguageProvider actions', () => { 10 | describe('Change Local Action', () => { 11 | it('has a type of CHANGE_LOCALE', () => { 12 | const expected = { 13 | type: CHANGE_LOCALE, 14 | locale: 'de', 15 | }; 16 | expect(changeLocale('de')).toEqual(expected); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/reducer.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import languageProviderReducer from '../reducer'; 4 | import { 5 | CHANGE_LOCALE, 6 | } from '../constants'; 7 | 8 | describe('languageProviderReducer', () => { 9 | it('returns the initial state', () => { 10 | expect(languageProviderReducer(undefined, {})).toEqual(fromJS({ 11 | locale: 'en', 12 | })); 13 | }); 14 | 15 | it('changes the locale', () => { 16 | expect(languageProviderReducer(undefined, { type: CHANGE_LOCALE, locale: 'de' }).toJS()).toEqual({ 17 | locale: 'de', 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /app/containers/LanguageProvider/tests/selectors.test.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | import { 4 | selectLanguage, 5 | } from '../selectors'; 6 | 7 | describe('selectLanguage', () => { 8 | it('should select the global state', () => { 9 | const globalState = fromJS({}); 10 | const mockedState = fromJS({ 11 | language: globalState, 12 | }); 13 | expect(selectLanguage(mockedState)).toEqual(globalState); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/containers/Lobby/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const selectTables = (state) => state.get('table'); 4 | 5 | const makeSelectLobby = () => createSelector( 6 | selectTables, 7 | (tableState) => { 8 | const tableAddresses = []; 9 | tableState.keySeq().forEach((key) => { 10 | if (key.length > 20) { 11 | tableAddresses.push(key); 12 | } 13 | }); 14 | return tableAddresses; 15 | } 16 | ); 17 | 18 | export { 19 | makeSelectLobby, 20 | }; 21 | -------------------------------------------------------------------------------- /app/containers/LobbyItem/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | const tableSelector = (state, props) => (state && props) ? state.getIn(['table', props.tableAddr]) : null; 4 | 5 | const makeSelectTableData = () => createSelector( 6 | tableSelector, 7 | (table) => { 8 | if (!table || !table.get('data')) { 9 | return null; 10 | } 11 | return table.get('data').toJS(); 12 | } 13 | ); 14 | 15 | const makeSelectTableLastHandId = () => createSelector( 16 | tableSelector, 17 | (table) => { 18 | if (!table) { 19 | return null; 20 | } 21 | let max = 0; 22 | table.keySeq().forEach((k) => { 23 | if (!isNaN(k)) { 24 | const handId = parseInt(k, 10); 25 | if (handId > max) { 26 | max = handId; 27 | } 28 | } 29 | }); 30 | if (max > 0) { 31 | return max; 32 | } 33 | return table.getIn(['data', 'lastHandNetted']) + 1; 34 | } 35 | ); 36 | 37 | export { 38 | makeSelectTableData, 39 | makeSelectTableLastHandId, 40 | }; 41 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/Wrapper.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const Wrapper = styled.div` 4 | padding: 2px; 5 | `; 6 | 7 | export default Wrapper; 8 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * LanguageToggle 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import { connect } from 'react-redux'; 10 | import { createSelector } from 'reselect'; 11 | 12 | import Toggle from 'components/Toggle'; 13 | import Wrapper from './Wrapper'; 14 | import messages from './messages'; 15 | import { appLocales } from '../../i18n'; 16 | import { changeLocale } from '../LanguageProvider/actions'; 17 | import { makeSelectLocale } from '../LanguageProvider/selectors'; 18 | 19 | export class LocaleToggle extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function 20 | render() { 21 | return ( 22 | 23 | 24 | 25 | ); 26 | } 27 | } 28 | 29 | LocaleToggle.propTypes = { 30 | onLocaleToggle: PropTypes.func, 31 | locale: PropTypes.string, 32 | }; 33 | 34 | const mapStateToProps = createSelector( 35 | makeSelectLocale(), 36 | (locale) => ({ locale }) 37 | ); 38 | 39 | export function mapDispatchToProps(dispatch) { 40 | return { 41 | onLocaleToggle: (evt) => dispatch(changeLocale(evt.target.value)), 42 | dispatch, 43 | }; 44 | } 45 | 46 | export default connect(mapStateToProps, mapDispatchToProps)(LocaleToggle); 47 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/messages.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LocaleToggle Messages 3 | * 4 | * This contains all the text for the LanguageToggle component. 5 | */ 6 | import { defineMessages } from 'react-intl'; 7 | 8 | export default defineMessages({ 9 | en: { 10 | id: 'boilerplate.containers.LocaleToggle.en', 11 | defaultMessage: 'en', 12 | }, 13 | de: { 14 | id: 'boilerplate.containers.LocaleToggle.de', 15 | defaultMessage: 'de', 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /app/containers/LocaleToggle/tests/Wrapper.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import Wrapper from '../Wrapper'; 5 | 6 | describe('', () => { 7 | it('should render an
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 | --------------------------------------------------------------------------------