({
+ bookMode: state.getIn(['app', 'bookMode'])
+});
+
+
+const mapDispatchToProps = (dispatch) => bindActionCreators(
+ {
+ navigateTo: NavigateActions.navigateTo
+ },
+ dispatch
+);
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(NavBar);
+
diff --git a/src/components/MyAccount/MyAccount.jsx b/src/components/MyAccount/MyAccount.jsx
index d7c18f5a7..32aba543e 100644
--- a/src/components/MyAccount/MyAccount.jsx
+++ b/src/components/MyAccount/MyAccount.jsx
@@ -50,6 +50,8 @@ import {
import {MyAccountPageSelector} from '../../selectors';
import PeerPlaysLogo from '../PeerPlaysLogo';
import {Config} from '../../constants';
+import {AppUtils} from '../../utility';
+
const Option = Select.Option;
@@ -216,7 +218,7 @@ class MyAccount extends PureComponent {
* link on the Breadcrumb - {@link Exchange}
*/
handleNavigateToHome() {
- this.props.navigateTo('/exchange');
+ this.props.navigateTo(AppUtils.getHomePath(this.props.bookMode));
}
/**
@@ -400,7 +402,8 @@ const mapStateToProps = (state) => ({
availableBalance: MyAccountPageSelector.availableBalanceSelector(state),
withdrawLoadingStatus: MyAccountPageSelector.withdrawLoadingStatusSelector(state),
convertedAvailableBalance: MyAccountPageSelector.formattedAvailableBalanceSelector(state),
- accountName: MyAccountPageSelector.accountNameSelector(state)
+ accountName: MyAccountPageSelector.accountNameSelector(state),
+ bookMode: state.getIn(['app', 'bookMode'])
});
function mapDispatchToProps(dispatch) {
diff --git a/src/components/MyWager/MyWager.jsx b/src/components/MyWager/MyWager.jsx
index 03da48d01..7f4068016 100644
--- a/src/components/MyWager/MyWager.jsx
+++ b/src/components/MyWager/MyWager.jsx
@@ -39,8 +39,9 @@ import {bindActionCreators} from 'redux';
import {Map} from 'immutable';
import {I18n} from 'react-redux-i18n';
import {MyWagerSelector, MyAccountPageSelector} from '../../selectors';
-import {MyWagerTabTypes} from '../../constants';
+import {MyWagerTabTypes, BookieModes} from '../../constants';
import PeerPlaysLogo from '../PeerPlaysLogo';
+import {AppUtils} from '../../utility';
const {getBetData, getBetTotal, getCurrencyFormat, getBetsLoadingStatus} = MyWagerSelector;
const TabPane = Tabs.TabPane;
@@ -80,7 +81,7 @@ class MyWager extends PureComponent {
/** Redirect to 'Home' screen when clicked on 'Home' link on the Breadcrumb */
onHomeLinkClick(e) {
e.preventDefault();
- this.props.navigateTo('/exchange');
+ this.props.navigateTo(AppUtils.getHomePath(this.props.bookMode));
}
/**
@@ -168,7 +169,13 @@ class MyWager extends PureComponent {
* This will navigat user to event full market screen
*/
handleEventClick(record) {
- this.props.navigateTo(`/exchange/bettingmarketgroup/${record.group_id}`);
+ if (this.props.bookMode === BookieModes.EXCHANGE) {
+ this.props.navigateTo(`/exchange/bettingmarketgroup/${record.group_id}`);
+ }
+
+ if (this.props.bookMode === BookieModes.SPORTSBOOK) {
+ this.props.navigateTo(`/sportsbook/events/${record.event_id}`);
+ }
}
/**
@@ -298,6 +305,7 @@ function filterOdds(tableData, oddsFormat) {
}
const mapStateToProps = (state) => ({
+ bookMode: state.getIn(['app', 'bookMode']),
betsData: getBetData(state),
betsLoadingStatus: getBetsLoadingStatus(state),
betsCurrencyFormat: getCurrencyFormat(state),
diff --git a/src/components/SideBar/SideBar.jsx b/src/components/SideBar/SideBar.jsx
index 6bd34c886..e9020addd 100644
--- a/src/components/SideBar/SideBar.jsx
+++ b/src/components/SideBar/SideBar.jsx
@@ -32,6 +32,8 @@ import PropTypes from 'prop-types';
import {SidebarSelector} from '../../selectors';
import log from 'loglevel';
import {DateUtils} from '../../utility';
+import {BookieModes} from '../../constants';
+import AppUtils from './../../utility/AppUtils';
class SideBar extends PureComponent {
constructor(props) {
@@ -47,7 +49,8 @@ class SideBar extends PureComponent {
componentWillReceiveProps(nextProps) {
if (
this.props.completeTree !== nextProps.completeTree ||
- this.props.objectId !== nextProps.objectId
+ this.props.objectId !== nextProps.objectId ||
+ this.props.bookMode !== nextProps.bookMode
) {
this.setState({
tree: this.createCurrentStateTree(nextProps.completeTree, nextProps.objectId)
@@ -55,6 +58,14 @@ class SideBar extends PureComponent {
}
}
+ componentDidUpdate(prevProps) {
+ if (prevProps.bookMode !== this.props.bookMode) {
+ this.setState({
+ tree: this.createCurrentStateTree(this.props.completeTree, this.props.objectId)
+ });
+ }
+ }
+
setNodeSelected(node) {
return node.set('isSelected', true).set('isOpen', true);
}
@@ -73,10 +84,7 @@ class SideBar extends PureComponent {
* for detailed explanation.
*/
/* https://stackoverflow.com/questions/41298577/how-to-get-altered-tree-from-immutable-tree-maximising-reuse-of-nodes*/ //eslint-disable-line
- createCurrentStateTree(
- completeTree,
- targetObjectId
- ) {
+ createCurrentStateTree(completeTree, targetObjectId) {
if (!targetObjectId || targetObjectId === 'exchange') {
//hardcode id for all-sports node,
targetObjectId = '0';
@@ -94,25 +102,28 @@ class SideBar extends PureComponent {
// For sport
if (keyPath.length === 1) {
- newTree = newTree.updateIn(keyPath.slice(0, 1), this.setNodeSelected);
+ newTree = newTree.updateIn(keyPath.slice(0, 1), (node) => node.set('isSelected', true).set('isOpen', true)); // eslint-disable-line
} else if (keyPath.length === 3) {
// For event group
newTree = newTree
- .updateIn(keyPath.slice(0, 1), this.setNodeOpen)
- .updateIn(keyPath.slice(0, 3), this.setNodeSelected);
+ .updateIn(keyPath.slice(0, 1), (node) => node.set('isOpen', true))
+ .updateIn(keyPath.slice(0, 3), (node) => node.set('isSelected', true).set('isOpen', true)
+ );
} else if (keyPath.length === 5) {
// For event
newTree = newTree
- .updateIn(keyPath.slice(0, 1), this.setNodeOpen)
- .updateIn(keyPath.slice(0, 3), this.setNodeSelected)
- .updateIn(keyPath.slice(0, 5), this.setNodeSelected);
+ .updateIn(keyPath.slice(0, 1), (node) => node.set('isOpen', true))
+ .updateIn(keyPath.slice(0, 3), (node) => node.set('isSelected', true).set('isOpen', true))
+ .updateIn(keyPath.slice(0, 5), (node) => node.set('isSelected', true).set('isOpen', true)
+ );
} else if (keyPath.length === 7) {
// For betting market group
newTree = newTree
- .updateIn(keyPath.slice(0, 1), this.setNodeOpen)
- .updateIn(keyPath.slice(0, 3), this.setNodeOpen)
- .updateIn(keyPath.slice(0, 5), this.setNodeSelected)
- .updateIn(keyPath.slice(0, 7), this.setNodeSelected);
+ .updateIn(keyPath.slice(0, 1), (node) => node.set('isOpen', true))
+ .updateIn(keyPath.slice(0, 3), (node) => node.set('isOpen', true))
+ .updateIn(keyPath.slice(0, 5), (node) => node.set('isSelected', true).set('isOpen', true))
+ .updateIn(keyPath.slice(0, 7), (node) => node.set('isSelected', true).set('isOpen', true)
+ );
}
// Compare all nodes to see which ones were altered:
@@ -120,16 +131,27 @@ class SideBar extends PureComponent {
const altered = differences(completeTree, newTree, 'children').map((x) => x.get('id'));
if (keyPath.length >= 5) {
+ if (this.props.bookMode === BookieModes.SPORTSBOOK) {
+ // If we're in sportbook mode
+ // remove all the children of the event from being shown in the sidebar.
+ keyPath.push('children');
+ newTree = newTree.removeIn(keyPath);
+ }
+
newTree = newTree.setIn(
keyPath.slice(0, 4),
- newTree.getIn(keyPath.slice(0, 4)).filter((metric) => metric.get('id') === altered[2])
+ newTree.getIn(keyPath.slice(0, 4)).filter((metric) => {
+ return metric.get('id') === altered[2];
+ })
);
}
if (keyPath.length >= 3) {
newTree = newTree.setIn(
keyPath.slice(0, 2),
- newTree.getIn(keyPath.slice(0, 2)).filter((metric) => metric.get('id') === altered[1])
+ newTree.getIn(keyPath.slice(0, 2)).filter((metric) => {
+ return metric.get('id') === altered[1];
+ })
);
}
@@ -165,24 +187,31 @@ class SideBar extends PureComponent {
* @param keyPath - is the path from root to current node
*/
onNodeMouseClick(event, tree, node) {
- const {navigateTo} = this.props;
+ let navPath = (AppUtils.getHomePath(this.props.bookMode));
// '0' is hardcode id for all-sports node,
if (node.id === '0') {
- navigateTo('/exchange/');
+ this.props.navigateTo(navPath);
} else {
if (node.customComponent.toLowerCase() === 'event') {
+ // If you're viewing a sportsbook, there is no BMG page. Stop and redirect to events page.
+
+ if (this.props.bookMode === BookieModes.SPORTSBOOK) {
+ // Return early so no further code is executed.
+ return this.props.navigateTo(navPath + '/events/' + node.id);
+ }
+
const moneyline = node.children.filter(
(mktGroup) => mktGroup.description.toUpperCase() === 'MONEYLINE'
);
if (moneyline.length > 0) {
- navigateTo('/exchange/bettingmarketgroup/' + moneyline[0].id);
+ this.props.navigateTo(navPath + '/bettingmarketgroup/' + moneyline[0].id);
} else {
- navigateTo('/exchange/bettingmarketgroup/' + node.children[0].id);
+ this.props.navigateTo(navPath + '/bettingmarketgroup/' + node.children[0].id);
}
} else {
- navigateTo('/exchange/' + node.customComponent.toLowerCase() + '/' + node.id);
+ this.props.navigateTo(navPath + '/' + node.customComponent.toLowerCase() + '/' + node.id);
}
}
}
@@ -263,7 +292,8 @@ SideBar.defaultProps = {
const mapStateToProps = (state) => {
return {
- completeTree: SidebarSelector.getSidebarCompleteTree(state)
+ completeTree: SidebarSelector.getSidebarCompleteTree(state),
+ bookMode: state.getIn(['app', 'bookMode'])
};
};
diff --git a/src/components/Sport/Sport.jsx b/src/components/Sport/Sport.jsx
index c03d973f1..939e148fe 100644
--- a/src/components/Sport/Sport.jsx
+++ b/src/components/Sport/Sport.jsx
@@ -5,7 +5,7 @@ import {SimpleBettingWidget} from '../BettingWidgets';
import {SportPageActions, NavigateActions} from '../../actions';
import {SportPageSelector, QuickBetDrawerSelector} from '../../selectors';
import PeerPlaysLogo from '../PeerPlaysLogo';
-import {DateUtils} from '../../utility';
+import {DateUtils, AppUtils} from '../../utility';
import {bindActionCreators} from 'redux';
const MAX_EVENTS_PER_WIDGET = 10;
@@ -20,7 +20,7 @@ class Sport extends PureComponent {
if (!nextProps.sport || nextProps.sport.isEmpty()) {
// Sport doesn't exist,
// Go back to home page
- this.props.navigateTo('/exchange');
+ this.props.navigateTo(AppUtils.getHomePath(this.props.bookMode));
} else {
const prevSportId = this.props.params.objectId;
const nextSportId = nextProps.params.objectId;
@@ -76,9 +76,11 @@ class Sport extends PureComponent {
const mapStateToProps = (state, ownProps) => {
const sport = SportPageSelector.getSport(state, ownProps);
+ const bookMode = state.getIn(['app', 'bookMode']);
let props = {
- sport
+ sport,
+ bookMode
};
// Populate other properties if sport exists
diff --git a/src/components/SportsBook/SportsBook.jsx b/src/components/SportsBook/SportsBook.jsx
new file mode 100644
index 000000000..f1addbf97
--- /dev/null
+++ b/src/components/SportsBook/SportsBook.jsx
@@ -0,0 +1,86 @@
+import React, {PureComponent} from 'react';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {BackingWidgetContainer} from '../BettingWidgets';
+import {EventPageSelector} from '../../selectors';
+import {ObjectUtils, SportsbookUtils} from '../../utility';
+import {NavigateActions} from '../../actions';
+
+const MAX_EVENTS = 3;
+
+class SportsBook extends PureComponent {
+ render() {
+
+ return (
+
+
+ {
+ this.props.allSports.map((sport) => {
+
+ const events = sport.get('events');
+
+ let eventsToDisplay = [];
+
+ events && events.slice(0, MAX_EVENTS).forEach((e) => {
+
+ let bmgs = e.get('bettingMarketGroups');
+
+ if (bmgs) {
+ let bmg = bmgs.first();
+
+ if (bmg && SportsbookUtils.hasBettingMarkets(bmg)) {
+ eventsToDisplay.push(
+ bmg
+ .set('eventName', e.get('name'))
+ .set('eventID', e.get('id'))
+ .set('eventTime', e.get('start_time'))
+ .set('eventStatus', ObjectUtils.eventStatus(e))
+ );
+ }
+ }
+ });
+
+ if (eventsToDisplay.length > 0) {
+ return (
+
+ );
+ } else {
+ return null;
+ }
+ })
+ }
+
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ const allSports = EventPageSelector.getAllSportsData(state, ownProps);
+
+ return {
+ allSports
+ };
+};
+
+const mapDispatchToProps = (dispatch) => bindActionCreators(
+ {
+ navigateTo: NavigateActions.navigateTo
+ },
+ dispatch
+);
+
+export default connect(mapStateToProps, mapDispatchToProps)(SportsBook);
diff --git a/src/components/SportsBook/SportsBook.less b/src/components/SportsBook/SportsBook.less
new file mode 100644
index 000000000..23f8659b6
--- /dev/null
+++ b/src/components/SportsBook/SportsBook.less
@@ -0,0 +1,16 @@
+@import (reference) '../App/Colors.less';
+@import (reference) '../App/ProThemeColors.less';
+
+.more-sport-link {
+ width: 100%;
+ height: 30px;
+ background-color: @cinder;
+ a {
+ float: right;
+ margin-right: 15px;
+ color: @white;
+ &:hover {
+ color: @dark_tangerine;
+ }
+ }
+}
diff --git a/src/components/SportsBook/index.js b/src/components/SportsBook/index.js
new file mode 100644
index 000000000..49e1548e8
--- /dev/null
+++ b/src/components/SportsBook/index.js
@@ -0,0 +1,3 @@
+import SportsBook from './SportsBook';
+import './SportsBook.less';
+export default SportsBook;
\ No newline at end of file
diff --git a/src/components/SportsBookEvent/SportsBookEvent.jsx b/src/components/SportsBookEvent/SportsBookEvent.jsx
new file mode 100644
index 000000000..f13b6f892
--- /dev/null
+++ b/src/components/SportsBookEvent/SportsBookEvent.jsx
@@ -0,0 +1,145 @@
+import React, {PureComponent} from 'react';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {BettingMarketGroupBanner} from '../Banners';
+import {BackingWidgetContainer} from '../BettingWidgets';
+import {ObjectUtils, DateUtils} from '../../utility';
+import PeerPlaysLogo from '../PeerPlaysLogo';
+import {MarketDrawerActions, NavigateActions, BettingMarketGroupPageActions} from '../../actions';
+import _ from 'lodash';
+
+import {
+ BettingMarketGroupPageSelector,
+ EventGroupPageSelector,
+ MarketDrawerSelector,
+ MyAccountPageSelector,
+ EventPageSelector,
+} from '../../selectors';
+
+class SportsBookEvent extends PureComponent {
+
+ componentDidMount() {
+ const doc = document.querySelector('body');
+ doc.style.minWidth = '1210px';
+ doc.style.overflow = 'overlay';
+ }
+
+ componentWillUnmount() {
+ document.querySelector('body').style.minWidth = '1002px';
+ }
+
+ componentWillMount() {
+ this.props.getOpenBetsForEvent(this.props.params.eventId);
+ }
+
+ componentWillUpdate(nextProps) {
+ if (!nextProps.event || nextProps.event.isEmpty()) {
+ // Betting market group doesn't exist,
+ // Go back to home page
+ this.props.navigateTo('/exchange');
+ } else {
+ const prevEventId = this.props.params.eventId;
+ const nextEventId = nextProps.params.eventId;
+
+ if (
+ nextEventId !== prevEventId ||
+ !_.isEqual(this.props.marketData.toJS(), nextProps.marketData.toJS()) ||
+ !_.isEqual(this.props.event, nextProps.event) ||
+ nextProps.eventName !== this.props.eventName ||
+ !_.isEqual(nextProps.eventStatus, this.props.eventStatus)
+ ) {
+ // Get the data
+ this.props.getOpenBetsForEvent(this.props.params.eventId);
+ }
+ }
+ }
+
+ render() {
+ // Return nothing if betting market group doesn't exist
+ if (!this.props.event || this.props.event.isEmpty() || this.props.eventStatus === null) {
+ return null;
+ } else {
+ return (
+
+
+
+ {this.props.marketData.reverse().map((e, index) => {
+ return (
+
+ );
+ })}
+
+
+ );
+ }
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ const event = EventPageSelector.getEvent(state, ownProps.params.eventId);
+ let sportName;
+
+ if (event) {
+ sportName = EventGroupPageSelector.getSportNameFromEvent(state, event);
+ }
+
+ let props = {
+ event,
+ oddsFormat: MyAccountPageSelector.oddsFormatSelector(state),
+ };
+
+ // Populate other properties if betting market group exists
+ if (event && !event.isEmpty()) {
+ _.assign(props, {
+ marketData: EventPageSelector.getMarketData(state, {
+ eventId: ownProps.params.eventId
+ }),
+ eventName: event.get('name'),
+ eventTime: DateUtils.getLocalDate(new Date(event.get('start_time'))),
+ eventStatus: ObjectUtils.eventStatus(event),
+ isLiveMarket: ObjectUtils.isActiveEvent(event),
+ unconfirmedBets: BettingMarketGroupPageSelector.getUnconfirmedBets(state, ownProps),
+ loadingStatus: BettingMarketGroupPageSelector.getLoadingStatus(state, ownProps),
+ canCreateBet: MarketDrawerSelector.canAcceptBet(state, ownProps),
+ sportName,
+ });
+ }
+
+ return props;
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return bindActionCreators(
+ {
+ createBet: MarketDrawerActions.createBet,
+ navigateTo: NavigateActions.navigateTo,
+ getData: BettingMarketGroupPageActions.getData,
+ resetOpenBets: MarketDrawerActions.resetOpenBets,
+ getOpenBetsForEvent: MarketDrawerActions.getOpenBetsForEvent
+ },
+ dispatch
+ );
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(SportsBookEvent);
diff --git a/src/components/SportsBookEvent/index.js b/src/components/SportsBookEvent/index.js
new file mode 100644
index 000000000..450a00e68
--- /dev/null
+++ b/src/components/SportsBookEvent/index.js
@@ -0,0 +1,3 @@
+import SportsBookEvent from './SportsBookEvent';
+
+export default SportsBookEvent;
\ No newline at end of file
diff --git a/src/components/SportsBookEventGroup/SportsBookEventGroup.jsx b/src/components/SportsBookEventGroup/SportsBookEventGroup.jsx
new file mode 100644
index 000000000..422afb76f
--- /dev/null
+++ b/src/components/SportsBookEventGroup/SportsBookEventGroup.jsx
@@ -0,0 +1,138 @@
+import React, {PureComponent} from 'react';
+import {connect} from 'react-redux';
+import {EventPageSelector, EventGroupPageSelector} from '../../selectors';
+import {BackingWidgetContainer} from '../BettingWidgets';
+import {SportBanner} from '../Banners';
+import {ObjectUtils} from '../../utility';
+import {Icon} from 'antd';
+
+const MAX_EVENTS = 15;
+class SportsBookEventGroup extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ pagination: 0
+ };
+ this.setPage = this.setPage.bind(this);
+ }
+
+
+ setPage(page) {
+ this.setState({
+ pagination: page
+ });
+ }
+
+ renderPagination(numPages) {
+
+ // Do not render pagination if there are less than two pages.
+ if (numPages < 2) {
+ return;
+ }
+
+ let pageNumbers = [];
+
+ for (let i = 0; i < numPages; i++) {
+ pageNumbers.push(i);
+ }
+
+ const pageNum = pageNumbers.map((page) => {
+ return (
+
this.setPage(page) }
+ className={ this.state.pagination === page ? 'active' : '' }
+ >
+ { page + 1 /* page starts from 0, but we want the display to start from 1 */}
+
+ );
+ });
+
+ const prevButton =
+
+ this.setPage(this.state.pagination-1) }
+ className={ this.state.pagination === 0 ? 'no-click' : '' }
+ >
+
+
+ ;
+
+ const nextButton =
+
+ this.setPage(this.state.pagination+1) }
+ className={ this.state.pagination === numPages-1 ? 'no-click' : '' }
+ >
+
+
+ ;
+
+ return (
+
+ {prevButton}
+ {pageNum}
+ {nextButton}
+
+ );
+ }
+
+ render() {
+ const eventGroup = this.props.eventGroup;
+
+ if (eventGroup) {
+ const events = eventGroup.get('events');
+ let eventsToDisplay = [];
+ let pagination = '';
+
+ if (events) {
+ let start = this.state.pagination * MAX_EVENTS;
+ let finish = (this.state.pagination + 1) * MAX_EVENTS;
+ let numPages = Math.ceil(events.size / MAX_EVENTS);
+ pagination = this.renderPagination(numPages);
+
+ events.slice(start, finish).forEach((e) => {
+ let bmgs = e.get('bettingMarketGroups');
+
+ if (bmgs && !bmgs.isEmpty()) {
+ let bmg = bmgs.first();
+
+ eventsToDisplay.push(
+ bmg
+ .set('eventName', e.get('name'))
+ .set('eventID', e.get('id'))
+ .set('eventTime', e.get('start_time'))
+ .set('eventStatus', ObjectUtils.eventStatus(e))
+ );
+ }
+ });
+ }
+
+ return (
+
+ );
+ }
+
+ return null;
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ const eventGroup = EventPageSelector.getEventGroupData(state, ownProps.params.objectId);
+ return {
+ eventGroup: eventGroup,
+ sportName: EventGroupPageSelector.getSportName(state, ownProps)
+ };
+};
+
+export default connect(mapStateToProps)(SportsBookEventGroup);
diff --git a/src/components/SportsBookEventGroup/SportsBookEventGroup.less b/src/components/SportsBookEventGroup/SportsBookEventGroup.less
new file mode 100644
index 000000000..71d6a6105
--- /dev/null
+++ b/src/components/SportsBookEventGroup/SportsBookEventGroup.less
@@ -0,0 +1,39 @@
+@import (reference) '../App/Colors.less';
+@import (reference) '../App/ProThemeColors.less';
+
+.event-group-pagination {
+ background-color: @cinder;
+ padding: 0px 15px 15px 0px;
+ display: flex;
+ justify-content: flex-end;
+ font-size: 16px;
+
+ .no-cursor {
+ cursor: not-allowed;
+ }
+
+ .pagination-icon {
+ font-size: 12px;
+ }
+
+ li {
+ display: inline-block;
+ padding: 5px 10px 5px 10px;
+ margin-right: 10px;
+ font-size: 16px;
+
+ &:hover {
+ cursor: pointer;
+ color: @dark_tangerine;
+ }
+
+ &.active {
+ background-color: @cello;
+ }
+
+ &.no-click {
+ pointer-events: none;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/components/SportsBookEventGroup/index.js b/src/components/SportsBookEventGroup/index.js
new file mode 100644
index 000000000..bdceda2ac
--- /dev/null
+++ b/src/components/SportsBookEventGroup/index.js
@@ -0,0 +1,3 @@
+import SportsBookEventGroup from './SportsBookEventGroup';
+import './SportsBookEventGroup.less';
+export default SportsBookEventGroup;
\ No newline at end of file
diff --git a/src/components/SportsBookSport/SportsBookSport.jsx b/src/components/SportsBookSport/SportsBookSport.jsx
new file mode 100644
index 000000000..429265e58
--- /dev/null
+++ b/src/components/SportsBookSport/SportsBookSport.jsx
@@ -0,0 +1,82 @@
+import React, {PureComponent} from 'react';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {EventPageSelector} from '../../selectors';
+import {BackingWidgetContainer} from '../BettingWidgets';
+import {SportBanner} from '../Banners';
+import {ObjectUtils} from '../../utility';
+import {NavigateActions} from '../../actions';
+
+const MAX_EVENTS = 10;
+class SportsBookSport extends PureComponent {
+ render() {
+ const sport = this.props.sport;
+ return (
+
+ { sport &&
}
+
+ {
+ sport && this.props.sport.get('eventGroups').map((eg) => {
+
+ const events = eg.get('events');
+
+ let eventsToDisplay = [];
+
+ events && events.slice(0, MAX_EVENTS).forEach((e) => {
+ let bmgs = e.get('bettingMarketGroups');
+
+ if (bmgs) {
+ let bmg = bmgs.first();
+
+ if (bmg) {
+ eventsToDisplay.push(
+ bmg
+ .set('eventName', e.get('name'))
+ .set('eventID', e.get('id'))
+ .set('eventTime', e.get('start_time'))
+ .set('eventStatus', ObjectUtils.eventStatus(e))
+ );
+ }
+ }
+ });
+
+ return eventsToDisplay.length > 0 &&
+ (
+
+ );
+ })
+ }
+
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ sport: EventPageSelector.getSportData(state, ownProps.params.objectId)
+ };
+};
+
+const mapDispatchToProps = (dispatch) => bindActionCreators(
+ {
+ navigateTo: NavigateActions.navigateTo
+ },
+ dispatch
+);
+
+export default connect(mapStateToProps, mapDispatchToProps)(SportsBookSport);
diff --git a/src/components/SportsBookSport/index.js b/src/components/SportsBookSport/index.js
new file mode 100644
index 000000000..90ae6c158
--- /dev/null
+++ b/src/components/SportsBookSport/index.js
@@ -0,0 +1,3 @@
+import SportsBookSport from './SportsBookSport';
+
+export default SportsBookSport;
\ No newline at end of file
diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js
index b5e7f41f3..2c41e5e3e 100644
--- a/src/constants/ActionTypes.js
+++ b/src/constants/ActionTypes.js
@@ -23,6 +23,7 @@ export default {
APP_SHOW_NOTIFICATION_CARD: 'APP_SHOW_NOTIFICATION_CARD',
APP_SET_TITLE_BAR_TRANSPARENCY: 'APP_SET_TITLE_BAR_TRANSPARENCY',
APP_SET_GATEWAY_ACCOUNT: 'APP_SET_GATEWAY_ACCOUNT',
+ APP_SET_BOOK_MODE: 'APP_SET_BOOK_MODE',
APP_HIDE_LICENSE_SCREEN: 'APP_HIDE_LICENSE_SCREEN',
// Auth actions
AUTH_RESET_AUTO_LOGIN_INFO: 'AUTH_RESET_AUTO_LOGIN_INFO',
diff --git a/src/constants/BackingWidgetTypes.js b/src/constants/BackingWidgetTypes.js
new file mode 100644
index 000000000..2618ab5cf
--- /dev/null
+++ b/src/constants/BackingWidgetTypes.js
@@ -0,0 +1,39 @@
+const orderPriority = {
+ TOP: 0,
+ MIDDLE: 5,
+ BOTTOM: 10,
+};
+
+// This constant object maps onto BMG descriptions produced by BOS
+const BackingWidgetTypes = {
+ MATCHODDS: 'MATCHODDS',
+ MONEYLINE: 'MONEYLINE',
+ OVERUNDER: 'OVERUNDER',
+};
+
+const BackingWidgetLayouts = {
+ MATCHODDS: {
+ columns: {
+ eventFlag: 6,
+ default: 8
+ },
+ order: orderPriority.TOP, // Highest Priority, always show on the top.
+ numberOfMarkets: 3,
+ },
+ MONEYLINE: {
+ columns: {
+ eventFlag: 7,
+ default: 12
+ },
+ order: orderPriority.TOP, // Highest Priority, always show on the top.
+ },
+ OVERUNDER: {
+ columns: {
+ eventFlag: 12,
+ default: 12
+ },
+ order: orderPriority.MIDDLE, // Middle priority, show below the top.
+ },
+};
+
+export {BackingWidgetTypes, BackingWidgetLayouts};
diff --git a/src/constants/BookieModes.js b/src/constants/BookieModes.js
new file mode 100644
index 000000000..3a9312c2a
--- /dev/null
+++ b/src/constants/BookieModes.js
@@ -0,0 +1,5 @@
+const BookieModes = {
+ EXCHANGE: 'exchange',
+ SPORTSBOOK: 'sportsbook',
+};
+export default BookieModes;
diff --git a/src/constants/LayoutConstants.js b/src/constants/LayoutConstants.js
new file mode 100644
index 000000000..50478ceb6
--- /dev/null
+++ b/src/constants/LayoutConstants.js
@@ -0,0 +1,11 @@
+// Enumeration for Loading Status
+const LayoutConstants = {
+ sidebarWidth: 220,
+ betslipWidth: 360,
+ splitPaneStyle: {
+ top: '0px',
+ position: 'fixed',
+ },
+};
+
+export default LayoutConstants;
diff --git a/src/constants/index.js b/src/constants/index.js
index 8859e8855..46878aa0c 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -7,6 +7,7 @@ import ObjectPrefix from './ObjectPrefix';
import BetTypes from './BetTypes';
import BetCategories from './BetCategories';
import CurrencyTypes from './CurrencyTypes';
+import BookieModes from './BookieModes';
import TimeRangePeriodTypes from './TimeRangePeriodTypes';
import ChainTypes from './ChainTypes';
import AppBackgroundTypes from './AppBackgroundTypes';
@@ -18,6 +19,8 @@ import BettingMarketStatus from './BettingMarketStatus';
import BettingMarketGroupStatus from './BettingMarketGroupStatus';
import BettingDrawerStates from './BettingDrawerStates';
import BettingMarketResolutionTypes from './BettingMarketResolutionTypes';
+import BackingWidgetTypes from './BackingWidgetTypes';
+import LayoutConstants from './LayoutConstants';
export {
Config,
@@ -39,5 +42,8 @@ export {
EventStatus,
BettingMarketStatus,
BettingMarketGroupStatus,
- BettingMarketResolutionTypes
+ BettingMarketResolutionTypes,
+ BookieModes,
+ BackingWidgetTypes,
+ LayoutConstants,
};
diff --git a/src/index.js b/src/index.js
index c314bba7f..0e81671a6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -16,6 +16,10 @@ import Main from './components/Main';
import Exchange from './components/Exchange';
import AllSports from './components/AllSports';
import Sport from './components/Sport';
+import SportsBook from './components/SportsBook';
+import SportsBookEvent from './components/SportsBookEvent';
+import SportsBookEventGroup from './components/SportsBookEventGroup';
+import SportsBookSport from './components/SportsBookSport';
import EventGroup from './components/EventGroup';
import BettingMarketGroup from './components/BettingMarketGroup';
import configureStore from './store/configureStore';
@@ -112,6 +116,14 @@ const routes = (
/>
+
+
+
+
+
+
+
+
diff --git a/src/reducers/AppReducer.js b/src/reducers/AppReducer.js
index ac72af3d0..c0da934b7 100644
--- a/src/reducers/AppReducer.js
+++ b/src/reducers/AppReducer.js
@@ -1,4 +1,5 @@
-import {ActionTypes, LoadingStatus, ConnectionStatus, AppBackgroundTypes} from '../constants';
+import {ActionTypes, LoadingStatus, ConnectionStatus, AppBackgroundTypes,
+ BookieModes} from '../constants';
import Immutable from 'immutable';
const initialState = Immutable.fromJS({
@@ -16,7 +17,9 @@ const initialState = Immutable.fromJS({
isShowNotificationCard: false,
isTitleBarTransparent: true,
gatewayAccount: {},
- showLicenseScreen: false
+ showLicenseScreen: false,
+ bookMode: BookieModes.EXCHANGE,
+
});
export default function(state = initialState, action) {
@@ -112,6 +115,12 @@ export default function(state = initialState, action) {
});
}
+ case ActionTypes.APP_SET_BOOK_MODE: {
+ return state.merge({
+ bookMode: action.mode,
+ });
+ }
+
default:
return state;
}
diff --git a/src/selectors/CommonSelector.js b/src/selectors/CommonSelector.js
index f1ef2be5e..46d7bae53 100644
--- a/src/selectors/CommonSelector.js
+++ b/src/selectors/CommonSelector.js
@@ -33,8 +33,12 @@ const getRulesById = (state) => state.getIn(['rule', 'rulesById']);
const getSportsById = (state) => state.getIn(['sport', 'sportsById']);
+const getSportById = (state, sportId) => state.getIn(['sport', 'sportsById', sportId]);
+
const getEventGroupsById = (state) => state.getIn(['eventGroup', 'eventGroupsById']);
+const getEventGroupById = (state, eventGroupId) => state.getIn(['eventGroup', 'eventGroupsById', eventGroupId]); // eslint-disable-line
+
const getEventGroupsBySportId = createSelector(
getEventGroupsById,
(eventGroupsById) => eventGroupsById.toList().groupBy((eventGroup) => eventGroup.get('sport_id'))
@@ -229,6 +233,8 @@ const CommonSelector = {
getNotificationSetting,
getAssetsById,
getSportsById,
+ getSportById,
+ getEventGroupById,
getEventGroupsById,
getEventGroupsBySportId,
getEventsById,
@@ -250,4 +256,4 @@ const CommonSelector = {
getSimpleBettingWidgetBinnedOrderBooksByEventId
};
-export default CommonSelector;
+export default CommonSelector;
\ No newline at end of file
diff --git a/src/selectors/EventPageSelector.js b/src/selectors/EventPageSelector.js
new file mode 100644
index 000000000..ee05908d2
--- /dev/null
+++ b/src/selectors/EventPageSelector.js
@@ -0,0 +1,393 @@
+import {createSelector} from 'reselect';
+import CommonSelector from './CommonSelector';
+import Immutable from 'immutable';
+import {SportsbookUtils} from '../utility';
+import {Config} from '../constants';
+
+const {
+ getBettingMarketsById,
+ getBinnedOrderBooksByBettingMarketId,
+ getActiveEventsBySportId,
+ getSportsById,
+ getSportById,
+ getEventGroupsBySportId,
+ getEventGroupById,
+ getEventsByEventGroupId
+} = CommonSelector;
+
+const filters = Config.filters;
+
+const getEvent = (state, id) => state.getIn(['event', 'eventsById', id]);
+
+const getEventIdByFromBMGId = (state, id) => {
+ const bmgs = state.getIn(['bettingMarketGroup', 'bettingMarketGroupsById']);
+ let foundEventID = -1;
+
+ if (bmgs) {
+ bmgs.valueSeq().forEach((bettingMarket) => {
+ if (bettingMarket.get('id') === id) {
+ foundEventID = bettingMarket.get('event_id');
+ }
+ });
+ }
+
+ return foundEventID;
+};
+
+const getBettingMarketGroupsByEventId = (state, ownProps) => {
+ const bmgs = state.getIn(['bettingMarketGroup', 'bettingMarketGroupsById']);
+ const eventId = ownProps.eventId;
+
+ let bettingMarketGroups = Immutable.List();
+ bmgs.valueSeq().forEach((bettingMarketGroup) => {
+ if (bettingMarketGroup.get('event_id') === eventId) {
+ bettingMarketGroups = bettingMarketGroups.push(bettingMarketGroup);
+ }
+ });
+ return bettingMarketGroups;
+};
+
+const getAllBettingMarketGroupsByEventId = (state) => {
+ const bmgs = state.getIn(['bettingMarketGroup', 'bettingMarketGroupsById']);
+ let events = [];
+ bmgs.valueSeq().forEach((bmg) => {
+ let eventID = bmg.get('event_id');
+
+ if (!events[eventID]) {
+ events[eventID] = Immutable.List();
+ }
+
+ events[eventID] = events[eventID].push(bmg);
+ });
+ return events;
+};
+
+const getFirstBettingMarketGroupByEventId = createSelector(
+ [getBettingMarketGroupsByEventId],
+ (bettingMarketGroups) => {
+ if (bettingMarketGroups.first()) {
+ return SportsbookUtils.prioritySort(bettingMarketGroups).first();
+ }
+ }
+);
+
+const getBettingMarketsByBMGID = createSelector([getBettingMarketsById], (bettingMarkets) => {
+ let bettingMarketsByGroupID = {};
+
+ bettingMarkets.forEach((bettingMarket) => {
+ const groupID = bettingMarket.get('group_id');
+
+ if (!bettingMarketsByGroupID[groupID]) {
+ bettingMarketsByGroupID[groupID] = [];
+ }
+
+ bettingMarketsByGroupID[groupID].push(bettingMarket);
+ });
+ return bettingMarketsByGroupID;
+});
+
+const getMarketData = createSelector(
+ [getBettingMarketGroupsByEventId, getBettingMarketsByBMGID, getBinnedOrderBooksByBettingMarketId],
+ (
+ bettingMarketGroups,
+ // bettingMarkets is a dictionary
+ // - Where the keys are BMG ID's
+ // - Where the values are arrays that contain all of the BM's that pertain to the BMG
+ bettingMarkets,
+ binnedOrderBooksByBettingMarketId
+ ) => {
+ // Iterate through the values of the bettingMarkets dictionary
+ Object.values(bettingMarkets).forEach((bm) => {
+ // There are one or more bm in a single betting market group, this loop matches them
+ // with the order book that pertains to the BM.
+ for (let i = 0; i < bm.length; i++) {
+ bm[i] = bm[i].set('orderBook', binnedOrderBooksByBettingMarketId.get(bm[i].get('id')));
+
+ // We only care about the lay bets (the bets that will display as open back bets)
+ let aggregated_lay_bets = bm[i].getIn(['orderBook', 'aggregated_lay_bets']);
+
+ if (aggregated_lay_bets) {
+ aggregated_lay_bets = aggregated_lay_bets.map((aggregated_lay_bet) => {
+ const odds = aggregated_lay_bet.get('backer_multiplier') / Config.oddsPrecision;
+ return aggregated_lay_bet.set('odds', odds);
+ });
+ bm[i] = bm[i].set(
+ 'backOrigin',
+ aggregated_lay_bets.sort((a, b) => a.get('odds') - b.get('odds'))
+ );
+ }
+ }
+ });
+
+ // Iterate through the list of betting market groups and...
+ bettingMarketGroups.forEach((bmg, index) => {
+ // Pick the betting markets out of the dictionary that pertain to the given BMG
+ bettingMarketGroups = bettingMarketGroups.set(
+ index,
+ bmg.set('bettingMarkets', bettingMarkets[bmg.get('id')])
+ );
+ });
+
+ // Group all of the over under BMGs as if they belonged to the same BMG.
+ bettingMarketGroups = SportsbookUtils.groupOverUnders(bettingMarketGroups);
+
+ // Sort the betting market groups so that they are in priority order. Then center
+ // the draw bm in BMG's where there is a draw option
+ bettingMarketGroups = SportsbookUtils.sortAndCenter(bettingMarketGroups);
+
+ return bettingMarketGroups;
+ }
+);
+
+const getBettingMarketsWithOrderBook = createSelector(
+ [getBettingMarketsById, getBinnedOrderBooksByBettingMarketId],
+ (bettingMarkets, binnedOrderBooksByBettingMarketId) => {
+ // Iterate through the values of the bettingMarkets dictionary
+ bettingMarkets = bettingMarkets.map((bm) => {
+ // There are one or more bm in a single betting market group, this loop matches them
+ // with the order book that pertains to the BM.
+
+ bm = bm.set('orderBook', binnedOrderBooksByBettingMarketId.get(bm.get('id')));
+
+ // We only care about the lay bets (the bets that will display as open back bets)
+ let aggregated_lay_bets = bm.getIn(['orderBook', 'aggregated_lay_bets']);
+
+ if (aggregated_lay_bets) {
+ aggregated_lay_bets = aggregated_lay_bets.map((aggregated_lay_bet) => {
+ const odds = aggregated_lay_bet.get('backer_multiplier') / Config.oddsPrecision;
+ return aggregated_lay_bet.set('odds', odds);
+ });
+ bm = bm.set(
+ 'backOrigin',
+ aggregated_lay_bets.sort((a, b) => a.get('odds') - b.get('odds'))
+ );
+ }
+
+ return bm;
+ });
+
+ let orderBooksByBMGID = [];
+
+ bettingMarkets.forEach((bm) => {
+ let bmgID = bm.get('group_id');
+
+ if (!orderBooksByBMGID[bmgID]) {
+ orderBooksByBMGID[bmgID] = [];
+ }
+
+ orderBooksByBMGID[bmgID].push(bm);
+ });
+
+ return orderBooksByBMGID;
+ }
+);
+
+const getAllSportsData = createSelector(
+ [
+ getSportsById,
+ getActiveEventsBySportId,
+ getAllBettingMarketGroupsByEventId,
+ getBettingMarketsWithOrderBook
+ ],
+ (sportsById, activeEventsBySportId, bmgsByEventID, bettingMarketsWithOrderBook) => {
+ let allSportsData = Immutable.List();
+
+ // Iterate through each sport to build each sport node
+ sportsById.forEach((sport) => {
+ // A sport node consists of a name and a sport ID
+ let sportNode = Immutable.Map()
+ .set('name', sport.get('name'))
+ .set('sport_id', sport.get('id'));
+
+ // Get the events that pertain to the current sport
+ const activeEvents = activeEventsBySportId.get(sport.get('id'));
+
+ // Iterate through each event and parse the relevant data
+ let eventNodes =
+ activeEvents &&
+ activeEvents.map((e) => {
+ let bmgs = bmgsByEventID[e.get('id')];
+
+ if (bmgs) {
+ bmgs = bmgs.filter((bmg) => {
+ let description = bmg.get('description').toUpperCase();
+ let passesFilters = false;
+
+ if (filters.bettingMarketGroup.description.includes(description)) {
+ passesFilters = true;
+ }
+
+ return passesFilters;
+ }).map((bmg) => {
+ let bmgID = bmg.get('id');
+ return bmg.set('bettingMarkets', bettingMarketsWithOrderBook[bmgID]);
+ });
+
+ bmgs = SportsbookUtils.sortAndCenter(bmgs);
+
+ // Put the list of BMGs into their respective events
+ e = e.set('bettingMarketGroups', bmgs);
+
+ return e;
+ } else {
+ return null;
+ }
+ });
+
+ if (eventNodes) {
+ eventNodes = eventNodes.filter((e) => e);
+ eventNodes = SportsbookUtils.sortEventsByDate(eventNodes);
+ }
+
+ // Set events to the sport
+ sportNode = sportNode.set('events', eventNodes);
+ // Set sport to the all sports data
+ allSportsData = allSportsData.push(sportNode);
+ });
+ return allSportsData;
+ }
+);
+
+const getSportData = createSelector(
+ [
+ getSportById,
+ getEventGroupsBySportId,
+ getEventsByEventGroupId,
+ getAllBettingMarketGroupsByEventId,
+ getBettingMarketsWithOrderBook
+ ],
+ (sport, eventGroups, events, bmgsByEventID, bettingMarketsWithOrderBook) => {
+ if (!eventGroups || !events || !sport) {
+ return;
+ }
+
+ let sportData = Immutable.Map();
+ sportData = sportData
+ .set('eventGroups', eventGroups.get(sport.get('id')))
+ .set('sportId', sport.get('id'))
+ .set('sportName', sport.get('name'));
+
+ eventGroups = sportData.get('eventGroups').map((eg) => {
+ let eventList = events.get(eg.get('id'));
+ let eventName = eg.get('name').toUpperCase();
+
+ if (eventList && !filters.eventGroup.name.includes(eventName)) {
+ eventList = eventList.map((e) => {
+ if (e.get('status') !== null && e.get('status') !== undefined) {
+
+ let bmgs = bmgsByEventID[e.get('id')];
+
+ if (bmgs) {
+ bmgs = bmgs.map((bmg) => {
+ let bmgID = bmg.get('id');
+ return bmg.set('bettingMarkets', bettingMarketsWithOrderBook[bmgID]);
+ }).filter((bmg) => {
+ let description = bmg.get('description').toUpperCase();
+ let passesFilters = false;
+
+ if (filters.bettingMarketGroup.description.includes(description)) {
+ passesFilters = true;
+ }
+
+ return passesFilters;
+ });
+
+ bmgs = SportsbookUtils.sortAndCenter(bmgs);
+ }
+
+ // Put the list of BMGs into their respective events
+ return e.set('bettingMarketGroups', bmgs);
+ } else {
+ return Immutable.List();
+ }
+ });
+
+ eventList = SportsbookUtils.sortEventsByDate(eventList);
+
+ if (eventList) {
+ return eg.set('events', eventList.filter((e) => e));
+ } else {
+ return Immutable.List();
+ }
+ } else {
+ return Immutable.List();
+ }
+ });
+
+
+ sportData = sportData.set('eventGroups', eventGroups);
+
+ return sportData;
+ }
+);
+
+const getEventGroupData = createSelector(
+ [
+ getEventGroupById,
+ getEventsByEventGroupId,
+ getAllBettingMarketGroupsByEventId,
+ getBettingMarketsWithOrderBook
+ ],
+ (eventGroup, events, bmgsByEventID, bettingMarketsWithOrderBook) => {
+ if (!eventGroup || !events || !bmgsByEventID || !bettingMarketsWithOrderBook) {
+ return;
+ }
+
+ let eventGroupData = Immutable.Map();
+
+ eventGroupData = eventGroupData
+ .set('name', eventGroup.get('name'))
+ .set('id', eventGroup.get('id'));
+
+ let eventList = events.get(eventGroup.get('id'));
+
+ eventList = eventList.map((e) => {
+ let bmgs = bmgsByEventID[e.get('id')];
+
+ if (bmgs) {
+ bmgs = bmgs.map((bmg) => {
+ let bmgID = bmg.get('id');
+ return bmg.set('bettingMarkets', bettingMarketsWithOrderBook[bmgID]);
+ }).filter((bmg) => {
+ let description = bmg.get('description').toUpperCase();
+ let passesFilters = false;
+
+ if (filters.bettingMarketGroup.description.includes(description)) {
+ passesFilters = true;
+ }
+
+ return passesFilters;
+ });
+ bmgs = SportsbookUtils.sortAndCenter(bmgs);
+ }
+
+ // Put the list of BMGs into their respective events, only if the list is not empty.
+ if (!bmgs.isEmpty()) {
+ return e.set('bettingMarketGroups', bmgs);
+ }
+
+ return null;
+ });
+
+ eventList = eventList.filter((e) => e);
+
+ eventList = SportsbookUtils.sortEventsByDate(eventList);
+
+ eventGroupData = eventGroupData.set('events', eventList);
+
+ return eventGroupData;
+ }
+);
+
+const EventPageSelector = {
+ getEvent,
+ getMarketData,
+ getBettingMarketGroupsByEventId,
+ getEventIdByFromBMGId,
+ getFirstBettingMarketGroupByEventId,
+ getAllSportsData,
+ getSportData,
+ getEventGroupData
+};
+
+export default EventPageSelector;
diff --git a/src/selectors/MyWagerSelector.js b/src/selectors/MyWagerSelector.js
index 9c477edb7..35b7d39be 100644
--- a/src/selectors/MyWagerSelector.js
+++ b/src/selectors/MyWagerSelector.js
@@ -175,6 +175,7 @@ const getExtendedBets = createSelector(
return bet
.set('betting_market_description', bettingMarketDescription)
.set('betting_market_group_description', bettingMarketGroupDescription)
+ .set('event_id', eventId)
.set('event_name', eventName)
.set('event_time', eventTime)
.set('sport_name', sportName);
diff --git a/src/selectors/index.js b/src/selectors/index.js
index fb39b6d71..817d4914e 100644
--- a/src/selectors/index.js
+++ b/src/selectors/index.js
@@ -1,21 +1,23 @@
-import MyWagerSelector from './MyWagerSelector';
-import MyAccountPageSelector from './MyAccountPageSelector';
-import SidebarSelector from './SidebarSelector';
-import BettingMarketGroupPageSelector from './BettingMarketGroupPageSelector';
import AllSportsSelector from './AllSportsSelector';
-import SportPageSelector from './SportPageSelector';
+import BettingMarketGroupPageSelector from './BettingMarketGroupPageSelector';
import EventGroupPageSelector from './EventGroupPageSelector';
+import EventPageSelector from './EventPageSelector';
import MarketDrawerSelector from './MarketDrawerSelector';
+import MyWagerSelector from './MyWagerSelector';
+import MyAccountPageSelector from './MyAccountPageSelector';
import QuickBetDrawerSelector from './QuickBetDrawerSelector';
+import SidebarSelector from './SidebarSelector';
+import SportPageSelector from './SportPageSelector';
export {
+ AllSportsSelector,
+ BettingMarketGroupPageSelector,
EventGroupPageSelector,
- SportPageSelector,
- SidebarSelector,
+ EventPageSelector,
+ MarketDrawerSelector,
MyWagerSelector,
MyAccountPageSelector,
- BettingMarketGroupPageSelector,
- AllSportsSelector,
- MarketDrawerSelector,
- QuickBetDrawerSelector
+ QuickBetDrawerSelector,
+ SidebarSelector,
+ SportPageSelector,
};
diff --git a/src/store/translations.js b/src/store/translations.js
index 687f3d490..19bff8038 100644
--- a/src/store/translations.js
+++ b/src/store/translations.js
@@ -33,7 +33,11 @@ export const translationsObject = {
},
titleBar: {
title: 'BookiePro.fun',
- clock: 'Local Time'
+ clock: 'Local Time',
+ sportsbookToggle: {
+ exchange: 'Exchange',
+ sportsbook: 'Sportsbook'
+ }
},
searchMenu: {
no_of_result_0: 'No results found',
diff --git a/src/utility/AppUtils.js b/src/utility/AppUtils.js
index cefd6adec..07b42071c 100644
--- a/src/utility/AppUtils.js
+++ b/src/utility/AppUtils.js
@@ -1,3 +1,6 @@
+import {BookieModes} from '../constants';
+
+
const AppUtils = {
// Check if the running platform is windows
isWindowsPlatform() {
@@ -14,7 +17,25 @@ const AppUtils = {
// Check if the app is running inside electron
isRunningInsideElectron() {
return window && window.process && window.process.type === 'renderer';
+ },
+
+ getHomePath(mode) {
+ let path;
+
+ switch (mode) {
+ case BookieModes.EXCHANGE:
+ path = '/exchange';
+ break;
+ case BookieModes.SPORTSBOOK:
+ path = '/sportsbook';
+ break;
+ default:
+ path = '/exchange';
+ }
+
+ return path;
}
+
};
export default AppUtils;
diff --git a/src/utility/DateUtils.js b/src/utility/DateUtils.js
index 39c317121..a0fcd5490 100644
--- a/src/utility/DateUtils.js
+++ b/src/utility/DateUtils.js
@@ -113,6 +113,22 @@ const DateUtils = {
return formatted;
},
+ getMonthDayAndTime(date) {
+ let wrappedDate = moment(date);
+ let formatted = wrappedDate.format('MMM D H:mm');
+
+ if (
+ wrappedDate
+ .calendar()
+ .toLowerCase()
+ .indexOf('today') !== -1
+ ) {
+ formatted = I18n.t('mybets.today');
+ }
+
+ return formatted;
+ },
+
/**
* calculate start date and end date given time range period data
*
diff --git a/src/utility/SportsbookUtils.js b/src/utility/SportsbookUtils.js
new file mode 100644
index 000000000..ecf888c2a
--- /dev/null
+++ b/src/utility/SportsbookUtils.js
@@ -0,0 +1,292 @@
+import {BackingWidgetTypes, BackingWidgetLayouts} from '../constants/BackingWidgetTypes';
+import Immutable from 'immutable';
+import {EventStatus} from '../constants';
+import moment from 'moment';
+
+const getDescriptionAsType = (description) => {
+ return description
+ .replace(/[\/\- ]/, '')
+ .split(' ')[0]
+ .toUpperCase();
+};
+
+/**
+ * getColumnSize()
+ *
+ * Depending on the type of the backingWidget (determined by the title),
+ * the UI needs to render the BM's in the widget on a column basis.
+ *
+ * The number of columns will vary from type to type with some overlap between
+ * widgets.
+ *
+ * @param {*} backingWidget
+ * @returns - An integer containing the column size with respect to ant designs column layout.
+ *
+ * @note - Antd uses a 24 column layout. This function takes that into account. The calling function
+ * should be able to call this function like so
+ */
+const getColumnSize = (type, eventFlag = false) => {
+ if (type) {
+ type = getDescriptionAsType(type);
+ }
+
+ if (BackingWidgetLayouts[type]) {
+ return BackingWidgetLayouts[type].columns[eventFlag ? 'eventFlag' : 'default'];
+ }
+
+ // The default column width for events is 7, 12 otherwise
+ return eventFlag ? 7 : 12;
+};
+
+/**
+ * groupOverUnders()
+ *
+ * This function examines the bmgs passed in, and groups the bmgs that belong to the
+ * category over/under.
+ *
+ * @param {*} bettingMarketGroups -
+ * @returns - A new list of wherein all over/under betting markets appear to live within
+ * a single BMG
+ */
+const groupOverUnders = (bettingMarketGroups) => {
+ let overUnders = Immutable.Map();
+ overUnders = overUnders.set('event_id', bettingMarketGroups.first().get('event_id'));
+ overUnders = overUnders.set('asset_id', bettingMarketGroups.first().get('asset_id'));
+ overUnders = overUnders.set(
+ 'delay_before_settling',
+ bettingMarketGroups.first().get('delay_before_settling')
+ );
+ overUnders = overUnders.set('description', 'Over/Under');
+ overUnders = overUnders.set('total_matched_bets_amount', 0);
+ overUnders = overUnders.set('bettingMarkets', Immutable.List());
+
+ let overUnderBMs = [];
+
+ let nonOverUnders = Immutable.List();
+
+ let newBettingMarketGroups = Immutable.List();
+
+ const overUnder = 'over/under';
+
+ // Iterate through the BMGs passed in
+ bettingMarketGroups.forEach((bmg) => {
+ // Check the current BMG's description to see if it matches over/under
+ let description = bmg.get('description').toLowerCase();
+
+ if (description.includes(overUnder)) {
+ // Add the list of BMs in the current BM to the list of over/unders
+ let bettingMarkets = bmg.get('bettingMarkets');
+
+ if (bettingMarkets) {
+ for (let i = 0; i < bmg.get('bettingMarkets').length; i++) {
+ overUnderBMs.push(bmg.get('bettingMarkets')[i]);
+ }
+ }
+ } else {
+ nonOverUnders = nonOverUnders.push(bmg);
+ }
+ });
+
+ // If there were over unders present in the bettingMarkets,
+ // add them to the list
+ if (overUnderBMs.length > 0) {
+ overUnders = overUnders.set('bettingMarkets', overUnderBMs.sort());
+ newBettingMarketGroups = newBettingMarketGroups.push(overUnders);
+ }
+
+ if (nonOverUnders.size > 0) {
+ nonOverUnders.forEach((bmg) => {
+ newBettingMarketGroups = newBettingMarketGroups.push(bmg);
+ });
+ }
+
+ return newBettingMarketGroups;
+};
+
+/**
+ * centerTheDraw()
+ *
+ * With respect to Match Odds, there are 3 betting markets, not just win and lose.
+ * "The Draw" needs to come second, or in the middle, with respect to the two other teams.
+ *
+ * @param {*} bettingMarketGroup - The bettingMarketGroup containing the two teams and a draw bm
+ * @returns - The bettingMarketGroup wherein the draw lives in the [1] element of the list
+ */
+const centerTheDraw = (bettingMarketGroup) => {
+ let bettingMarkets = bettingMarketGroup.get('bettingMarkets');
+ const description = getDescriptionAsType(bettingMarketGroup.get('description'));
+
+ if (bettingMarkets) {
+ // If there is not the correct number of markets within the BMG, then return the original object
+ if (bettingMarkets.length !== BackingWidgetLayouts[description].numberOfMarkets) {
+ return bettingMarketGroup;
+ }
+
+ // Find the index that the draw is in
+ let drawIndex = -1;
+ bettingMarkets.forEach((bm, index) => {
+ if (
+ bm
+ .get('description')
+ .replace(/\s/g, '')
+ .toUpperCase() === 'THEDRAW'
+ ) {
+ drawIndex = index;
+ }
+ });
+
+ // If the draw is not in the middle. Swap it with the middle index.
+ if (drawIndex !== 1) {
+ let temp = bettingMarkets[1];
+ bettingMarkets[1] = bettingMarkets[drawIndex];
+ bettingMarkets[drawIndex] = temp;
+ }
+ }
+
+ bettingMarketGroup = bettingMarketGroup.set('bettingMarkets', bettingMarkets);
+
+ return bettingMarketGroup;
+};
+
+/**
+ * prioritySort()
+ *
+ * Priority sort uses the layout priorities defined in the constant BackingWidgetLayouts to order
+ * the bettingMarketGroups inside of the bettingMarketGroups list.
+ *
+ * @param {*} bettingMarketGroups - An Immutable list containing bettingMarketGroups
+ * @returns - A listed sorted by the priorities defined in BackingWidgetLayouts
+ */
+const prioritySort = (bettingMarketGroups) => {
+ bettingMarketGroups = bettingMarketGroups.sort((a, b) => {
+ let typeA = getDescriptionAsType(a.get('description'));
+ let typeB = getDescriptionAsType(b.get('description'));
+
+ if (BackingWidgetLayouts[typeA] && BackingWidgetLayouts[typeB]) {
+ return BackingWidgetLayouts[typeA].order > BackingWidgetLayouts[typeB].order;
+ } else {
+ return true;
+ }
+ });
+ return bettingMarketGroups;
+};
+
+/**
+ * isMatchOdds()
+ *
+ * This function will determine if the betting market group is a match odds betting market group
+ *
+ * @param {*} bettingMarketGroup - The betting market group in question.
+ * @returns - True if the bmg is a match odds bmg. False otherwise.
+ */
+const isMatchodds = (bettingMarketGroup) => {
+ const description = getDescriptionAsType(bettingMarketGroup.get('description'));
+
+ if (description === BackingWidgetTypes.MATCHODDS) {
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * isMoneyline()
+ *
+ * This function will determine if the betting market group is a moneyline betting market group
+ *
+ * @param {*} bettingMarketGroup - The betting market group in question.
+ * @returns - True if the bmg is a moneyline bmg. False otherwise.
+ */
+const isMoneyline = (bettingMarketGroup) => {
+ const description = getDescriptionAsType(bettingMarketGroup.get('description'));
+
+ if (description === BackingWidgetTypes.MONEYLINE) {
+ return true;
+ }
+
+ return false;
+};
+
+
+/**
+ * isInThePast()
+ *
+ * This function determines whether or not the event passed in is in the past
+ *
+ * @param {*} event - The event in question
+ * @returns - True if the event is in the past, false otherwise.
+ */
+const isInThePast = (event) => {
+ let today = moment();
+ let date = moment(event.get('start_time'));
+
+ if (date.isBefore(today)) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
+const sortAndCenter = (bettingMarketGroups) => {
+ bettingMarketGroups = prioritySort(bettingMarketGroups);
+
+ bettingMarketGroups.forEach((bmg) => {
+ // If there is a betting market group that belongs to match odds
+ if (isMatchodds(bmg)) {
+ // The draw needs to be centered
+ bmg = centerTheDraw(bmg);
+ }
+ });
+ return bettingMarketGroups;
+};
+
+const isAbleToBet = (eventStatus) => {
+ if (eventStatus) {
+ switch (eventStatus[1]) {
+ case EventStatus.FINISHED:
+ case EventStatus.FROZEN:
+ case EventStatus.COMPLETED:
+ case EventStatus.SETTLED:
+ case EventStatus.CANCELED:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ return true;
+};
+
+const hasBettingMarkets = (bettingMarketGroup) => {
+ return !!bettingMarketGroup.get('bettingMarkets');
+};
+
+const sortEventsByDate = (eventList) => {
+
+ eventList = eventList.sort((a, b) => {
+ if (moment(a.get('start_time')).isBefore(moment(b.get('start_time')))) {
+ return -1;
+ } else {
+ return 1;
+ }
+ });
+
+ return eventList;
+};
+
+const SportsbookUtils = {
+ centerTheDraw,
+ getColumnSize,
+ getDescriptionAsType,
+ groupOverUnders,
+ hasBettingMarkets,
+ isAbleToBet,
+ isInThePast,
+ isMatchodds,
+ isMoneyline,
+ prioritySort,
+ sortAndCenter,
+ sortEventsByDate
+};
+
+export default SportsbookUtils;
diff --git a/src/utility/index.js b/src/utility/index.js
index 5d5bf03de..8bc61a257 100644
--- a/src/utility/index.js
+++ b/src/utility/index.js
@@ -15,6 +15,7 @@ import ObjectUtils from './ObjectUtils';
import MyWagerUtils from './MyWagerUtils';
import AuthUtils from './AuthUtils';
import HelpAndSupportUtils from './HelpAndSupportUtils';
+import SportsbookUtils from './SportsbookUtils';
export {
AppUtils,
@@ -33,5 +34,6 @@ export {
MyWagerUtils,
ObjectUtils,
HelpAndSupportUtils,
- AuthUtils
+ AuthUtils,
+ SportsbookUtils,
};