/* eslint-disable react/no-unused-state */
import { Component } from 'react';
import { SinglesMinMaxWinCalculator } from 'system-manager';
import Constants, { MINIMUM_MULTIPLE_ODDS_LENGTH, NO_BONUS } from '../../constants';
import { SystemMatches, SystemSplitManager, OddsByMatches } from '../../utils/systemManager';
import {
setupCalc,
newObjectRef,
filterDuplicateSelections,
convertIntoUnits,
} from '../../utils/common';
/**
* Betslip handler functions
*
* @class
*/
class BetslipHandler extends Component {
/**
* Setup placebet settings calculate total stake, bonus, potential win (min/max)
*
* @function
* @param {Array} singleStakes
* @param {Array} combi
* @param {number} multiple
* @param {number} other
* @returns {object} Settings (total stake, potential win (min/max), bonus amount)
*/
setupPlacebet = (singleStakes, combi, multiple, other) => {
const selectedType = this.props.selectedBetType;
const systemManager = this.state.systemManager;
const lastKey = this.props.numberOfSelections;
const maxPotWin = this.state.maxPotWin;
const minPotWin = this.state.minPotWin;
const odds = this.props.odds;
const combinations = this.props.combinations?.length > 0
? this.props.combinations : this.combinations;
if (selectedType !== Constants.BET_TYPE.NO_BET) {
switch (selectedType) {
case Constants.BET_TYPE.SINGLE: {
const singlesOdds = OddsByMatches(this.props.selectedOdds, singleStakes);
const winnings = SinglesMinMaxWinCalculator(singlesOdds, this.props.winCap);
const totalStake = singleStakes.reduce((accumulator, value) => (
accumulator + Number(value)
), 0);
return ({
totalStake,
potWinMax: convertIntoUnits(winnings.max),
potWinMin: convertIntoUnits(winnings.min),
bonusAmt: NO_BONUS,
});
}
case Constants.BET_TYPE.MULTIPLE: {
if (systemManager) {
const percentage = systemManager[lastKey].BONUSPERCENTAGE;
const potWinMax = maxPotWin[maxPotWin.length - 1];
const rez = this.calcForStakes(
multiple, potWinMax, potWinMax, null, systemManager[lastKey],
this.props.betslipSettings.minStake, false,
);
return {
...rez,
percentage,
};
}
break;
}
case Constants.BET_TYPE.COMBINATION:
return this.calcForStakes(
combi, maxPotWin, minPotWin, combinations,
systemManager, this.props.betslipSettings.minStakePerSelection, true,
);
case Constants.BET_TYPE.FREEBET: {
if (systemManager) {
const potWin = odds.map((o) => o.odd);
const potWinMax = systemManager[lastKey]
? systemManager[lastKey].POTWINMAX : potWin;
const potWinMin = systemManager[lastKey]
? systemManager[lastKey].POTWINMIN : potWin;
const bonusAmt = systemManager[lastKey]
? systemManager[lastKey].BONUSMAX * other
: null;
const percentage = systemManager[lastKey]
? systemManager[lastKey].BONUSPERCENTAGE
: null;
return ({
potWinMax: (potWinMax - 1) * other,
potWinMin: (potWinMin - 1) * other,
minStake: this.props.betslipSettings.minStake,
bonusAmt,
percentage,
});
}
break;
}
default:
return this.calcForStakes(
other, maxPotWin, minPotWin, combinations[1],
systemManager, this.props.betslipSettings.minStakePerSelection, false,
);
}
}
return this.setup;
}
/**
* Calculate settings for selected bet type
*
* @function
* @param {number|Array} stake
* @param {number|Array} bonusOddMax
* @param {number|Array} bonusOddMin
* @param {number|null|Array} numOfBets
* @param {object|null} manager
* @param {number} minStake
* @param {boolean} isArray
* @returns {object} Settings (total stake, potential win (min/max), bonus amount)
*/
calcForStakes = (stake, bonusOddMax, bonusOddMin, numOfBets, manager, minStake, isArray) => {
let calc = newObjectRef(this.setup);
if (isArray) {
for (let i = 0; i < stake.length; i += 1) {
calc = setupCalc(
calc, stake[i], bonusOddMax[i], bonusOddMin[i],
numOfBets && numOfBets[i], manager && manager[i + 1], minStake,
);
}
} else {
calc = setupCalc(calc, stake, bonusOddMax, bonusOddMin, numOfBets, manager, minStake);
}
return {
...calc,
minStake,
};
}
/**
* Change stake value
*
* @function
* @param {numer} val
* @returns {void}
*/
updateStakeValue = (val) => {
const value = val <= this.props.betslipSettings.maxStake
? val
: this.props.betslipSettings.maxStake;
const {
singleStakes, combiStakes,
} = this.props;
const selectedBetType = this.props.selectedBetType;
let multipleStake = this.props.multipleStake;
let otherStake = this.props.otherStake;
if (selectedBetType === Constants.BET_TYPE.SINGLE) {
singleStakes[this.state.stakeInFocus] = value || '';
} else if (selectedBetType === Constants.BET_TYPE.COMBINATION) {
combiStakes[this.state.stakeInFocus] = value || '';
} else if (selectedBetType === Constants.BET_TYPE.MULTIPLE) {
multipleStake = value || '';
} else {
otherStake = value || '';
}
this.props.setStakes({
singleStakes,
multipleStake,
combiStakes,
otherStake,
});
const settings = this.setupPlacebet(singleStakes, combiStakes, multipleStake, otherStake);
this.setState({
settings,
rerender: !this.state.rerender,
stakeHasChanges: true,
maxPotWinPayoutErr: this.checkMaximumWinningPayoutErr(settings.potWinMax),
});
}
/**
* Change stake value
*
* @function
* @returns {void}
*/
updateCalc = () => {
const {
singleStakes, combiStakes,
} = this.props;
const multipleStake = this.props.multipleStake;
const otherStake = this.props.otherStake;
this.props.setStakes({
singleStakes,
multipleStake,
combiStakes,
otherStake,
});
const settings = this.setupPlacebet(singleStakes, combiStakes, multipleStake, otherStake);
this.setState({
settings,
rerender: !this.state.rerender,
stakeHasChanges: true,
maxPotWinPayoutErr: this.checkMaximumWinningPayoutErr(settings.potWinMax),
});
}
/**
* Change stake value inserted in input
*
* @function
* @param {Event} e
* @returns {void}
*/
changeStakeInput = (e) => {
let stake = e && e.target.value;
stake = stake === 0 || stake === '' ? 0 : parseInt(stake.split(',').join(''));
if ((this.props.selectedBetType !== Constants.BET_TYPE.FREEBET
&& stake) || stake === 0) {
const value = stake > 0 ? stake : 0;
// eslint-disable-next-line no-restricted-globals
if (!isNaN(value)) {
this.updateStakeValue(value);
} else {
this.updateStakeValue(0);
}
}
}
/**
* Change stake value by clicking on stake buttons
*
* @function
* @param {Event} e
* @returns {void}
*/
addButton = (e) => {
const selectedType = this.props.selectedBetType;
const value = e.target.id;
if (selectedType === Constants.BET_TYPE.FREEBET) {
this.updateStakeValue(parseInt(value));
} else {
const newState = selectedType === Constants.BET_TYPE.SINGLE
? this.props.singleStakes[this.state.stakeInFocus] || 0
: selectedType === Constants.BET_TYPE.MULTIPLE
? this.props.multipleStake || 0
: selectedType === Constants.BET_TYPE.COMBINATION
? this.props.combiStakes[this.state.stakeInFocus] || 0
: this.props.otherStake || 0;
this.updateStakeValue(parseInt(newState) + parseInt(value));
}
}
/**
* Change stake value to 0
*
* @function
* @returns {void}
*/
clearStake = () => {
this.updateStakeValue(0);
}
/**
* Initialize split bet tab
*
* @function
* @param {number} num
* @param {number} numOfSplitBets
* @returns {void}
*/
initSplitBetTab = (num, numOfSplitBets) => {
this.combinationTypes = [];
this.combinations = [num, numOfSplitBets];
this.combinationTypes.push(`${num < 4 ? Constants.DIGITS[num - 1] : `${Constants.DIGITS[num - 1]} Folds`} (${numOfSplitBets})`);
}
/**
* Initialize combination bet tab
*
* @function
* @param {object} systemManager
* @param {boolean} isDeleting
* @returns {object} Odds (min/max), potential win (min/max)
*/
initSystemTab = (systemManager, isDeleting) => {
const maxOdd = [];
const minOdd = [];
const maxPotWin = [];
const minPotWin = [];
this.combinationTypes = [];
this.combinations = [];
let combiStakes = this.props.combiStakes || [];
let singleStakes = this.props.singleStakes || [];
let multipleStake = this.props.multipleStake || '';
let otherStake = this.props.otherStake || '';
for (let i = 0, j = 1; i < this.props.numberOfSelections; i += 1, j += 1) {
const {
POTWINMAX, POTWINMIN, ODDMAX, ODDMIN, NUM,
} = systemManager[j];
this.combinationTypes.push(`${i < 3 ? Constants.DIGITS[i] : `${Constants.DIGITS[i]} Folds`} (${NUM})`);
this.combinations.push(NUM);
maxOdd.push(ODDMAX);
minOdd.push(ODDMIN);
maxPotWin.push(POTWINMAX);
minPotWin.push(POTWINMIN);
if (isDeleting || this.props.placebetAfterLogin) {
combiStakes = [];
singleStakes = [];
multipleStake = '';
otherStake = '';
this.setState({
maxPotWin: [],
minPotWin: [],
settings: {
totalStake: 0,
potWinMax: 0,
potWinMin: 0,
bonusAmt: 0,
},
});
}
}
if (this.props.placebetBackupLogin !== true) {
this.props.setStakes({
multipleStake,
combiStakes,
singleStakes,
otherStake,
});
}
return {
minOdd, maxOdd, maxPotWin, minPotWin,
};
}
/**
* Change tab from bottom navigation
*
* @function
* @param {string} [betType]
* @param {boolean} [isDeleting]
* @returns {void}
*/
changeBetType = (betType, isDeleting) => {
const propOdds = this.props.odds;
if (this.props.numberOfSelections === 0) {
this.props.setSelectedBetType({
selectedBetType: Constants.BET_TYPE.NO_BET,
});
} else {
const { numOfMatchesForSys, oddsPerMatch } = SystemMatches(propOdds);
if (numOfMatchesForSys === 1
&& betType !== Constants.BET_TYPE.FREEBET) {
this.props.setSelectedBetType({
selectedBetType: Constants.BET_TYPE.SINGLE,
});
} else if (numOfMatchesForSys === this.props.numberOfSelections
&& parseInt(this.props.numberOfSelections) > 0) {
const odds = propOdds.map((o) => o.odd);
const systemManager = SystemSplitManager(
odds,
this.props.bonusPercentages,
this.props.bonusOddsThreshold,
false,
this.props.winCap,
);
const selectedType = betType && betType !== Constants.BET_TYPE.SPLIT_COLUMN
&& betType !== Constants.BET_TYPE.NO_BET
? betType : Constants.BET_TYPE.MULTIPLE;
const {
minOdd, maxOdd, minPotWin, maxPotWin,
} = this.initSystemTab(systemManager, isDeleting);
this.props.setSelectedBetType({
selectedBetType: selectedType,
});
this.setState({
minOdd,
maxOdd,
maxPotWin,
minPotWin,
stakeInFocus: 0,
systemManager,
});
} else {
const splitManager = SystemSplitManager(
oddsPerMatch,
this.props.bonusPercentages,
this.props.bonusOddsThreshold,
true,
this.props.winCap,
);
const selectedType = betType && betType !== Constants.BET_TYPE.NO_BET
? betType : Constants.BET_TYPE.SPLIT_COLUMN;
for (let i = 0; i < this.props.combiStakes.length; i += 1) {
this.props.combiStakes[i] = '';
}
this.props.setSelectedBetType({
selectedBetType: selectedType,
});
this.initSplitBetTab(numOfMatchesForSys, splitManager.NUM);
this.setState({
numOfMatchesForSys,
numOfSplitBets: splitManager.NUM,
minOdd: [splitManager.ODDMIN],
maxOdd: [splitManager.ODDMAX],
maxPotWin: [splitManager.POTWINMAX],
minPotWin: [splitManager.POTWINMIN],
stakeInFocus: 0,
systemManager: splitManager,
});
}
}
}
/**
* Parse stake string for placebet API
*
* @function
* @returns {object} Stake, bet type, and free bet id if it is free bet
*/
parsePlacebetStake = () => {
const selectedType = this.props.selectedBetType;
const combinations = this.props.prevCombinations?.length > 0
? this.props.prevCombinations : this.combinations;
let type;
let stake = '';
let selectedStake = null;
let freeBetId = null;
switch (selectedType) {
case Constants.BET_TYPE.FREEBET: {
stake = this.props.otherStake * 100;
freeBetId = this.props.freeBets.find((bet) => (
parseInt(bet.stake / 100) === this.props.otherStake)).id;
if (this.props.numberOfSelections === 1) {
type = Constants.BET_TYPE_VALUES.SINGLE;
stake = `1:${stake}&T:${stake}`;
} else {
type = Constants.BET_TYPE_VALUES.MULTIPLE;
}
break;
}
case Constants.BET_TYPE.SINGLE: {
selectedStake = this.props.singleStakes;
type = Constants.BET_TYPE_VALUES.SINGLE;
break;
}
case Constants.BET_TYPE.MULTIPLE: {
stake = this.props.multipleStake * 100;
type = Constants.BET_TYPE_VALUES.MULTIPLE;
break;
}
case Constants.BET_TYPE.SPLIT_COLUMN: {
stake = `${combinations[0]}:${this.props.otherStake * 100}&T:${this.props.otherStake * 100 * combinations[1]}`;
type = Constants.BET_TYPE_VALUES.SPLIT_COLUMN;
break;
}
default: {
selectedStake = this.props.combiStakes;
type = Constants.BET_TYPE_VALUES.COMBINATION;
}
}
if (selectedStake) {
for (let s = 0; s < selectedStake.length; s += 1) {
if (selectedStake[s]) { stake += `${s + 1}:${selectedStake[s] * 100}&`; }
}
stake += `T:${this.state.settings.totalStake * 100}`;
}
return {
stake, type, freeBetId,
};
}
/**
* Validate max stakes before placebet
*
* @function
* @returns {boolean}
*/
checkMaxStakeError = () => {
if (this.props.selectedBetType === Constants.BET_TYPE.SINGLE
|| this.props.selectedBetType === Constants.BET_TYPE.COMBINATION
|| this.props.selectedBetType === Constants.BET_TYPE.SPLIT_COLUMN) {
return this.state.settings.totalStake > this.props.betslipSettings.maxStake;
}
return false;
}
/**
* Validate min stakes before placebet
*
* @function
* @returns {boolean}
*/
checkMinStakeError = () => {
let flag = true;
switch (this.props.selectedBetType) {
case Constants.BET_TYPE.SINGLE:
return this.props.singleStakes.findIndex((stake) => {
if (stake) flag = false;
return parseInt(stake) < this.props.betslipSettings.minStake;
}) !== -1 || flag;
case Constants.BET_TYPE.COMBINATION:
return this.props.combiStakes.findIndex((stake) => {
if (stake) flag = false;
return parseInt(stake) < this.props.betslipSettings.minStakePerSelection;
}) !== -1 || flag;
case Constants.BET_TYPE.MULTIPLE:
return !this.props.multipleStake
|| parseInt(this.props.multipleStake) < this.props.betslipSettings.minStake;
case Constants.BET_TYPE.FREEBET:
case Constants.BET_TYPE.SPLIT_COLUMN:
return !this.props.otherStake
|| parseInt(this.props.otherStake) < this.props.betslipSettings.minStakePerSelection;
default:
return false;
}
}
/**
* Validate maximum winning payout before placebet
*
* @function
* @param {number} potWinMax
* @returns {boolean}
*/
checkMaximumWinningPayoutErr = (potWinMax) => {
const win = potWinMax || this.state.settings.potWinMax;
return win > (this.props.winCap / 100);
};
/**
* Validate inputs fields before placebet action
*
* @function
* @returns {boolean}
*/
checkError = () => ({
minStakeErr: this.checkMinStakeError(),
maxStakeErr: this.checkMaxStakeError(),
maxPotWinPayoutErr: this.checkMaximumWinningPayoutErr(null),
});
/**
* Select single bet type from bottom navigation
*
* @function
* @returns {void}
*/
selectSingleBet = () => {
if (this.props.selectedBetType !== Constants.BET_TYPE.NO_BET) {
this.props.setSelectedBetType({ selectedBetType: Constants.BET_TYPE.SINGLE });
this.setState({
stakeInFocus: 0,
});
}
this.changeBetType(Constants.BET_TYPE.SINGLE);
}
/**
* Select multiple bet type from bottom navigation
*
* @function
* @returns {void}
*/
selectMultipleBet = () => {
if ((this.props.selectedBetType !== Constants.BET_TYPE.NO_BET
&& this.props.selectedOdds?.length >= MINIMUM_MULTIPLE_ODDS_LENGTH)) {
this.props.setSelectedBetType({ selectedBetType: Constants.BET_TYPE.MULTIPLE });
}
this.changeBetType(Constants.BET_TYPE.MULTIPLE);
}
/**
* Select combination bet type from bottom navigation (Can be combinations or split bet)
*
* @function
* @returns {void}
*/
openOtherTypes = () => {
const type = this.props.selectedBetType;
if (type !== Constants.BET_TYPE.NO_BET) {
const uniqueSelectedOddsArr = filterDuplicateSelections(this.props.selectedOdds);
const newSelectedType = uniqueSelectedOddsArr.length !== this.props.selectedOdds.length
? Constants.BET_TYPE.SPLIT_COLUMN
: Constants.BET_TYPE.COMBINATION;
this.props.setSelectedBetType({
selectedBetType: newSelectedType,
});
this.setState({
stakeInFocus: 0,
});
}
}
/**
* Select free bet dtype from bottom navigation
*
* @function
* @returns {void}
*/
openFreeBetType = () => {
if ((this.props.selectedBetType !== Constants.BET_TYPE.NO_BET
&& this.props.selectedBetType !== Constants.BET_TYPE.SPLIT_COLUMN)) {
this.props.setSelectedBetType({ selectedBetType: Constants.BET_TYPE.FREEBET });
this.setState({
stakeInFocus: 0,
});
}
}
/**
* Return first 5 bets amount
*
* @function
* @returns {Array}
*/
getFreeBetsAmount = () => {
const freeBetsAmounts = [];
this.props.freeBets.map((bet) => {
const stake = parseInt(bet.stake / 100);
if (freeBetsAmounts.findIndex((b) => b === stake) === -1 && freeBetsAmounts.length < 5) {
freeBetsAmounts.push(stake);
}
});
freeBetsAmounts.sort((a, b) => (a > b ? 1 : -1));
return freeBetsAmounts;
}
/**
* Set input in focus by input index
*
* @function
* @param {number} index
* @returns {void}
*/
setInputInFocus = (index) => {
this.tid1 = setTimeout(() => {
this.setState({
stakeInFocus: index,
showButtons: true,
});
}, 100);
if (this.state.error === true || this.props.placeBetErrorMgs) {
this.closeError();
}
}
/**
* Set input out of focus and hide buttons
*
* @function
* @param {Event} e
* @returns {void}
*/
setInputOutOfFocus = (e) => {
if (e) {
const targetIsButton = e.relatedTarget != null && e.relatedTarget.classList.contains('chip-button');
if ((!targetIsButton) || e.relatedTarget === null) {
this.tid2 = setTimeout(() => {
this.setState({ showButtons: false });
}, 100);
} else if (targetIsButton) {
e.target.focus();
}
}
}
/**
* Clear all timeouts
*
* @function
* @returns {void}
*/
clearAllTimeouts = () => {
clearTimeout(this.tid1);
clearTimeout(this.tid2);
}
/**
* Clear all selections
*
* @function
* @returns {void}
*/
clearAllSelections = () => {
this.props.toggleSelectedOdds({ selectedOdds: [], index: null });
this.props.clearAllSelections();
}
/**
* Clear selected row
*
* @function
* @param {number} index
* @returns {void}
*/
clearSelection = (index) => {
const odds = this.props.odds;
if (odds.length > 1) {
const selectedOdds = Array.from(odds);
selectedOdds.splice(index, 1);
this.props.toggleSelectedOdds({ selectedOdds, index });
} else {
this.clearAllSelections();
}
this.updateStakeValue(0);
this.setState({
stakeInFocus: 0,
settings: {
totalStake: 0,
potWinMax: 0,
potWinMin: 0,
bonusAmt: 0,
},
});
this.props.setStakes({
singleStakes: [],
multipleStake: '',
combiStakes: [],
otherStake: '',
});
}
/**
* Close error modal
*
* @function
* @returns {void}
*/
closeError = () => {
this.setState({
minStakeErr: false,
maxStakeErr: false,
maxPotWinPayoutErr: false,
});
this.props.closePlaceBetErrorMessage();
}
/**
* Close confirmation modal
*
* @function
* @returns {void}
*/
closeSlipConfirmation = () => {
this.setState({ openNewPlacedBetsModal: true });
this.props.closeSlipConfirmation();
/* if in freebets and no free bets */
if (this.props.freeBets.length === 0
&& this.props.selectedBetType === Constants.BET_TYPE.FREEBET) {
if (this.props.numberOfSelections === 1) {
this.props.setSelectedBetType({ selectedBetType: Constants.BET_TYPE.SINGLE });
} else {
this.props.setSelectedBetType({ selectedBetType: Constants.BET_TYPE.MULTIPLE });
}
}
}
/**
* Toggle betslip modal
*
* @function
* @returns {void}
*/
toggleBetslipModal = () => {
if (this.props.betSlipConfimation) {
this.clearAllSelections();
} else {
this.props.toggleBetslip();
}
}
/**
* Place bet action
*
* @function
* @returns {void}
*/
placebet = () => {
const propOdds = this.props.odds;
const { minStakeErr, maxStakeErr, maxPotWinPayoutErr } = this.checkError();
if (minStakeErr === false && maxStakeErr === false) {
const odds = propOdds.map((odd) => `${odd.league}:${odd.matchId + 1}:${odd.selectionId}`).join('$');
const placedSelection = propOdds.map((odd) => `${odd.league}:${odd.matchId + 1}:${odd.selectionId}:${odd.odd}`);
const payload = this.parsePlacebetStake();
const maxPotWin = Math.round(this.state.settings.potWinMax * 100 + 0.0001).toFixed(0);
let cappedMaxPotWin = parseInt(maxPotWin) > this.props.winCap
? this.props.winCap.toFixed(0) : maxPotWin;
// handle case when win is super big and contains e+. example 1.5623e+22
if (typeof cappedMaxPotWin === 'string' && cappedMaxPotWin.indexOf('e+') !== -1) {
cappedMaxPotWin = this.props.winCap.toFixed(0);
}
if (!this.props.user && this.props.selectedBetType) {
this.props.setSelectedBetType({
selectedBetType: this.props.selectedBetType,
});
this.props.setBackUpLoginProps({
selectedOdds: this.props.selectedOdds,
stakes: {
multipleStake: this.props.multipleStake,
combiStakes: this.props.combiStakes,
singleStakes: this.props.singleStakes,
},
numberOfSelections: this.props.numberOfSelections,
placebetPayload: payload,
prevCombinationTypes: this.combinationTypes,
prevCombinations: this.combinations,
});
}
this.props.placebetRequest({
odds,
placedSelection,
maxPotWin: cappedMaxPotWin,
roundId: this.props.roundId,
...payload,
});
this.setState({
minStakeErr: false,
maxStakeErr: false,
stakeHasChanges: false,
});
} else {
this.setState({
minStakeErr,
maxStakeErr,
maxPotWinPayoutErr,
});
}
}
}
export default BetslipHandler;